mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Implement support for public/ webroot folder (#7741)
* API Implement support for public/ webroot folder * Bugfixes and refactor based on feedback
This commit is contained in:
parent
ea50b01d32
commit
8d077203d4
@ -76,6 +76,7 @@ before_script:
|
||||
# Install composer dependencies
|
||||
- export PATH=~/.composer/vendor/bin:$PATH
|
||||
- composer validate
|
||||
- mkdir ./public
|
||||
- if [[ $DB == PGSQL ]]; then composer require silverstripe/postgresql:2.0.x-dev --no-update; fi
|
||||
- if [[ $DB == SQLITE ]]; then composer require silverstripe/sqlite3:2.0.x-dev --no-update; fi
|
||||
- composer require silverstripe/recipe-core:1.1.x-dev silverstripe/admin:1.1.x-dev silverstripe/versioned:1.1.x-dev --no-update
|
||||
|
@ -40,9 +40,9 @@ $ composer create-project silverstripe/installer ./silverstripe
|
||||
## Install and configure
|
||||
* Option 1: Environment file - Set up a file named `.env` file either in the webroot and setup as per the [Environment Management process](/getting_started/environment_management).
|
||||
|
||||
* Option 2: Installer - Visit `http://localhost/silverstripe` - you will see SilverStripe's installation screen.
|
||||
* Option 2: Installer - Visit `http://localhost/silverstripe/public` - you will see SilverStripe's installation screen.
|
||||
* You should be able to click "Install SilverStripe" and the installer will do its thing. It takes a minute or two.
|
||||
* Once the installer has finished, visit `http://localhost/silverstripe`. You should see your new SilverStripe site's
|
||||
* Once the installer has finished, visit `http://localhost/silverstripe/public`. You should see your new SilverStripe site's
|
||||
home page.
|
||||
|
||||
## Troubleshooting
|
||||
|
@ -190,7 +190,7 @@ After gettng the code installed, make sure you set the folder permissions proper
|
||||
|
||||
## Start SilverStripe installer
|
||||
|
||||
Open a browser and point it to `http://localhost/ss`
|
||||
Open a browser and point it to `http://localhost/ss/public`
|
||||
|
||||
If an installation screen shows up, congratulations! We're very close now.
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
# Lightttpd
|
||||
|
||||
1. Lighttpd works fine so long as you provide a custom config. Add the following to lighttpd.conf **BEFORE** installing
|
||||
Silverstripe. Replace "yoursite.com" and "/home/yoursite/public_html/" below.
|
||||
Silverstripe. Replace "yoursite.com" and "/home/yoursite/public/" below.
|
||||
|
||||
|
||||
$HTTP["host"] == "yoursite.com" {
|
||||
server.document-root = "/home/yoursite/public_html/"
|
||||
server.document-root = "/home/yoursite/public/"
|
||||
|
||||
# Disable directory listings
|
||||
dir-listing.activate = "disable"
|
||||
@ -14,16 +14,11 @@ Silverstripe. Replace "yoursite.com" and "/home/yoursite/public_html/" below.
|
||||
url.access-deny += ( ".ss" )
|
||||
static-file.exclude-extensions += ( ".ss" )
|
||||
|
||||
# Deny access to SilverStripe command-line interface
|
||||
$HTTP["url"] =~ "^/vendor/silverstripe/framework/cli-script.php" {
|
||||
# Deny access to vendor
|
||||
$HTTP["url"] =~ "^/vendor" {
|
||||
url.access-deny = ( "" )
|
||||
}
|
||||
|
||||
# Disable FastCGI in assets directory (so that PHP files are not executed)
|
||||
$HTTP["url"] =~ "^/assets/" {
|
||||
fastcgi.server = ()
|
||||
}
|
||||
|
||||
# Rewrite URLs so they are nicer
|
||||
url.rewrite-once = (
|
||||
"^/.*\.[A-Za-z0-9]+.*?$" => "$0",
|
||||
|
@ -16,11 +16,12 @@ If you don't fully understand the configuration presented here, consult the
|
||||
Especially be aware of [accidental php-execution](https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ "Don't trust the tutorials") when extending the configuration.
|
||||
</div>
|
||||
|
||||
But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`:
|
||||
But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`. This assumes
|
||||
you are running your site configuration with a separate `public/` webroot folder.
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
root /path/to/ss/folder;
|
||||
root /var/www/the-website/public;
|
||||
|
||||
server_name site.com www.site.com;
|
||||
|
||||
@ -44,52 +45,14 @@ But enough of the disclaimer, on to the actual configuration — typically in `n
|
||||
try_files $uri index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ /framework/.*(main|rpc|tiny_mce_gzip)\.php$ {
|
||||
fastcgi_keep_conn on;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /(mysite|framework|cms)/.*\.(php|php3|php4|php5|phtml|inc)$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ /\.. {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ \.ss$ {
|
||||
satisfy any;
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ web\.config$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ \.ya?ml$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ^~ /vendor/ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~* /silverstripe-cache/ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~* composer\.(json|lock)$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~* /(cms|framework)/silverstripe_version$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_keep_conn on;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
|
@ -57,35 +57,12 @@ Create `/etc/nginx/silverstripe.conf` and add this configuration:
|
||||
location ^~ /assets/ {
|
||||
try_files $uri =404;
|
||||
}
|
||||
location ~ /(mysite|framework|cms)/.*\.(php|php3|php4|php5|phtml|inc)$ {
|
||||
deny all;
|
||||
}
|
||||
location ~ /\.. {
|
||||
deny all;
|
||||
}
|
||||
location ~ \.ss$ {
|
||||
satisfy any;
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
}
|
||||
location ~ web\.config$ {
|
||||
deny all;
|
||||
}
|
||||
location ~ \.ya?ml$ {
|
||||
deny all;
|
||||
}
|
||||
location ^~ /vendor/ {
|
||||
deny all;
|
||||
}
|
||||
location ~* /silverstripe-cache/ {
|
||||
deny all;
|
||||
}
|
||||
location ~* composer\.(json|lock)$ {
|
||||
deny all;
|
||||
}
|
||||
location ~* /(cms|framework)/silverstripe_version$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
The above script passes all non-static file requests to `/index.php` in the webroot which relies on
|
||||
`hhvm.conf` being included prior so that php requests are handled.
|
||||
@ -97,7 +74,7 @@ e.g. `/etc/nginx/sites-enabled/mysite`:
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
root /var/www/mysite;
|
||||
root /var/www/mysite/public;
|
||||
server_name www.mysite.com;
|
||||
|
||||
error_log /var/log/nginx/mysite.error.log;
|
||||
|
@ -9,9 +9,11 @@ directories is meaningful to its logic.
|
||||
|
||||
Directory | Description
|
||||
--------- | -----------
|
||||
`assets/` | Images and other files uploaded via the SilverStripe CMS. You can also place your own content inside it, and link to it from within the content area of the CMS.
|
||||
`resources/`| Public files from modules (added automatically)
|
||||
`public/` | Webserver public webroot
|
||||
`public/assets/` | Images and other files uploaded via the SilverStripe CMS. You can also place your own content inside it, and link to it from within the content area of the CMS.
|
||||
`public/resources/` | Exposed public files added from modules. Folders within this parent will match that of the source root location.
|
||||
`vendor/` | SilverStripe modules and other supporting libraries (the framework is in `vendor/silverstripe/framework`)
|
||||
`themes/` | Standard theme installation location
|
||||
|
||||
## Custom Code Structure
|
||||
|
||||
|
@ -6,6 +6,8 @@ This version introduces many breaking changes, which in most projects can be man
|
||||
of automatic upgrade processes as well as manual code review. This document reviews these changes and will
|
||||
guide developers in preparing existing 3.x code for compatibility with 4.0
|
||||
|
||||
For users upgrading to 4.1.0 please see the specific [4.1.0 upgrading guide](4.1.0.md).
|
||||
|
||||
## Overview {#overview}
|
||||
|
||||
* Minimum version dependencies have increased; PHP 5.5 and Internet Explorer 11 (or other modern browser)
|
||||
|
208
docs/en/04_Changelogs/4.1.0.md
Normal file
208
docs/en/04_Changelogs/4.1.0.md
Normal file
@ -0,0 +1,208 @@
|
||||
# 4.1.0
|
||||
|
||||
## Overview {#overview}
|
||||
|
||||
* Support for public webroot folder `public/`
|
||||
* Better support for cross-platform filesystem path manipulation
|
||||
|
||||
## Upgrading
|
||||
|
||||
### Upgrade `public/` folder
|
||||
|
||||
This release allows the maintenance of a public webroot folder which separates all
|
||||
web-accessible files from protected project files (like the vendor folder
|
||||
and all of your PHP files). This makes your web hosting more secure, and
|
||||
less likely to leak information through accidentally deployed files like
|
||||
a project README. New projects will default to this behaviour. Existing
|
||||
projects (updating from 3.x or 4.0) will continue working as-is, but we
|
||||
strongly recommend switching to the public webroot structure in order to
|
||||
get the security benefits.
|
||||
|
||||
This folder name is not configurable, but is turned on by creating this folder, and off by ensuring
|
||||
this folder doesn't exist.
|
||||
|
||||
When separating the public webroot from the BASE_PATH it is necessary to move a few files during migration:
|
||||
|
||||
- Move `.htaccess` from base to `public/`
|
||||
- Move `index.php` from base to `public/`
|
||||
- Move `assets` folder (including the nested `assets/.protected` folder) into `public/`.
|
||||
This is the only folder which needs write permissions.
|
||||
- Ensure that the `public/resources` folder exists; If this folder already exists in root, you should
|
||||
delete this, and re-generate it by running `composer vendor-expose` in your root path.
|
||||
- Any public assets committed directly to your project intended to be served directly to the
|
||||
webserver. E.g. move `mysite/javascript/script.js` to `public/javascript/script.js`.
|
||||
You can then use `Requirements::css('javascript/script.js');` /
|
||||
`<% require css('javascript/script.js') %>` to include this file.
|
||||
- Ensure that the web-root configured for your server of choice points to the public/ folder instead of the base path.
|
||||
E.g. an apache virtualhost configuration would look like:
|
||||
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName mywebsite.com
|
||||
ServerAlias *.mywebsite.com
|
||||
VirtualDocumentRoot "/var/www/Sites/mywebsite/public/"
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
You may also need to add various changes to your code if you reference the BASE_PATH directly:
|
||||
|
||||
- You should use `Director::publicFolder()` instead of `Director::baseFolder()` if referring to the
|
||||
public folder.
|
||||
- You can check if a public folder exists with `Director::publicDir()`
|
||||
|
||||
### Example `public/` folder structure
|
||||
|
||||
For example, this is an existing folder structure:
|
||||
|
||||
```
|
||||
/var/www/mysite
|
||||
├── assets/
|
||||
│ └── .protected/
|
||||
├── mysite/
|
||||
│ ├── code/
|
||||
│ │ ├── Page.php
|
||||
│ │ └── PageController.php
|
||||
│ └── css/
|
||||
│ └── projectstyle.css
|
||||
├── resources/ _(auto-generated by vendor-plugin `composer vendor-expose` command)_
|
||||
│ └── silverstripe/
|
||||
│ └── blog/
|
||||
│ └── css/ _(symlink)_
|
||||
│ └── blog.css
|
||||
├── themes/
|
||||
│ └── mytheme/
|
||||
│ ├── css/
|
||||
│ │ └── theme.css
|
||||
│ └── templates/
|
||||
│ └── BlogPage.ss
|
||||
├── vendor/
|
||||
│ └── silverstripe/
|
||||
│ └── blog/
|
||||
│ ├── css/ _(exposed in blog composer.json)_
|
||||
│ │ └── blog.css
|
||||
│ └── composer.json
|
||||
├── .htaccess
|
||||
├── composer.json
|
||||
├── favicon.ico
|
||||
├── index.php
|
||||
└── install.php
|
||||
```
|
||||
|
||||
After migration the folder structure would look like:
|
||||
|
||||
```
|
||||
/var/www/mysite
|
||||
├── mysite/
|
||||
│ └── code/
|
||||
│ ├── Page.php
|
||||
│ └── PageController.php
|
||||
├── public/
|
||||
│ ├── assets/
|
||||
│ │ └── .protected/
|
||||
│ ├── css/
|
||||
│ │ └── somestyle.css
|
||||
│ ├── resources/ _(auto-generated by vendor-plugin `composer vendor-expose` command)_
|
||||
│ │ ├── themes/
|
||||
│ │ │ └── mytheme/
|
||||
│ │ │ └── css/ _(symlink)_
|
||||
│ │ │ └── theme.css
|
||||
│ │ └── vendor/
|
||||
│ │ └── silverstripe/
|
||||
│ │ └── blog/
|
||||
│ │ └── css/ _(symlink)_
|
||||
│ │ └── blog.css
|
||||
│ ├── .htaccess
|
||||
│ ├── favicon.ico
|
||||
│ ├── index.php
|
||||
│ └── install.php
|
||||
├── themes/
|
||||
│ └── mytheme/
|
||||
│ ├── css/ _(exposed in root composer.json)_
|
||||
│ │ └── theme.css
|
||||
│ └── templates/
|
||||
│ └── BlogPage.ss
|
||||
├── vendor/
|
||||
│ └── silverstripe/
|
||||
│ └── blog/
|
||||
│ ├── css/ _(exposed in blog composer.json)_
|
||||
│ │ └── blog.css
|
||||
│ └── composer.json
|
||||
└── composer.php
|
||||
```
|
||||
|
||||
### Use new `$public` theme set
|
||||
|
||||
In addition there is a new helper pseudo-theme that you can configure to expose files in the `public/`
|
||||
folder to the themed css / javascript file lookup. For instance, this is how you can prioritise those
|
||||
files:
|
||||
|
||||
```yaml
|
||||
---
|
||||
Name: mytheme
|
||||
---
|
||||
SilverStripe\View\SSViewer:
|
||||
themes:
|
||||
- '$public'
|
||||
- 'simple'
|
||||
- '$default'
|
||||
```
|
||||
|
||||
This would allow `<% require themedCSS('style.css') %>` to find a file comitted to `public/css/style.css`.
|
||||
|
||||
Note that `Requirements` calls will look in both the `public` folder (first) and then the base path when
|
||||
resolving css or javascript files. Any files that aren't in the public folder must be exposed using
|
||||
the composer.json "expose" mechanism described below.
|
||||
|
||||
### Expose root project files
|
||||
|
||||
If you have files comitted to source control outside of the `public` folder, but you need them to be available
|
||||
to the web server, you can also use the composer.json `expose` directive to symlink / copy these to `public/resources/`.
|
||||
|
||||
**composer.json** (in project root)
|
||||
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"expose": [
|
||||
"mysite/client"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then run the composer helper `composer vendor-expose` in your project root. This will symlink (or copy)
|
||||
the `mysite/client` directory to `public/resources/mysite/client`.
|
||||
|
||||
If you are using third party modules which may not have explicit `expose` directives,
|
||||
you can also expose those assets manually by declaring the full path to the directory to expose.
|
||||
This works the same for `silverstripe-module` and `silverstripe-vendormodule` types.
|
||||
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"expose": [
|
||||
"vendor/somevendor/somemodule/css",
|
||||
"anothermodule/css"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more information on how vendor modules work please see the documentation on the
|
||||
[vendor plugin page](https://github.com/silverstripe/vendor-plugin) or the
|
||||
[publishing a module](/developer_guides/extending/how_tos/publish_a_module) documentation.
|
||||
|
||||
### Path manipulation helpers
|
||||
|
||||
The following filesystem helpers have been added in order to better support working with cross-platform
|
||||
path manipulation:
|
||||
|
||||
* `SilverStripe\Core\Convert::slashes()` to convert directory separators to either `/` or `\`
|
||||
* `SilverStripe\Core\Path::join()` which will join one or more relative or absolute paths.
|
||||
* `SilverStripe\Core\Path::normalise()` which will normalise and trim directory separators in a relative or absolute path
|
||||
|
||||
For example: normalising `Convert::normalise('/some\\dir/')` will convert to `/some/dir`.
|
||||
Setting the second arg to true will also trim leading slashes.
|
||||
E.g. `Convert::normalise('/sub\\dir/', true)` will convert to `sub/dir`.
|
||||
|
||||
It is preferrable to use these helpers in your code instead of assuming `DIRECTORY_SEPARATOR` === `/`
|
@ -11,6 +11,7 @@ use SilverStripe\Core\Extensible;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\Core\Path;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\View\Requirements;
|
||||
@ -76,15 +77,13 @@ class Director implements TemplateGlobalProvider
|
||||
private static $alternate_base_folder;
|
||||
|
||||
/**
|
||||
* Force the base_url to a specific value.
|
||||
* If assigned, default_base_url and the value in the $_SERVER
|
||||
* global is ignored.
|
||||
* Supports back-ticked vars; E.g. '`SS_BASE_URL`'
|
||||
* Override PUBLIC_DIR. Set to a non-null value to override.
|
||||
* Setting to an empty string will disable public dir.
|
||||
*
|
||||
* @config
|
||||
* @var string
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $alternate_base_url;
|
||||
private static $alternate_public_dir = null;
|
||||
|
||||
/**
|
||||
* Base url to populate if cannot be determined otherwise.
|
||||
@ -589,7 +588,40 @@ class Director implements TemplateGlobalProvider
|
||||
public static function baseFolder()
|
||||
{
|
||||
$alternate = Director::config()->uninherited('alternate_base_folder');
|
||||
return ($alternate) ? $alternate : BASE_PATH;
|
||||
return $alternate ?: BASE_PATH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if using a seperate public dir, and if so return this directory
|
||||
* name.
|
||||
*
|
||||
* This will be removed in 5.0 and fixed to 'public'
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function publicDir()
|
||||
{
|
||||
$alternate = self::config()->uninherited('alternate_public_dir');
|
||||
if (isset($alternate)) {
|
||||
return $alternate;
|
||||
}
|
||||
return PUBLIC_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the webroot of the project, which may be a subfolder of {@see baseFolder()}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function publicFolder()
|
||||
{
|
||||
$folder = self::baseFolder();
|
||||
$publicDir = self::publicDir();
|
||||
if ($publicDir) {
|
||||
return Path::join($folder, $publicDir);
|
||||
}
|
||||
|
||||
return $folder;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -618,7 +650,7 @@ class Director implements TemplateGlobalProvider
|
||||
}
|
||||
|
||||
// Remove base folder or url
|
||||
foreach ([self::baseFolder(), self::baseURL()] as $base) {
|
||||
foreach ([self::publicFolder(), self::baseFolder(), self::baseURL()] as $base) {
|
||||
// Ensure single / doesn't break comparison (unless it would make base empty)
|
||||
$base = rtrim($base, '\\/') ?: $base;
|
||||
if (stripos($url, $base) === 0) {
|
||||
@ -746,7 +778,21 @@ class Director implements TemplateGlobalProvider
|
||||
*/
|
||||
public static function getAbsFile($file)
|
||||
{
|
||||
return self::is_absolute($file) ? $file : Director::baseFolder() . '/' . $file;
|
||||
// If already absolute
|
||||
if (self::is_absolute($file)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
// If path is relative to public folder search there first
|
||||
if (self::publicDir()) {
|
||||
$path = Path::join(self::publicFolder(), $file);
|
||||
if (file_exists($path)) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to base folder
|
||||
return Path::join(self::baseFolder(), $file);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,8 +4,11 @@ namespace SilverStripe\Control;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Manifest\ManifestFileFinder;
|
||||
use SilverStripe\Core\Manifest\ModuleResource;
|
||||
use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
||||
use SilverStripe\Core\Path;
|
||||
|
||||
/**
|
||||
* Generate URLs assuming that BASE_PATH is also the webroot
|
||||
@ -21,9 +24,7 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
|
||||
* @config
|
||||
* @var array
|
||||
*/
|
||||
private static $url_rewrites = [
|
||||
'#^vendor/#i' => 'resources/',
|
||||
];
|
||||
private static $url_rewrites = [];
|
||||
|
||||
/*
|
||||
* @var string
|
||||
@ -65,40 +66,163 @@ class SimpleResourceURLGenerator implements ResourceURLGenerator
|
||||
*/
|
||||
public function urlForResource($relativePath)
|
||||
{
|
||||
$query = '';
|
||||
if ($relativePath instanceof ModuleResource) {
|
||||
// Load from module resource
|
||||
$resource = $relativePath;
|
||||
$relativePath = $resource->getRelativePath();
|
||||
$exists = $resource->exists();
|
||||
$absolutePath = $resource->getPath();
|
||||
list($exists, $absolutePath, $relativePath) = $this->resolveModuleResource($relativePath);
|
||||
} else {
|
||||
// Use normal string
|
||||
$absolutePath = preg_replace('/\?.*/', '', Director::baseFolder() . '/' . $relativePath);
|
||||
$exists = file_exists($absolutePath);
|
||||
// Save querystring for later
|
||||
if (strpos($relativePath, '?') !== false) {
|
||||
list($relativePath, $query) = explode('?', $relativePath);
|
||||
}
|
||||
|
||||
// Determine lookup mechanism based on existence of public/ folder.
|
||||
// From 5.0 onwards only resolvePublicResource() will be used.
|
||||
if (!Director::publicDir()) {
|
||||
list($exists, $absolutePath, $relativePath) = $this->resolveUnsecuredResource($relativePath);
|
||||
} else {
|
||||
list($exists, $absolutePath, $relativePath) = $this->resolvePublicResource($relativePath);
|
||||
}
|
||||
}
|
||||
if (!$exists) {
|
||||
trigger_error("File {$relativePath} does not exist", E_USER_NOTICE);
|
||||
}
|
||||
|
||||
// Switch slashes for URL
|
||||
$relativeURL = Convert::slashes($relativePath, '/');
|
||||
|
||||
// Apply url rewrites
|
||||
$rules = Config::inst()->get(static::class, 'url_rewrites') ?: [];
|
||||
foreach ($rules as $from => $to) {
|
||||
$relativePath = preg_replace($from, $to, $relativePath);
|
||||
$relativeURL = preg_replace($from, $to, $relativeURL);
|
||||
}
|
||||
|
||||
// Apply nonce
|
||||
$nonce = '';
|
||||
// Don't add nonce to directories
|
||||
if ($this->nonceStyle && $exists && is_file($absolutePath)) {
|
||||
$nonce = (strpos($relativePath, '?') === false) ? '?' : '&';
|
||||
|
||||
switch ($this->nonceStyle) {
|
||||
case 'mtime':
|
||||
$nonce .= "m=" . filemtime($absolutePath);
|
||||
if ($query) {
|
||||
$query .= '&';
|
||||
}
|
||||
$query .= "m=" . filemtime($absolutePath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Director::baseURL() . $relativePath . $nonce;
|
||||
// Add back querystring
|
||||
if ($query) {
|
||||
$relativeURL .= '?' . $query;
|
||||
}
|
||||
|
||||
return Director::baseURL() . $relativeURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update relative path for a module resource
|
||||
*
|
||||
* @param ModuleResource $resource
|
||||
* @return array List of [$exists, $absolutePath, $relativePath]
|
||||
*/
|
||||
protected function resolveModuleResource(ModuleResource $resource)
|
||||
{
|
||||
// Load from module resource
|
||||
$relativePath = $resource->getRelativePath();
|
||||
$exists = $resource->exists();
|
||||
$absolutePath = $resource->getPath();
|
||||
|
||||
// Rewrite to resources with public directory
|
||||
if (Director::publicDir()) {
|
||||
// All resources mapped directly to resources/
|
||||
$relativePath = Path::join(ManifestFileFinder::RESOURCES_DIR, $relativePath);
|
||||
} elseif (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
|
||||
// @todo Non-public dir support will be removed in 5.0, so remove this block there
|
||||
// If there is no public folder, map to resources/ but trim leading vendor/ too (4.0 compat)
|
||||
$relativePath = Path::join(
|
||||
ManifestFileFinder::RESOURCES_DIR,
|
||||
substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
|
||||
);
|
||||
}
|
||||
return [$exists, $absolutePath, $relativePath];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve resource in the absence of a public/ folder
|
||||
*
|
||||
* @deprecated 4.1.0...5.0.0 Will be removed in 5.0 when public/ folder becomes mandatory
|
||||
* @param string $relativePath
|
||||
* @return array List of [$exists, $absolutePath, $relativePath]
|
||||
*/
|
||||
protected function resolveUnsecuredResource($relativePath)
|
||||
{
|
||||
// Check if the path requested is public-only, but we have no public folder
|
||||
$publicOnly = $this->inferPublicResourceRequired($relativePath);
|
||||
if ($publicOnly) {
|
||||
trigger_error('Requesting a public resource without a public folder has no effect', E_USER_WARNING);
|
||||
}
|
||||
|
||||
// Resolve path to base
|
||||
$absolutePath = Path::join(Director::baseFolder(), $relativePath);
|
||||
$exists = file_exists($absolutePath);
|
||||
|
||||
// Rewrite vendor/ to resources/ folder
|
||||
if (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
|
||||
$relativePath = Path::join(
|
||||
ManifestFileFinder::RESOURCES_DIR,
|
||||
substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
|
||||
);
|
||||
}
|
||||
return [$exists, $absolutePath, $relativePath];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the requested $relativePath requires a public-only resource.
|
||||
* An error will occur if this file isn't immediately available in the public/ assets folder.
|
||||
*
|
||||
* @param string $relativePath Requested relative path which may have a public/ prefix.
|
||||
* This prefix will be removed if exists. This path will also be normalised to match DIRECTORY_SEPARATOR
|
||||
* @return bool True if the resource must be a public resource
|
||||
*/
|
||||
protected function inferPublicResourceRequired(&$relativePath)
|
||||
{
|
||||
// Normalise path
|
||||
$relativePath = Path::normalise($relativePath, true);
|
||||
|
||||
// Detect public-only request
|
||||
$publicOnly = stripos($relativePath, 'public' . DIRECTORY_SEPARATOR) === 0;
|
||||
if ($publicOnly) {
|
||||
$relativePath = substr($relativePath, strlen(Director::publicDir() . DIRECTORY_SEPARATOR));
|
||||
}
|
||||
return $publicOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a resource that may either exist in a public/ folder, or be exposed from the base path to
|
||||
* public/resources/
|
||||
*
|
||||
* @param string $relativePath
|
||||
* @return array List of [$exists, $absolutePath, $relativePath]
|
||||
*/
|
||||
protected function resolvePublicResource($relativePath)
|
||||
{
|
||||
// Determine if we should search both public and base resources, or only public
|
||||
$publicOnly = $this->inferPublicResourceRequired($relativePath);
|
||||
|
||||
// Search public folder first, and unless `public/` is prefixed, also private base path
|
||||
$publicPath = Path::join(Director::publicFolder(), $relativePath);
|
||||
if (file_exists($publicPath)) {
|
||||
// String is a literal url comitted directly to public folder
|
||||
return [true, $publicPath, $relativePath];
|
||||
}
|
||||
|
||||
// Fall back to private path (and assume expose will make this available to resources/)
|
||||
$privatePath = Path::join(Director::baseFolder(), $relativePath);
|
||||
if (!$publicOnly && file_exists($privatePath)) {
|
||||
// String is private but exposed to resources/, so rewrite to the symlinked base
|
||||
$relativePath = Path::join(ManifestFileFinder::RESOURCES_DIR, $relativePath);
|
||||
return [true, $privatePath, $relativePath];
|
||||
}
|
||||
|
||||
// File doesn't exist, fail
|
||||
return [false, null, $relativePath];
|
||||
}
|
||||
}
|
||||
|
@ -591,4 +591,20 @@ class Convert
|
||||
$num = round($bytes / pow(1024, $scale), $decimal);
|
||||
return $num . $scales[$scale];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert slashes in relative or asolute filesystem path. Defaults to DIRECTORY_SEPARATOR
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $separator
|
||||
* @param bool $multiple Collapses multiple slashes or not
|
||||
* @return string
|
||||
*/
|
||||
public static function slashes($path, $separator = DIRECTORY_SEPARATOR, $multiple = true)
|
||||
{
|
||||
if ($multiple) {
|
||||
return preg_replace('#[/\\\\]+#', $separator, $path);
|
||||
}
|
||||
return str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ use SilverStripe\Dev\DebugView;
|
||||
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
|
||||
use SilverStripe\Logging\ErrorHandler;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\View\PublicThemes;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use SilverStripe\View\ThemeManifest;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
@ -115,7 +117,8 @@ class CoreKernel implements Kernel
|
||||
|
||||
// Load template manifest
|
||||
$themeResourceLoader = ThemeResourceLoader::inst();
|
||||
$themeResourceLoader->addSet('$default', new ThemeManifest(
|
||||
$themeResourceLoader->addSet(SSViewer::PUBLIC_THEME, new PublicThemes());
|
||||
$themeResourceLoader->addSet(SSViewer::DEFAULT_THEME, new ThemeManifest(
|
||||
$basePath,
|
||||
null, // project is defined in config, and this argument is deprecated
|
||||
$manifestCacheFactory
|
||||
@ -306,9 +309,9 @@ class CoreKernel implements Kernel
|
||||
protected function redirectToInstaller()
|
||||
{
|
||||
// Error if installer not available
|
||||
if (!file_exists($this->basePath . '/install.php')) {
|
||||
if (!file_exists(Director::publicFolder() . '/install.php')) {
|
||||
throw new HTTPResponse_Exception(
|
||||
'SilverStripe Framework requires a $databaseConfig defined.',
|
||||
'SilverStripe Framework requires database configuration defined via .env',
|
||||
500
|
||||
);
|
||||
}
|
||||
|
@ -3,11 +3,16 @@
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Serializable;
|
||||
use SilverStripe\Core\Path;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
class Module implements Serializable
|
||||
{
|
||||
/**
|
||||
* @deprecated 4.1..5.0 Use Path::normalise() instead
|
||||
*/
|
||||
const TRIM_CHARS = ' /\\';
|
||||
|
||||
/**
|
||||
@ -42,12 +47,12 @@ class Module implements Serializable
|
||||
* Construct a module
|
||||
*
|
||||
* @param string $path Absolute filesystem path to this module
|
||||
* @param string $base base url for the application this module is installed in
|
||||
* @param string $basePath base path for the application this module is installed in
|
||||
*/
|
||||
public function __construct($path, $base)
|
||||
public function __construct($path, $basePath)
|
||||
{
|
||||
$this->path = rtrim($path, self::TRIM_CHARS);
|
||||
$this->basePath = rtrim($base, self::TRIM_CHARS);
|
||||
$this->path = Path::normalise($path);
|
||||
$this->basePath = Path::normalise($basePath);
|
||||
$this->loadComposer();
|
||||
}
|
||||
|
||||
@ -137,7 +142,10 @@ class Module implements Serializable
|
||||
*/
|
||||
public function getRelativePath()
|
||||
{
|
||||
return trim(substr($this->path, strlen($this->basePath)), self::TRIM_CHARS);
|
||||
if ($this->path === $this->basePath) {
|
||||
return '';
|
||||
}
|
||||
return substr($this->path, strlen($this->basePath) + 1);
|
||||
}
|
||||
|
||||
public function serialize()
|
||||
@ -188,7 +196,10 @@ class Module implements Serializable
|
||||
*/
|
||||
public function getResource($path)
|
||||
{
|
||||
$path = trim($path, self::TRIM_CHARS);
|
||||
$path = Path::normalise($path, true);
|
||||
if (empty($path)) {
|
||||
throw new InvalidArgumentException('$path is required');
|
||||
}
|
||||
if (isset($this->resources[$path])) {
|
||||
return $this->resources[$path];
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Core\Manifest;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Path;
|
||||
|
||||
/**
|
||||
* This object represents a single resource file attached to a module, and can be used
|
||||
@ -39,7 +40,7 @@ class ModuleResource
|
||||
public function __construct(Module $module, $relativePath)
|
||||
{
|
||||
$this->module = $module;
|
||||
$this->relativePath = ltrim($relativePath, Module::TRIM_CHARS);
|
||||
$this->relativePath = Path::normalise($relativePath, true);
|
||||
if (empty($this->relativePath)) {
|
||||
throw new InvalidArgumentException("Resource cannot have empty path");
|
||||
}
|
||||
@ -55,7 +56,7 @@ class ModuleResource
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->module->getPath() . '/' . $this->relativePath;
|
||||
return Path::join($this->module->getPath(), $this->relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,8 +69,12 @@ class ModuleResource
|
||||
*/
|
||||
public function getRelativePath()
|
||||
{
|
||||
$path = $this->module->getRelativePath() . '/' . $this->relativePath;
|
||||
return ltrim($path, Module::TRIM_CHARS);
|
||||
// Root module
|
||||
$parent = $this->module->getRelativePath();
|
||||
if (!$parent) {
|
||||
return $this->relativePath;
|
||||
}
|
||||
return Path::join($parent, $this->relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,15 +140,8 @@ class ModuleResource
|
||||
*/
|
||||
public function getRelativeResource($path)
|
||||
{
|
||||
// Check cache
|
||||
$path = trim($path, Module::TRIM_CHARS);
|
||||
if (isset($this->resources[$path])) {
|
||||
return $this->resources[$path];
|
||||
}
|
||||
|
||||
// Build new relative path
|
||||
$relativeBase = rtrim($this->relativePath, Module::TRIM_CHARS);
|
||||
$relativePath = "{$relativeBase}/{$path}";
|
||||
return $this->resources[$path] = new ModuleResource($this->getModule(), $relativePath);
|
||||
// Defer to parent module
|
||||
$relativeToModule = Path::join($this->relativePath, $path);
|
||||
return $this->getModule()->getResource($relativeToModule);
|
||||
}
|
||||
}
|
||||
|
61
src/Core/Path.php
Normal file
61
src/Core/Path.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Path manipulation helpers
|
||||
*/
|
||||
class Path
|
||||
{
|
||||
const TRIM_CHARS = ' /\\';
|
||||
|
||||
/**
|
||||
* Joins one or more paths, normalising all separators to DIRECTORY_SEPARATOR
|
||||
*
|
||||
* Note: Errors on collapsed `/../` for security reasons. Use realpath() if you need to
|
||||
* join a trusted relative path.
|
||||
* @link https://www.owasp.org/index.php/Testing_Directory_traversal/file_include_(OTG-AUTHZ-001)
|
||||
* @see File::join_paths() for joining file identifiers
|
||||
*
|
||||
* @param array $parts
|
||||
* @return string Combined path, not including trailing slash (unless it's a single slash)
|
||||
*/
|
||||
public static function join(...$parts)
|
||||
{
|
||||
// In case $parts passed as an array in first parameter
|
||||
if (count($parts) === 1 && is_array($parts[0])) {
|
||||
$parts = $parts[0];
|
||||
}
|
||||
|
||||
// Cleanup and join all parts
|
||||
$parts = array_filter(array_map('trim', $parts));
|
||||
$fullPath = static::normalise(implode(DIRECTORY_SEPARATOR, $parts));
|
||||
|
||||
// Protect against directory traversal vulnerability (OTG-AUTHZ-001)
|
||||
if (strpos($fullPath, '..') !== false) {
|
||||
throw new InvalidArgumentException('Can not collapse relative folders');
|
||||
}
|
||||
|
||||
return $fullPath ?: DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise absolute or relative filesystem path.
|
||||
* Important: Single slashes are converted to empty strings (empty relative paths)
|
||||
*
|
||||
* @param string $path Input path
|
||||
* @param bool $relative
|
||||
* @return string Path with no trailing slash. If $relative is true, also trim leading slashes
|
||||
*/
|
||||
public static function normalise($path, $relative = false)
|
||||
{
|
||||
$path = trim(Convert::slashes($path));
|
||||
if ($relative) {
|
||||
return trim($path, self::TRIM_CHARS);
|
||||
} else {
|
||||
return rtrim($path, self::TRIM_CHARS);
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ class TempFolder
|
||||
$parent = static::getTempParentFolder($base);
|
||||
|
||||
// The actual temp folder is a subfolder of getTempParentFolder(), named by username
|
||||
$subfolder = $parent . DIRECTORY_SEPARATOR . static::getTempFolderUsername();
|
||||
$subfolder = Path::join($parent, static::getTempFolderUsername());
|
||||
|
||||
if (!@file_exists($subfolder)) {
|
||||
mkdir($subfolder);
|
||||
@ -65,20 +65,21 @@ class TempFolder
|
||||
*/
|
||||
protected static function getTempParentFolder($base)
|
||||
{
|
||||
$base = rtrim($base, '/\\');
|
||||
// first, try finding a silverstripe-cache dir built off the base path
|
||||
$tempPath = $base . DIRECTORY_SEPARATOR . 'silverstripe-cache';
|
||||
if (@file_exists($tempPath)) {
|
||||
if ((fileperms($tempPath) & 0777) != 0777) {
|
||||
@chmod($tempPath, 0777);
|
||||
$localPath = Path::join($base, 'silverstripe-cache');
|
||||
if (@file_exists($localPath)) {
|
||||
if ((fileperms($localPath) & 0777) != 0777) {
|
||||
@chmod($localPath, 0777);
|
||||
}
|
||||
return $tempPath;
|
||||
return $localPath;
|
||||
}
|
||||
|
||||
// failing the above, try finding a namespaced silverstripe-cache dir in the system temp
|
||||
$tempPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR .
|
||||
$tempPath = Path::join(
|
||||
sys_get_temp_dir(),
|
||||
'silverstripe-cache-php' . preg_replace('/[^\w-\.+]+/', '-', PHP_VERSION) .
|
||||
str_replace(array(' ', '/', ':', '\\'), '-', $base);
|
||||
str_replace(array(' ', '/', ':', '\\'), '-', $base)
|
||||
);
|
||||
if (!@file_exists($tempPath)) {
|
||||
$oldUMask = umask(0);
|
||||
@mkdir($tempPath, 0777);
|
||||
@ -95,7 +96,7 @@ class TempFolder
|
||||
|
||||
// failing to use the system path, attempt to create a local silverstripe-cache dir
|
||||
if (!$worked) {
|
||||
$tempPath = $base . DIRECTORY_SEPARATOR . 'silverstripe-cache';
|
||||
$tempPath = $localPath;
|
||||
if (!@file_exists($tempPath)) {
|
||||
$oldUMask = umask(0);
|
||||
@mkdir($tempPath, 0777);
|
||||
|
@ -7,6 +7,7 @@ use Exception;
|
||||
use InvalidArgumentException;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SilverStripe\Core\Path;
|
||||
use SilverStripe\Core\TempFolder;
|
||||
use SplFileInfo;
|
||||
|
||||
@ -64,9 +65,29 @@ class InstallRequirements
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base path for this installation
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseDir()
|
||||
{
|
||||
return rtrim($this->baseDir, '/\\') . '/';
|
||||
return Path::normalise($this->baseDir) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to public directory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPublicDir()
|
||||
{
|
||||
$base = $this->getBaseDir();
|
||||
$public = Path::join($base, 'public') . DIRECTORY_SEPARATOR;
|
||||
if (file_exists($public)) {
|
||||
return $public;
|
||||
}
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -297,16 +318,30 @@ class InstallRequirements
|
||||
));
|
||||
}
|
||||
|
||||
// Check public folder exists
|
||||
$this->requireFile(
|
||||
'public',
|
||||
[
|
||||
'File permissions',
|
||||
'Is there a public/ directory?',
|
||||
'It is recommended to have a separate public/ web directory',
|
||||
],
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
// Ensure root assets dir is writable
|
||||
$this->requireWriteable('assets', array("File permissions", "Is the assets/ directory writeable?", null));
|
||||
$this->requireWriteable(ASSETS_PATH, array("File permissions", "Is the assets/ directory writeable?", null), true);
|
||||
|
||||
// Ensure all assets files are writable
|
||||
$assetsDir = $this->getBaseDir() . 'assets';
|
||||
$innerIterator = new RecursiveDirectoryIterator($assetsDir, RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$innerIterator = new RecursiveDirectoryIterator(ASSETS_PATH, RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$iterator = new RecursiveIteratorIterator($innerIterator, RecursiveIteratorIterator::SELF_FIRST);
|
||||
/** @var SplFileInfo $file */
|
||||
foreach ($iterator as $file) {
|
||||
// Only report file as error if not writable
|
||||
if ($file->isWritable()) {
|
||||
continue;
|
||||
}
|
||||
$relativePath = substr($file->getPathname(), strlen($this->getBaseDir()));
|
||||
$message = $file->isDir()
|
||||
? "Is the {$relativePath} directory writeable?"
|
||||
@ -838,13 +873,23 @@ class InstallRequirements
|
||||
}
|
||||
}
|
||||
|
||||
public function requireFile($filename, $testDetails)
|
||||
public function requireFile($filename, $testDetails, $absolute = false, $error = true)
|
||||
{
|
||||
$this->testing($testDetails);
|
||||
$filename = $this->getBaseDir() . $filename;
|
||||
if (!file_exists($filename)) {
|
||||
|
||||
if ($absolute) {
|
||||
$filename = Path::normalise($filename);
|
||||
} else {
|
||||
$filename = Path::join($this->getBaseDir(), $filename);
|
||||
}
|
||||
if (file_exists($filename)) {
|
||||
return;
|
||||
}
|
||||
$testDetails[2] .= " (file '$filename' not found)";
|
||||
if ($error) {
|
||||
$this->error($testDetails);
|
||||
} else {
|
||||
$this->warning($testDetails);
|
||||
}
|
||||
}
|
||||
|
||||
@ -853,9 +898,9 @@ class InstallRequirements
|
||||
$this->testing($testDetails);
|
||||
|
||||
if ($absolute) {
|
||||
$filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
|
||||
$filename = Path::normalise($filename);
|
||||
} else {
|
||||
$filename = $this->getBaseDir() . str_replace('/', DIRECTORY_SEPARATOR, $filename);
|
||||
$filename = Path::join($this->getBaseDir(), $filename);
|
||||
}
|
||||
|
||||
if (file_exists($filename)) {
|
||||
@ -864,7 +909,10 @@ class InstallRequirements
|
||||
$isWriteable = is_writeable(dirname($filename));
|
||||
}
|
||||
|
||||
if (!$isWriteable) {
|
||||
if ($isWriteable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (function_exists('posix_getgroups')) {
|
||||
$userID = posix_geteuid();
|
||||
$user = posix_getpwuid($userID);
|
||||
@ -906,7 +954,6 @@ class InstallRequirements
|
||||
$this->warning($testDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function requireTempFolder($testDetails)
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ use SilverStripe\Control\Cookie;
|
||||
use SilverStripe\Control\HTTPApplication;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPRequestBuilder;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\CoreKernel;
|
||||
use SilverStripe\Core\EnvironmentLoader;
|
||||
use SilverStripe\Core\Kernel;
|
||||
@ -27,13 +28,15 @@ class Installer extends InstallRequirements
|
||||
|
||||
protected function installHeader()
|
||||
{
|
||||
$clientPath = PUBLIC_DIR
|
||||
? 'resources/vendor/silverstripe/framework/src/Dev/Install/client'
|
||||
: 'resources/silverstripe/framework/src/Dev/Install/client';
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Installing SilverStripe...</title>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="resources/silverstripe/framework/src/Dev/Install/client/styles/install.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="<?=$clientPath; ?>/styles/install.css"/>
|
||||
<script src="//code.jquery.com/jquery-1.7.2.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@ -209,7 +212,15 @@ use SilverStripe\Control\HTTPRequestBuilder;
|
||||
use SilverStripe\Core\CoreKernel;
|
||||
use SilverStripe\Core\Startup\ErrorControlChainMiddleware;
|
||||
|
||||
// Find autoload.php
|
||||
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
} elseif (file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
} else {
|
||||
echo "autoload.php not found";
|
||||
die;
|
||||
}
|
||||
|
||||
// Build request and detect flush
|
||||
$request = HTTPRequestBuilder::createFromEnvironment();
|
||||
@ -221,7 +232,8 @@ $app->addMiddleware(new ErrorControlChainMiddleware($app));
|
||||
$response = $app->handle($request);
|
||||
$response->output();
|
||||
PHP;
|
||||
$this->writeToFile('index.php', $content);
|
||||
$path = $this->getPublicDir() . 'index.php';
|
||||
$this->writeToFile($path, $content, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -340,11 +352,13 @@ PHP
|
||||
if ($config['theme'] && $config['theme'] !== 'tutorial') {
|
||||
$theme = $this->ymlString($config['theme']);
|
||||
$themeYML = <<<YML
|
||||
- '\$public'
|
||||
- '$theme'
|
||||
- '\$default'
|
||||
YML;
|
||||
} else {
|
||||
$themeYML = <<<YML
|
||||
- '\$public'
|
||||
- '\$default'
|
||||
YML;
|
||||
}
|
||||
@ -378,21 +392,24 @@ YML
|
||||
/**
|
||||
* Write file to given location
|
||||
*
|
||||
* @param $filename
|
||||
* @param $content
|
||||
* @param string $filename
|
||||
* @param string $content
|
||||
* @param bool $absolute If $filename is absolute path set to true
|
||||
* @return bool
|
||||
*/
|
||||
public function writeToFile($filename, $content)
|
||||
public function writeToFile($filename, $content, $absolute = false)
|
||||
{
|
||||
$base = $this->getBaseDir();
|
||||
$this->statusMessage("Setting up $base$filename");
|
||||
$path = $absolute
|
||||
? $filename
|
||||
: $this->getBaseDir() . $filename;
|
||||
$this->statusMessage("Setting up $path");
|
||||
|
||||
if ((@$fh = fopen($base . $filename, 'wb')) && fwrite($fh, $content) && fclose($fh)) {
|
||||
if ((@$fh = fopen($path, 'wb')) && fwrite($fh, $content) && fclose($fh)) {
|
||||
// Set permissions to writable
|
||||
@chmod($base . $filename, 0775);
|
||||
@chmod($path, 0775);
|
||||
return true;
|
||||
}
|
||||
$this->error("Couldn't write to file $base$filename");
|
||||
$this->error("Couldn't write to file $path");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -405,11 +422,7 @@ YML
|
||||
$end = "\n### SILVERSTRIPE END ###";
|
||||
|
||||
$base = dirname($_SERVER['SCRIPT_NAME']);
|
||||
if (defined('DIRECTORY_SEPARATOR')) {
|
||||
$base = str_replace(DIRECTORY_SEPARATOR, '/', $base);
|
||||
} else {
|
||||
$base = str_replace("\\", '/', $base);
|
||||
}
|
||||
$base = Convert::slashes($base, '/');
|
||||
|
||||
if ($base != '.') {
|
||||
$baseClause = "RewriteBase '$base'\n";
|
||||
@ -474,8 +487,9 @@ ErrorDocument 500 /assets/error-500.html
|
||||
</IfModule>
|
||||
TEXT;
|
||||
|
||||
if (file_exists('.htaccess')) {
|
||||
$htaccess = file_get_contents('.htaccess');
|
||||
$htaccessPath = $this->getPublicDir() . '.htaccess';
|
||||
if (file_exists($htaccessPath)) {
|
||||
$htaccess = file_get_contents($htaccessPath);
|
||||
|
||||
if (strpos($htaccess, '### SILVERSTRIPE START ###') === false
|
||||
&& strpos($htaccess, '### SILVERSTRIPE END ###') === false
|
||||
@ -492,7 +506,7 @@ TEXT;
|
||||
}
|
||||
}
|
||||
|
||||
$this->writeToFile('.htaccess', $start . $rewrite . $end);
|
||||
$this->writeToFile($htaccessPath, $start . $rewrite . $end, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -509,7 +523,6 @@ TEXT;
|
||||
<requestFiltering>
|
||||
<hiddenSegments applyToWebDAV="false">
|
||||
<add segment="silverstripe-cache" />
|
||||
<add segment="vendor" />
|
||||
<add segment="composer.json" />
|
||||
<add segment="composer.lock" />
|
||||
</hiddenSegments>
|
||||
@ -534,7 +547,8 @@ TEXT;
|
||||
</configuration>
|
||||
TEXT;
|
||||
|
||||
$this->writeToFile('web.config', $content);
|
||||
$path = $this->getPublicDir() . 'web.config';
|
||||
$this->writeToFile($path, $content, true);
|
||||
}
|
||||
|
||||
public function checkRewrite()
|
||||
@ -542,8 +556,11 @@ TEXT;
|
||||
$token = new ParameterConfirmationToken('flush', new HTTPRequest('GET', '/'));
|
||||
$params = http_build_query($token->params());
|
||||
|
||||
$destinationURL = str_replace('install.php', '', $_SERVER['SCRIPT_NAME']) .
|
||||
($this->checkModuleExists('cms') ? "home/successfullyinstalled?$params" : "?$params");
|
||||
$destinationURL = BASE_URL . '/' . (
|
||||
$this->checkModuleExists('cms')
|
||||
? "home/successfullyinstalled?$params"
|
||||
: "?$params"
|
||||
);
|
||||
|
||||
echo <<<HTML
|
||||
<li id="ModRewriteResult">Testing...</li>
|
||||
|
@ -5,6 +5,7 @@
|
||||
<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
|
||||
<head>
|
||||
<title>SilverStripe CMS / Framework Installation</title>
|
||||
<base href="<?php echo htmlentities($base); ?>/" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<script type="application/javascript" src="//code.jquery.com/jquery-1.7.2.min.js"></script>
|
||||
<script type="application/javascript" src="<?=$clientPath; ?>/js/install.js"></script>
|
||||
|
@ -105,7 +105,10 @@ if ($installFromCli && ($req->hasErrors() || $dbReq->hasErrors())) {
|
||||
}
|
||||
|
||||
// Path to client resources (copied through silverstripe/vendor-plugin)
|
||||
$clientPath = 'resources/silverstripe/framework/src/Dev/Install/client';
|
||||
$base = BASE_URL;
|
||||
$clientPath = PUBLIC_DIR
|
||||
? 'resources/vendor/silverstripe/framework/src/Dev/Install/client'
|
||||
: 'resources/silverstripe/framework/src/Dev/Install/client';
|
||||
|
||||
// If already installed, ensure the user clicked "reinstall"
|
||||
$expectedArg = $alreadyInstalled ? 'reinstall' : 'go';
|
||||
@ -135,6 +138,7 @@ $adminConfig = $config->getAdminConfig($_REQUEST, false);
|
||||
|
||||
// config-form.html vars (placeholder to prevent deletion)
|
||||
[
|
||||
$base,
|
||||
$theme,
|
||||
$clientPath,
|
||||
$adminConfig,
|
||||
|
11
src/View/PublicThemes.php
Normal file
11
src/View/PublicThemes.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\View;
|
||||
|
||||
class PublicThemes implements ThemeList
|
||||
{
|
||||
public function getThemes()
|
||||
{
|
||||
return PUBLIC_DIR ? ['/' . PUBLIC_DIR] : [];
|
||||
}
|
||||
}
|
@ -331,13 +331,13 @@ class Requirements implements Flushable
|
||||
* 'framework/javascript/lang'
|
||||
* @param bool $return Return all relative file paths rather than including them in
|
||||
* requirements
|
||||
* @param bool $langOnly Only include language files, not the base libraries
|
||||
* @param bool $langOnly @deprecated 4.1...5.0 as i18n.js should be included manually in your project
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function add_i18n_javascript($langDir, $return = false, $langOnly = false)
|
||||
{
|
||||
return self::backend()->add_i18n_javascript($langDir, $return, $langOnly);
|
||||
return self::backend()->add_i18n_javascript($langDir, $return);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,6 +14,7 @@ use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Manifest\ModuleResourceLoader;
|
||||
use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
||||
use SilverStripe\Core\Path;
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\i18n\i18n;
|
||||
@ -603,7 +604,12 @@ class Requirements_Backend
|
||||
public function javascriptTemplate($file, $vars, $uniquenessID = null)
|
||||
{
|
||||
$file = ModuleResourceLoader::singleton()->resolvePath($file);
|
||||
$script = file_get_contents(Director::getAbsFile($file));
|
||||
$absolutePath = Director::getAbsFile($file);
|
||||
if (!file_exists($absolutePath)) {
|
||||
throw new InvalidArgumentException("Javascript template file {$file} does not exist");
|
||||
}
|
||||
|
||||
$script = file_get_contents($absolutePath);
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
@ -629,9 +635,9 @@ class Requirements_Backend
|
||||
{
|
||||
$file = ModuleResourceLoader::singleton()->resolvePath($file);
|
||||
|
||||
$this->css[$file] = array(
|
||||
$this->css[$file] = [
|
||||
"media" => $media
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -997,12 +1003,6 @@ class Requirements_Backend
|
||||
$langDir = ModuleResourceLoader::singleton()->resolvePath($langDir);
|
||||
|
||||
$files = array();
|
||||
$base = Director::baseFolder() . '/';
|
||||
|
||||
if (substr($langDir, -1) != '/') {
|
||||
$langDir .= '/';
|
||||
}
|
||||
|
||||
$candidates = array(
|
||||
'en.js',
|
||||
'en_US.js',
|
||||
@ -1012,20 +1012,22 @@ class Requirements_Backend
|
||||
i18n::get_locale() . '.js',
|
||||
);
|
||||
foreach ($candidates as $candidate) {
|
||||
if (file_exists($base . DIRECTORY_SEPARATOR . $langDir . $candidate)) {
|
||||
$files[] = $langDir . $candidate;
|
||||
$relativePath = Path::join($langDir, $candidate);
|
||||
$absolutePath = Director::getAbsFile($relativePath);
|
||||
if (file_exists($absolutePath)) {
|
||||
$files[] = $relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
if ($return) {
|
||||
return $files;
|
||||
} else {
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
$this->javascript($file);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path for specified file
|
||||
@ -1345,9 +1347,12 @@ MESSAGE
|
||||
function () use ($fileList, $minify, $type) {
|
||||
// Physically combine all file content
|
||||
$combinedData = '';
|
||||
$base = Director::baseFolder() . '/';
|
||||
foreach ($fileList as $file) {
|
||||
$fileContent = file_get_contents($base . $file);
|
||||
$filePath = Director::getAbsFile($file);
|
||||
if (!file_exists($filePath)) {
|
||||
throw new InvalidArgumentException("Combined file {$file} does not exist");
|
||||
}
|
||||
$fileContent = file_get_contents($filePath);
|
||||
// Use configured minifier
|
||||
if ($minify) {
|
||||
$fileContent = $this->minifier->minify($fileContent, $type, $file);
|
||||
@ -1419,11 +1424,11 @@ MESSAGE
|
||||
protected function hashOfFiles($fileList)
|
||||
{
|
||||
// Get hash based on hash of each file
|
||||
$base = Director::baseFolder() . '/';
|
||||
$hash = '';
|
||||
foreach ($fileList as $file) {
|
||||
if (file_exists($base . $file)) {
|
||||
$hash .= sha1_file($base . $file);
|
||||
$absolutePath = Director::getAbsFile($file);
|
||||
if (file_exists($absolutePath)) {
|
||||
$hash .= sha1_file($absolutePath);
|
||||
} else {
|
||||
throw new InvalidArgumentException("Combined file {$file} does not exist");
|
||||
}
|
||||
|
@ -49,6 +49,11 @@ class SSViewer implements Flushable
|
||||
*/
|
||||
const DEFAULT_THEME = '$default';
|
||||
|
||||
/**
|
||||
* Identifier for the public theme
|
||||
*/
|
||||
const PUBLIC_THEME = '$public';
|
||||
|
||||
/**
|
||||
* A list (highest priority first) of themes to use
|
||||
* Only used when {@link $theme_enabled} is set to TRUE.
|
||||
@ -267,7 +272,7 @@ class SSViewer implements Flushable
|
||||
*/
|
||||
public static function get_themes()
|
||||
{
|
||||
$default = [self::DEFAULT_THEME];
|
||||
$default = [self::PUBLIC_THEME, self::DEFAULT_THEME];
|
||||
|
||||
if (!SSViewer::config()->uninherited('theme_enabled')) {
|
||||
return $default;
|
||||
@ -284,7 +289,7 @@ class SSViewer implements Flushable
|
||||
|
||||
// Support legacy behaviour
|
||||
if ($theme = SSViewer::config()->uninherited('theme')) {
|
||||
return [$theme, self::DEFAULT_THEME];
|
||||
return [self::PUBLIC_THEME, $theme, self::DEFAULT_THEME];
|
||||
}
|
||||
|
||||
return $default;
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\View;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Path;
|
||||
|
||||
/**
|
||||
* Handles finding templates from a stack of template manifest objects.
|
||||
@ -103,12 +104,12 @@ class ThemeResourceLoader
|
||||
if (count($parts) > 1) {
|
||||
throw new InvalidArgumentException("Invalid theme identifier {$identifier}");
|
||||
}
|
||||
return ltrim($identifier, '/');
|
||||
return Path::normalise($identifier, true);
|
||||
}
|
||||
|
||||
// If there is no slash / colon it's a legacy theme
|
||||
if ($slashPos === false && count($parts) === 1) {
|
||||
return THEMES_DIR.'/'.$identifier;
|
||||
return Path::join(THEMES_DIR, $identifier);
|
||||
}
|
||||
|
||||
// Extract from <vendor>/<module>:<theme> format.
|
||||
@ -148,7 +149,7 @@ class ThemeResourceLoader
|
||||
}
|
||||
|
||||
// Join module with subpath
|
||||
return ltrim($modulePath . $subpath, '/');
|
||||
return Path::normalise($modulePath . $subpath, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,7 +215,7 @@ class ThemeResourceLoader
|
||||
foreach ($themePaths as $themePath) {
|
||||
// Join path
|
||||
$pathParts = [ $this->base, $themePath, 'templates', $head, $type, $tail ];
|
||||
$path = implode('/', array_filter($pathParts)) . '.ss';
|
||||
$path = Path::join($pathParts) . '.ss';
|
||||
if (file_exists($path)) {
|
||||
return $path;
|
||||
}
|
||||
@ -282,16 +283,13 @@ class ThemeResourceLoader
|
||||
*/
|
||||
public function findThemedResource($resource, $themes)
|
||||
{
|
||||
if ($resource[0] !== '/') {
|
||||
$resource = '/' . $resource;
|
||||
}
|
||||
|
||||
$paths = $this->getThemePaths($themes);
|
||||
|
||||
foreach ($paths as $themePath) {
|
||||
$abspath = $this->base . '/' . $themePath;
|
||||
if (file_exists($abspath . $resource)) {
|
||||
return $themePath . $resource;
|
||||
$relativePath = Path::join($themePath, $resource);
|
||||
$absolutePath = Path::join($this->base, $relativePath);
|
||||
if (file_exists($absolutePath)) {
|
||||
return $relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Environment;
|
||||
use SilverStripe\Core\EnvironmentLoader;
|
||||
use SilverStripe\Core\TempFolder;
|
||||
@ -57,6 +58,10 @@ if (!defined('BASE_PATH')) {
|
||||
}));
|
||||
}
|
||||
|
||||
// Set public webroot dir / path
|
||||
define('PUBLIC_DIR', is_dir(BASE_PATH . DIRECTORY_SEPARATOR . 'public') ? 'public' : '');
|
||||
define('PUBLIC_PATH', PUBLIC_DIR ? BASE_PATH . DIRECTORY_SEPARATOR . PUBLIC_DIR : BASE_PATH);
|
||||
|
||||
// Allow a first class env var to be set that disables .env file loading
|
||||
if (!Environment::getEnv('SS_IGNORE_DOT_ENV')) {
|
||||
call_user_func(function () {
|
||||
@ -102,13 +107,17 @@ if (!defined('BASE_URL')) {
|
||||
|
||||
// Determine the base URL by comparing SCRIPT_NAME to SCRIPT_FILENAME and getting common elements
|
||||
// This tends not to work on CLI
|
||||
$path = realpath($_SERVER['SCRIPT_FILENAME']);
|
||||
if (substr($path, 0, strlen(BASE_PATH)) == BASE_PATH) {
|
||||
$urlSegmentToRemove = str_replace('\\', '/', substr($path, strlen(BASE_PATH)));
|
||||
if (substr($_SERVER['SCRIPT_NAME'], -strlen($urlSegmentToRemove)) == $urlSegmentToRemove) {
|
||||
$baseURL = substr($_SERVER['SCRIPT_NAME'], 0, -strlen($urlSegmentToRemove));
|
||||
// ltrim('.'), normalise slashes to '/', and rtrim('/')
|
||||
return rtrim(str_replace('\\', '/', ltrim($baseURL, '.')), '/');
|
||||
$path = Convert::slashes($_SERVER['SCRIPT_FILENAME']);
|
||||
$scriptName = Convert::slashes($_SERVER['SCRIPT_NAME'], '/');
|
||||
|
||||
// Ensure script is served from public folder (otherwise error)
|
||||
if (stripos($path, PUBLIC_PATH) === 0) {
|
||||
// Get entire url following PUBLIC_PATH
|
||||
$urlSegmentToRemove = Convert::slashes(substr($path, strlen(PUBLIC_PATH)), '/');
|
||||
if (substr($scriptName, -strlen($urlSegmentToRemove)) === $urlSegmentToRemove) {
|
||||
// Remove this from end of SCRIPT_NAME to get url to base
|
||||
$baseURL = substr($scriptName, 0, -strlen($urlSegmentToRemove));
|
||||
return rtrim(ltrim($baseURL, '.'), '/');
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,7 +144,14 @@ if (!defined('ASSETS_DIR')) {
|
||||
define('ASSETS_DIR', 'assets');
|
||||
}
|
||||
if (!defined('ASSETS_PATH')) {
|
||||
define('ASSETS_PATH', BASE_PATH . DIRECTORY_SEPARATOR . ASSETS_DIR);
|
||||
call_user_func(function () {
|
||||
$paths = [
|
||||
BASE_PATH,
|
||||
(PUBLIC_DIR ? PUBLIC_DIR : null),
|
||||
ASSETS_DIR
|
||||
];
|
||||
define('ASSETS_PATH', implode(DIRECTORY_SEPARATOR, array_filter($paths)));
|
||||
});
|
||||
}
|
||||
|
||||
// Custom include path - deprecated
|
||||
|
@ -338,6 +338,12 @@ class DirectorTest extends SapphireTest
|
||||
BASE_PATH . '/some/file.txt',
|
||||
'some/file.txt',
|
||||
],
|
||||
// public folder is found
|
||||
[
|
||||
'http://www.mysite.com/base/folder',
|
||||
PUBLIC_PATH . '/some/file.txt',
|
||||
'some/file.txt',
|
||||
],
|
||||
// querystring is protected
|
||||
[
|
||||
'http://www.mysite.com/base/folder',
|
||||
|
@ -22,6 +22,10 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
|
||||
'alternate_base_url',
|
||||
'http://www.mysite.com/'
|
||||
);
|
||||
Director::config()->set(
|
||||
'alternate_public_dir',
|
||||
'public'
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddMTime()
|
||||
@ -30,7 +34,7 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
|
||||
$generator = Injector::inst()->get(ResourceURLGenerator::class);
|
||||
$mtime = filemtime(__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot/basemodule/client/file.js');
|
||||
$this->assertEquals(
|
||||
'/basemodule/client/file.js?m='.$mtime,
|
||||
'/resources/basemodule/client/file.js?m='.$mtime,
|
||||
$generator->urlForResource('basemodule/client/file.js')
|
||||
);
|
||||
}
|
||||
@ -43,11 +47,34 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
|
||||
__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot/vendor/silverstripe/mymodule/client/style.css'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'/resources/silverstripe/mymodule/client/style.css?m='.$mtime,
|
||||
'/resources/vendor/silverstripe/mymodule/client/style.css?m='.$mtime,
|
||||
$generator->urlForResource('vendor/silverstripe/mymodule/client/style.css')
|
||||
);
|
||||
}
|
||||
|
||||
public function testPublicDirResource()
|
||||
{
|
||||
/** @var SimpleResourceURLGenerator $generator */
|
||||
$generator = Injector::inst()->get(ResourceURLGenerator::class);
|
||||
$mtime = filemtime(
|
||||
__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot/public/basemodule/css/style.css'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'/basemodule/css/style.css?m='.$mtime,
|
||||
$generator->urlForResource('public/basemodule/css/style.css')
|
||||
);
|
||||
|
||||
$mtime = filemtime(
|
||||
__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot/basemodule/client/file.js'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'/resources/basemodule/client/file.js?m='.$mtime,
|
||||
$generator->urlForResource('basemodule/client/file.js')
|
||||
);
|
||||
}
|
||||
|
||||
public function testModuleResource()
|
||||
{
|
||||
/** @var SimpleResourceURLGenerator $generator */
|
||||
@ -60,7 +87,7 @@ class SimpleResourceURLGeneratorTest extends SapphireTest
|
||||
__DIR__ .'/SimpleResourceURLGeneratorTest/_fakewebroot/vendor/silverstripe/mymodule/client/style.css'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'/resources/silverstripe/mymodule/client/style.css?m='.$mtime,
|
||||
'/resources/vendor/silverstripe/mymodule/client/style.css?m='.$mtime,
|
||||
$generator->urlForResource($module->getResource('client/style.css'))
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,2 @@
|
||||
/* mymodule/style.css */
|
||||
body {}
|
@ -3,7 +3,6 @@
|
||||
namespace SilverStripe\Core\Tests\Manifest;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
@ -27,6 +26,7 @@ class ModuleResourceTest extends SapphireTest
|
||||
$this->manifest = new ModuleManifest($this->base);
|
||||
$this->manifest->init();
|
||||
Director::config()->set('alternate_base_url', 'http://www.mysite.com/basefolder/');
|
||||
Director::config()->set('alternate_public_dir', 'public');
|
||||
}
|
||||
|
||||
public function testBaseModuleResource()
|
||||
@ -42,7 +42,7 @@ class ModuleResourceTest extends SapphireTest
|
||||
$resource->getPath()
|
||||
);
|
||||
$this->assertStringStartsWith(
|
||||
'/basefolder/module/client/script.js?m=',
|
||||
'/basefolder/resources/module/client/script.js?m=',
|
||||
$resource->getURL()
|
||||
);
|
||||
}
|
||||
@ -60,7 +60,7 @@ class ModuleResourceTest extends SapphireTest
|
||||
$resource->getPath()
|
||||
);
|
||||
$this->assertStringStartsWith(
|
||||
'/basefolder/resources/silverstripe/modulec/client/script.js?m=',
|
||||
'/basefolder/resources/vendor/silverstripe/modulec/client/script.js?m=',
|
||||
$resource->getURL()
|
||||
);
|
||||
}
|
||||
@ -80,7 +80,7 @@ class ModuleResourceTest extends SapphireTest
|
||||
$resource->getPath()
|
||||
);
|
||||
$this->assertStringStartsWith(
|
||||
'/basefolder/resources/silverstripe/modulec/client/script.js?m=',
|
||||
'/basefolder/resources/vendor/silverstripe/modulec/client/script.js?m=',
|
||||
$resource->getURL()
|
||||
);
|
||||
}
|
||||
|
118
tests/php/Core/PathTest.php
Normal file
118
tests/php/Core/PathTest.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Tests;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Core\Path;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class PathTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* Test paths are joined
|
||||
*
|
||||
* @dataProvider providerTestJoinPaths
|
||||
* @param array $args Arguments to pass to Path::join()
|
||||
* @param string $expected Expected path
|
||||
*/
|
||||
public function testJoinPaths($args, $expected)
|
||||
{
|
||||
$joined = Path::join($args);
|
||||
$this->assertEquals($expected, $joined);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of tests for testJoinPaths
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestJoinPaths()
|
||||
{
|
||||
$tests = [
|
||||
// Single arg
|
||||
[['/'], '/'],
|
||||
[['\\'], '/'],
|
||||
[['base'], 'base'],
|
||||
[['c:/base\\'], 'c:/base'],
|
||||
// Windows paths
|
||||
[['c:/', 'bob'], 'c:/bob'],
|
||||
[['c:/', '\\bob/'], 'c:/bob'],
|
||||
[['c:\\basedir', '/bob\\'], 'c:/basedir/bob'],
|
||||
// Empty-ish paths to clear out
|
||||
[['/root/dir', '/', ' ', 'next/', '\\'], '/root/dir/next'],
|
||||
[['/', '/', ' ', '/', '\\'], '/'],
|
||||
[['/', '', '',], '/'],
|
||||
[['/root', '/', ' ', '/', '\\'], '/root'],
|
||||
[['', '/root', '/', ' ', '/', '\\'], '/root'],
|
||||
[['', 'root', '/', ' ', '/', '\\'], 'root'],
|
||||
[['\\', '', '/root', '/', ' ', '/', '\\'], '/root'],
|
||||
// join blocks of paths
|
||||
[['/root/dir', 'another/path\\to/join'], '/root/dir/another/path/to/join'],
|
||||
];
|
||||
|
||||
// Rewrite tests for other filesystems (output arg only)
|
||||
if (DIRECTORY_SEPARATOR !== '/') {
|
||||
foreach ($tests as $index => $test) {
|
||||
$tests[$index][1] = str_replace('/', DIRECTORY_SEPARATOR, $tests[$index][1]);
|
||||
}
|
||||
}
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that joinPaths give the appropriate error
|
||||
*
|
||||
* @dataProvider providerTestJoinPathsErrors
|
||||
* @param array $args Arguments to pass to Filesystem::joinPath()
|
||||
* @param string $error Expected path
|
||||
*/
|
||||
public function testJoinPathsErrors($args, $error)
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage($error);
|
||||
Path::join($args);
|
||||
}
|
||||
|
||||
public function providerTestJoinPathsErrors()
|
||||
{
|
||||
return [
|
||||
[['/base', '../passwd'], 'Can not collapse relative folders'],
|
||||
[['/base/../', 'passwd/path'], 'Can not collapse relative folders'],
|
||||
[['../', 'passwd/path'], 'Can not collapse relative folders'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerTestNormalise
|
||||
* @param string $input
|
||||
* @param string $expected
|
||||
*/
|
||||
public function testNormalise($input, $expected)
|
||||
{
|
||||
$output = Path::normalise($input);
|
||||
$this->assertEquals($expected, $output, "Expected $input to be normalised to $expected");
|
||||
}
|
||||
|
||||
public function providerTestNormalise()
|
||||
{
|
||||
$tests = [
|
||||
// Windows paths
|
||||
["c:/bob", "c:/bob"],
|
||||
["c://bob", "c:/bob"],
|
||||
["/root/dir/", "/root/dir"],
|
||||
["/root\\dir\\\\sub/", "/root/dir/sub"],
|
||||
[" /some/dir/ ", "/some/dir"],
|
||||
["", ""],
|
||||
["/", ""],
|
||||
["\\", ""],
|
||||
];
|
||||
|
||||
// Rewrite tests for other filesystems (output arg only)
|
||||
if (DIRECTORY_SEPARATOR !== '/') {
|
||||
foreach ($tests as $index => $test) {
|
||||
$tests[$index][1] = str_replace('/', DIRECTORY_SEPARATOR, $tests[$index][1]);
|
||||
}
|
||||
}
|
||||
return $tests;
|
||||
}
|
||||
}
|
@ -140,7 +140,7 @@ class HTMLEditorFieldTest extends FunctionalTest
|
||||
= '/assets/HTMLEditorFieldTest/f5c7c2f814/example__ResizedImageWzEwLDIwXQ.jpg';
|
||||
|
||||
$this->assertEquals($neededFilename, (string)$xml[0]['src'], 'Correct URL of resized image is set.');
|
||||
$this->assertTrue(file_exists(BASE_PATH.DIRECTORY_SEPARATOR.$neededFilename), 'File for resized image exists');
|
||||
$this->assertTrue(file_exists(PUBLIC_PATH.DIRECTORY_SEPARATOR.$neededFilename), 'File for resized image exists');
|
||||
$this->assertEquals(false, $obj->HasBrokenFile, 'Referenced image file exists.');
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ class TinyMCECombinedGeneratorTest extends SapphireTest
|
||||
// Set custom base_path for tinymce
|
||||
Director::config()->set('alternate_base_folder', __DIR__ . '/TinyMCECombinedGeneratorTest');
|
||||
Director::config()->set('alternate_base_url', 'http://www.mysite.com/basedir/');
|
||||
Director::config()->set('alternate_public_dir', ''); // Disable public dir
|
||||
SSViewer::config()->set('themes', [SSViewer::DEFAULT_THEME]);
|
||||
TinyMCEConfig::config()
|
||||
->set('base_dir', 'tinymce')
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace SilverStripe\View\Tests;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\View\Requirements;
|
||||
@ -11,6 +12,8 @@ use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore;
|
||||
use SilverStripe\View\Requirements_Backend;
|
||||
use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
||||
use SilverStripe\Control\SimpleResourceURLGenerator;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
|
||||
/**
|
||||
* @todo Test that order of combine_files() is correct
|
||||
@ -20,16 +23,28 @@ use SilverStripe\Control\SimpleResourceURLGenerator;
|
||||
class RequirementsTest extends SapphireTest
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ThemeResourceLoader
|
||||
*/
|
||||
protected $oldThemeResourceLoader = null;
|
||||
|
||||
static $html_template = '<html><head></head><body></body></html>';
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
Director::config()->set('alternate_base_folder', __DIR__ . '/SSViewerTest');
|
||||
Director::config()->set('alternate_base_url', 'http://www.mysite.com/basedir/');
|
||||
Director::config()->set('alternate_public_dir', 'public'); // Enforce public dir
|
||||
// Add public as a theme in itself
|
||||
SSViewer::set_themes([SSViewer::PUBLIC_THEME, SSViewer::DEFAULT_THEME]);
|
||||
TestAssetStore::activate('RequirementsTest'); // Set backend root to /RequirementsTest
|
||||
$this->oldThemeResourceLoader = ThemeResourceLoader::inst();
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
ThemeResourceLoader::set_instance($this->oldThemeResourceLoader);
|
||||
TestAssetStore::reset();
|
||||
parent::tearDown();
|
||||
}
|
||||
@ -84,20 +99,23 @@ class RequirementsTest extends SapphireTest
|
||||
*/
|
||||
protected function setupCombinedRequirements($backend)
|
||||
{
|
||||
$basePath = $this->getThemeRoot();
|
||||
$this->setupRequirements($backend);
|
||||
|
||||
// require files normally (e.g. called from a FormField instance)
|
||||
$backend->javascript($basePath . '/javascript/RequirementsTest_a.js');
|
||||
$backend->javascript($basePath . '/javascript/RequirementsTest_b.js');
|
||||
$backend->javascript($basePath . '/javascript/RequirementsTest_c.js');
|
||||
$backend->javascript('javascript/RequirementsTest_a.js');
|
||||
$backend->javascript('javascript/RequirementsTest_b.js');
|
||||
$backend->javascript('javascript/RequirementsTest_c.js');
|
||||
|
||||
// Public resources may or may not be specified with `public/` prefix
|
||||
$backend->javascript('javascript/RequirementsTest_d.js');
|
||||
$backend->javascript('public/javascript/RequirementsTest_e.js');
|
||||
|
||||
// require two of those files as combined includes
|
||||
$backend->combineFiles(
|
||||
'RequirementsTest_bc.js',
|
||||
array(
|
||||
$basePath . '/javascript/RequirementsTest_b.js',
|
||||
$basePath . '/javascript/RequirementsTest_c.js'
|
||||
'javascript/RequirementsTest_b.js',
|
||||
'javascript/RequirementsTest_c.js'
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -109,15 +127,14 @@ class RequirementsTest extends SapphireTest
|
||||
*/
|
||||
protected function setupCombinedNonrequiredRequirements($backend)
|
||||
{
|
||||
$basePath = $this->getThemeRoot();
|
||||
$this->setupRequirements($backend);
|
||||
|
||||
// require files as combined includes
|
||||
$backend->combineFiles(
|
||||
'RequirementsTest_bc.js',
|
||||
array(
|
||||
$basePath . '/javascript/RequirementsTest_b.js',
|
||||
$basePath . '/javascript/RequirementsTest_c.js'
|
||||
'javascript/RequirementsTest_b.js',
|
||||
'javascript/RequirementsTest_c.js'
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -129,25 +146,24 @@ class RequirementsTest extends SapphireTest
|
||||
*/
|
||||
protected function setupCombinedRequirementsJavascriptAsyncDefer($backend, $async, $defer)
|
||||
{
|
||||
$basePath = $this->getThemeRoot();
|
||||
$this->setupRequirements($backend);
|
||||
|
||||
// require files normally (e.g. called from a FormField instance)
|
||||
$backend->javascript($basePath . '/javascript/RequirementsTest_a.js');
|
||||
$backend->javascript($basePath . '/javascript/RequirementsTest_b.js');
|
||||
$backend->javascript($basePath . '/javascript/RequirementsTest_c.js');
|
||||
$backend->javascript('javascript/RequirementsTest_a.js');
|
||||
$backend->javascript('javascript/RequirementsTest_b.js');
|
||||
$backend->javascript('javascript/RequirementsTest_c.js');
|
||||
|
||||
// require two of those files as combined includes
|
||||
$backend->combineFiles(
|
||||
'RequirementsTest_bc.js',
|
||||
array(
|
||||
$basePath . '/javascript/RequirementsTest_b.js',
|
||||
$basePath . '/javascript/RequirementsTest_c.js'
|
||||
),
|
||||
array(
|
||||
[
|
||||
'javascript/RequirementsTest_b.js',
|
||||
'javascript/RequirementsTest_c.js'
|
||||
],
|
||||
[
|
||||
'async' => $async,
|
||||
'defer' => $defer,
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@ -155,17 +171,14 @@ class RequirementsTest extends SapphireTest
|
||||
{
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$basePath = $this->getThemeRoot();
|
||||
$this->setupRequirements($backend);
|
||||
|
||||
// require files normally (e.g. called from a FormField instance)
|
||||
$backend->javascript(
|
||||
$basePath . '/javascript/RequirementsTest_a.js',
|
||||
[
|
||||
'type' => 'application/json'
|
||||
]
|
||||
'javascript/RequirementsTest_a.js',
|
||||
[ 'type' => 'application/json' ]
|
||||
);
|
||||
$backend->javascript($basePath . '/javascript/RequirementsTest_b.js');
|
||||
$backend->javascript('javascript/RequirementsTest_b.js');
|
||||
$result = $backend->includeInHTML(self::$html_template);
|
||||
$this->assertRegExp(
|
||||
'#<script type="application/json" src=".*/javascript/RequirementsTest_a.js#',
|
||||
@ -505,26 +518,27 @@ class RequirementsTest extends SapphireTest
|
||||
|
||||
public function testCombinedCss()
|
||||
{
|
||||
$basePath = $this->getThemeRoot();
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
|
||||
$backend->combineFiles(
|
||||
'print.css',
|
||||
array(
|
||||
$basePath . '/css/RequirementsTest_print_a.css',
|
||||
$basePath . '/css/RequirementsTest_print_b.css'
|
||||
),
|
||||
array(
|
||||
[
|
||||
'css/RequirementsTest_print_a.css',
|
||||
'css/RequirementsTest_print_b.css',
|
||||
'css/RequirementsTest_print_d.css',
|
||||
'public/css/RequirementsTest_print_e.css',
|
||||
],
|
||||
[
|
||||
'media' => 'print'
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
$html = $backend->includeInHTML(self::$html_template);
|
||||
|
||||
$this->assertRegExp(
|
||||
'/href=".*\/print\-94e723d\.css/',
|
||||
'/href=".*\/print\-69ce614\.css/',
|
||||
$html,
|
||||
'Print stylesheets have been combined.'
|
||||
);
|
||||
@ -540,22 +554,26 @@ class RequirementsTest extends SapphireTest
|
||||
$this->setupRequirements($backend);
|
||||
$backend->combineFiles(
|
||||
'style.css',
|
||||
array(
|
||||
$basePath . '/css/RequirementsTest_b.css',
|
||||
$basePath . '/css/RequirementsTest_c.css'
|
||||
)
|
||||
[
|
||||
'css/RequirementsTest_b.css',
|
||||
'css/RequirementsTest_c.css',
|
||||
'css/RequirementsTest_d.css',
|
||||
'public/css/RequirementsTest_e.css',
|
||||
]
|
||||
);
|
||||
$backend->combineFiles(
|
||||
'style.css',
|
||||
array(
|
||||
$basePath . '/css/RequirementsTest_b.css',
|
||||
$basePath . '/css/RequirementsTest_c.css'
|
||||
'css/RequirementsTest_b.css',
|
||||
'css/RequirementsTest_c.css',
|
||||
'css/RequirementsTest_d.css',
|
||||
'public/css/RequirementsTest_e.css',
|
||||
)
|
||||
);
|
||||
|
||||
$html = $backend->includeInHTML(self::$html_template);
|
||||
$this->assertRegExp(
|
||||
'/href=".*\/style\-bcd90f5\.css/',
|
||||
'/href=".*\/style\-8011538\.css/',
|
||||
$html,
|
||||
'Stylesheets have been combined.'
|
||||
);
|
||||
@ -563,7 +581,6 @@ class RequirementsTest extends SapphireTest
|
||||
|
||||
public function testBlockedCombinedJavascript()
|
||||
{
|
||||
$basePath = $this->getThemeRoot();
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupCombinedRequirements($backend);
|
||||
@ -585,7 +602,7 @@ class RequirementsTest extends SapphireTest
|
||||
|
||||
/* BLOCKED UNCOMBINED FILES ARE NOT INCLUDED */
|
||||
$this->setupCombinedRequirements($backend);
|
||||
$backend->block($basePath .'/javascript/RequirementsTest_b.js');
|
||||
$backend->block('javascript/RequirementsTest_b.js');
|
||||
$combinedFileName2 = '/_combinedfiles/RequirementsTest_bc-3748f67.js'; // SHA1 without file b included
|
||||
$combinedFilePath2 = TestAssetStore::base_path() . $combinedFileName2;
|
||||
clearstatcache(); // needed to get accurate file_exists() results
|
||||
@ -596,7 +613,7 @@ class RequirementsTest extends SapphireTest
|
||||
file_get_contents($combinedFilePath2),
|
||||
'blocked uncombined files are not included'
|
||||
);
|
||||
$backend->unblock($basePath . '/javascript/RequirementsTest_b.js');
|
||||
$backend->unblock('javascript/RequirementsTest_b.js');
|
||||
|
||||
/* A SINGLE FILE CAN'T BE INCLUDED IN TWO COMBINED FILES */
|
||||
$this->setupCombinedRequirements($backend);
|
||||
@ -606,28 +623,26 @@ class RequirementsTest extends SapphireTest
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage(sprintf(
|
||||
"Requirements_Backend::combine_files(): Already included file(s) %s in combined file '%s'",
|
||||
$basePath . '/javascript/RequirementsTest_c.js',
|
||||
'javascript/RequirementsTest_c.js',
|
||||
'RequirementsTest_bc.js'
|
||||
));
|
||||
$backend->combineFiles(
|
||||
'RequirementsTest_ac.js',
|
||||
array(
|
||||
$basePath . '/javascript/RequirementsTest_a.js',
|
||||
$basePath . '/javascript/RequirementsTest_c.js'
|
||||
)
|
||||
[
|
||||
'javascript/RequirementsTest_a.js',
|
||||
'javascript/RequirementsTest_c.js'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function testArgsInUrls()
|
||||
{
|
||||
$basePath = $this->getThemeRoot();
|
||||
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
|
||||
$backend->javascript($basePath . '/javascript/RequirementsTest_a.js?test=1&test=2&test=3');
|
||||
$backend->css($basePath . '/css/RequirementsTest_a.css?test=1&test=2&test=3');
|
||||
$backend->javascript('javascript/RequirementsTest_a.js?test=1&test=2&test=3');
|
||||
$backend->css('css/RequirementsTest_a.css?test=1&test=2&test=3');
|
||||
$html = $backend->includeInHTML(self::$html_template);
|
||||
|
||||
/* Javascript has correct path */
|
||||
@ -647,12 +662,10 @@ class RequirementsTest extends SapphireTest
|
||||
|
||||
public function testRequirementsBackend()
|
||||
{
|
||||
$basePath = $this->getThemeRoot();
|
||||
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
$backend->javascript($basePath . '/a.js');
|
||||
$backend->javascript('a.js');
|
||||
|
||||
$this->assertCount(
|
||||
1,
|
||||
@ -660,48 +673,48 @@ class RequirementsTest extends SapphireTest
|
||||
"There should be only 1 file included in required javascript."
|
||||
);
|
||||
$this->assertArrayHasKey(
|
||||
$basePath . '/a.js',
|
||||
'a.js',
|
||||
$backend->getJavascript(),
|
||||
"a.js should be included in required javascript."
|
||||
);
|
||||
|
||||
$backend->javascript($basePath . '/b.js');
|
||||
$backend->javascript('b.js');
|
||||
$this->assertCount(
|
||||
2,
|
||||
$backend->getJavascript(),
|
||||
"There should be 2 files included in required javascript."
|
||||
);
|
||||
|
||||
$backend->block($basePath . '/a.js');
|
||||
$backend->block('a.js');
|
||||
$this->assertCount(
|
||||
1,
|
||||
$backend->getJavascript(),
|
||||
"There should be only 1 file included in required javascript."
|
||||
);
|
||||
$this->assertArrayNotHasKey(
|
||||
$basePath . '/a.js',
|
||||
'a.js',
|
||||
$backend->getJavascript(),
|
||||
"a.js should not be included in required javascript after it has been blocked."
|
||||
);
|
||||
$this->assertArrayHasKey(
|
||||
$basePath . '/b.js',
|
||||
'b.js',
|
||||
$backend->getJavascript(),
|
||||
"b.js should be included in required javascript."
|
||||
);
|
||||
|
||||
$backend->css($basePath . '/a.css');
|
||||
$backend->css('a.css');
|
||||
$this->assertCount(
|
||||
1,
|
||||
$backend->getCSS(),
|
||||
"There should be only 1 file included in required css."
|
||||
);
|
||||
$this->assertArrayHasKey(
|
||||
$basePath . '/a.css',
|
||||
'a.css',
|
||||
$backend->getCSS(),
|
||||
"a.css should be in required css."
|
||||
);
|
||||
|
||||
$backend->block($basePath . '/a.css');
|
||||
$backend->block('a.css');
|
||||
$this->assertCount(
|
||||
0,
|
||||
$backend->getCSS(),
|
||||
@ -711,8 +724,6 @@ class RequirementsTest extends SapphireTest
|
||||
|
||||
public function testAppendAndBlockWithModuleResourceLoader()
|
||||
{
|
||||
$basePath = $this->getThemeRoot();
|
||||
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
@ -735,38 +746,40 @@ class RequirementsTest extends SapphireTest
|
||||
|
||||
public function testConditionalTemplateRequire()
|
||||
{
|
||||
$testPath = $this->getThemeRoot();
|
||||
// Set /SSViewerTest and /SSViewerTest/public as themes
|
||||
SSViewer::set_themes([
|
||||
'/',
|
||||
SSViewer::PUBLIC_THEME
|
||||
]);
|
||||
ThemeResourceLoader::set_instance(new ThemeResourceLoader(__DIR__ . '/SSViewerTest'));
|
||||
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
$holder = Requirements::backend();
|
||||
Requirements::set_backend($backend);
|
||||
$data = new ArrayData(
|
||||
array(
|
||||
$data = new ArrayData([
|
||||
'FailTest' => true,
|
||||
)
|
||||
);
|
||||
]);
|
||||
|
||||
// Note: SSViewer theme automatically registered due to 'templates' directory
|
||||
$data->renderWith('RequirementsTest_Conditionals');
|
||||
$this->assertFileIncluded($backend, 'css', $testPath .'/css/RequirementsTest_a.css');
|
||||
$this->assertFileIncluded($backend, 'css', 'css/RequirementsTest_a.css');
|
||||
$this->assertFileIncluded(
|
||||
$backend,
|
||||
'js',
|
||||
array(
|
||||
$testPath .'/javascript/RequirementsTest_b.js',
|
||||
$testPath .'/javascript/RequirementsTest_c.js'
|
||||
)
|
||||
[
|
||||
'javascript/RequirementsTest_b.js',
|
||||
'javascript/RequirementsTest_c.js'
|
||||
]
|
||||
);
|
||||
$this->assertFileNotIncluded($backend, 'js', $testPath .'/javascript/RequirementsTest_a.js');
|
||||
$this->assertFileNotIncluded($backend, 'js', 'javascript/RequirementsTest_a.js');
|
||||
$this->assertFileNotIncluded(
|
||||
$backend,
|
||||
'css',
|
||||
array(
|
||||
$testPath .'/css/RequirementsTest_b.css',
|
||||
$testPath .'/css/RequirementsTest_c.css'
|
||||
)
|
||||
[
|
||||
'css/RequirementsTest_b.css',
|
||||
'css/RequirementsTest_c.css'
|
||||
]
|
||||
);
|
||||
$backend->clear();
|
||||
$data = new ArrayData(
|
||||
@ -775,23 +788,23 @@ class RequirementsTest extends SapphireTest
|
||||
)
|
||||
);
|
||||
$data->renderWith('RequirementsTest_Conditionals');
|
||||
$this->assertFileNotIncluded($backend, 'css', $testPath .'/css/RequirementsTest_a.css');
|
||||
$this->assertFileNotIncluded($backend, 'css', 'css/RequirementsTest_a.css');
|
||||
$this->assertFileNotIncluded(
|
||||
$backend,
|
||||
'js',
|
||||
array(
|
||||
$testPath .'/javascript/RequirementsTest_b.js',
|
||||
$testPath .'/javascript/RequirementsTest_c.js'
|
||||
)
|
||||
[
|
||||
'javascript/RequirementsTest_b.js',
|
||||
'javascript/RequirementsTest_c.js',
|
||||
]
|
||||
);
|
||||
$this->assertFileIncluded($backend, 'js', $testPath .'/javascript/RequirementsTest_a.js');
|
||||
$this->assertFileIncluded($backend, 'js', 'javascript/RequirementsTest_a.js');
|
||||
$this->assertFileIncluded(
|
||||
$backend,
|
||||
'css',
|
||||
array(
|
||||
$testPath .'/css/RequirementsTest_b.css',
|
||||
$testPath .'/css/RequirementsTest_c.css'
|
||||
)
|
||||
[
|
||||
'css/RequirementsTest_b.css',
|
||||
'css/RequirementsTest_c.css',
|
||||
]
|
||||
);
|
||||
Requirements::set_backend($holder);
|
||||
}
|
||||
@ -822,7 +835,7 @@ class RequirementsTest extends SapphireTest
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
$backend->javascript($this->getThemeRoot() . '/javascript/RequirementsTest_a.js');
|
||||
$backend->javascript('javascript/RequirementsTest_a.js');
|
||||
$html = $backend->includeInHTML($template);
|
||||
//wiping out commented-out html
|
||||
$html = preg_replace('/<!--(.*)-->/Uis', '', $html);
|
||||
@ -841,7 +854,7 @@ class RequirementsTest extends SapphireTest
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
|
||||
$src = $this->getThemeRoot() . '/javascript/RequirementsTest_a.js';
|
||||
$src = 'javascript/RequirementsTest_a.js';
|
||||
$backend->javascript($src);
|
||||
$html = $backend->includeInHTML($template);
|
||||
$urlSrc = $urlGenerator->urlForResource($src);
|
||||
@ -923,16 +936,15 @@ EOS
|
||||
Injector::inst()->registerService($urlGenerator, ResourceURLGenerator::class);
|
||||
|
||||
$template = '<html><head></head><body><header>My header</header><p>Body</p></body></html>';
|
||||
$basePath = $this->getThemeRoot();
|
||||
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
|
||||
$backend->javascript($basePath .'/javascript/RequirementsTest_a.js');
|
||||
$backend->javascript($basePath .'/javascript/RequirementsTest_b.js?foo=bar&bla=blubb');
|
||||
$backend->css($basePath .'/css/RequirementsTest_a.css');
|
||||
$backend->css($basePath .'/css/RequirementsTest_b.css?foo=bar&bla=blubb');
|
||||
$backend->javascript('javascript/RequirementsTest_a.js');
|
||||
$backend->javascript('javascript/RequirementsTest_b.js?foo=bar&bla=blubb');
|
||||
$backend->css('css/RequirementsTest_a.css');
|
||||
$backend->css('css/RequirementsTest_b.css?foo=bar&bla=blubb');
|
||||
|
||||
$urlGenerator->setNonceStyle('mtime');
|
||||
$html = $backend->includeInHTML($template);
|
||||
@ -957,27 +969,26 @@ EOS
|
||||
{
|
||||
/** @var Requirements_Backend $backend */
|
||||
$template = '<html><head></head><body><header>My header</header><p>Body</p></body></html>';
|
||||
$basePath = $this->getThemeRoot();
|
||||
|
||||
// Test that provided files block subsequent files
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
$backend->javascript($basePath . '/javascript/RequirementsTest_a.js');
|
||||
$backend->javascript('javascript/RequirementsTest_a.js');
|
||||
$backend->javascript(
|
||||
$basePath . '/javascript/RequirementsTest_b.js',
|
||||
'javascript/RequirementsTest_b.js',
|
||||
[
|
||||
'provides' => [
|
||||
$basePath . '/javascript/RequirementsTest_a.js',
|
||||
$basePath . '/javascript/RequirementsTest_c.js'
|
||||
]
|
||||
'javascript/RequirementsTest_a.js',
|
||||
'javascript/RequirementsTest_c.js',
|
||||
],
|
||||
]
|
||||
);
|
||||
$backend->javascript($basePath . '/javascript/RequirementsTest_c.js');
|
||||
$backend->javascript('javascript/RequirementsTest_c.js');
|
||||
// Note that _a.js isn't considered provided because it was included
|
||||
// before it was marked as provided
|
||||
$this->assertEquals(
|
||||
[
|
||||
$basePath . '/javascript/RequirementsTest_c.js' => $basePath . '/javascript/RequirementsTest_c.js'
|
||||
'javascript/RequirementsTest_c.js' => 'javascript/RequirementsTest_c.js'
|
||||
],
|
||||
$backend->getProvidedScripts()
|
||||
);
|
||||
@ -989,20 +1000,20 @@ EOS
|
||||
// Test that provided files block subsequent combined files
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
$backend->combineFiles('combined_a.js', [$basePath . '/javascript/RequirementsTest_a.js']);
|
||||
$backend->combineFiles('combined_a.js', ['javascript/RequirementsTest_a.js']);
|
||||
$backend->javascript(
|
||||
$basePath . '/javascript/RequirementsTest_b.js',
|
||||
'javascript/RequirementsTest_b.js',
|
||||
[
|
||||
'provides' => [
|
||||
$basePath . '/javascript/RequirementsTest_a.js',
|
||||
$basePath . '/javascript/RequirementsTest_c.js'
|
||||
'javascript/RequirementsTest_a.js',
|
||||
'javascript/RequirementsTest_c.js'
|
||||
]
|
||||
]
|
||||
);
|
||||
$backend->combineFiles('combined_c.js', [$basePath . '/javascript/RequirementsTest_c.js']);
|
||||
$backend->combineFiles('combined_c.js', ['javascript/RequirementsTest_c.js']);
|
||||
$this->assertEquals(
|
||||
[
|
||||
$basePath . '/javascript/RequirementsTest_c.js' => $basePath . '/javascript/RequirementsTest_c.js'
|
||||
'javascript/RequirementsTest_c.js' => 'javascript/RequirementsTest_c.js'
|
||||
],
|
||||
$backend->getProvidedScripts()
|
||||
);
|
||||
@ -1098,14 +1109,4 @@ EOS
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base directory of theme to use for this test
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getThemeRoot()
|
||||
{
|
||||
return $this->getCurrentRelativePath() . '/SSViewerTest';
|
||||
}
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ class SSViewerTest extends SapphireTest
|
||||
// secondly, make sure that requirements is generated, even though minification failed
|
||||
$testBackend->processCombinedFiles();
|
||||
$js = array_keys($testBackend->getJavascript());
|
||||
$combinedTestFilePath = BASE_PATH . reset($js);
|
||||
$combinedTestFilePath = Director::publicFolder() . reset($js);
|
||||
$this->assertContains('_combinedfiles/testRequirementsCombine-4c0e97a.js', $combinedTestFilePath);
|
||||
|
||||
// and make sure the combined content matches the input content, i.e. no loss of functionality
|
||||
|
@ -0,0 +1 @@
|
||||
.c {color: #0ff;}
|
@ -0,0 +1 @@
|
||||
.c {color: #0ff;}
|
@ -0,0 +1 @@
|
||||
.c {color: #0ff;}
|
@ -0,0 +1 @@
|
||||
.c {color: #0ff;}
|
@ -0,0 +1 @@
|
||||
alert('d');
|
@ -0,0 +1 @@
|
||||
alert('e');
|
Loading…
Reference in New Issue
Block a user