2017-06-27 04:30:48 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace SilverStripe\Control;
|
|
|
|
|
2017-06-28 06:59:41 +02:00
|
|
|
use InvalidArgumentException;
|
2017-09-21 07:54:06 +02:00
|
|
|
use SilverStripe\Core\Config\Config;
|
2018-01-12 04:25:02 +01:00
|
|
|
use SilverStripe\Core\Convert;
|
|
|
|
use SilverStripe\Core\Manifest\ManifestFileFinder;
|
2017-09-21 07:54:06 +02:00
|
|
|
use SilverStripe\Core\Manifest\ModuleResource;
|
2017-06-27 04:30:48 +02:00
|
|
|
use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
2018-01-12 04:25:02 +01:00
|
|
|
use SilverStripe\Core\Path;
|
2017-06-27 04:30:48 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate URLs assuming that BASE_PATH is also the webroot
|
|
|
|
* Standard SilverStripe 3 operation
|
|
|
|
*/
|
|
|
|
class SimpleResourceURLGenerator implements ResourceURLGenerator
|
|
|
|
{
|
2017-09-21 07:54:06 +02:00
|
|
|
/**
|
|
|
|
* Rewrites applied after generating url.
|
|
|
|
* Note: requires either silverstripe/vendor-plugin-helper or silverstripe/vendor-plugin
|
|
|
|
* to ensure the file is available.
|
|
|
|
*
|
|
|
|
* @config
|
|
|
|
* @var array
|
|
|
|
*/
|
2018-01-12 04:25:02 +01:00
|
|
|
private static $url_rewrites = [];
|
2017-09-21 07:54:06 +02:00
|
|
|
|
2017-06-27 04:30:48 +02:00
|
|
|
/*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $nonceStyle;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the style of nonce-suffixes to use, or null if disabled
|
|
|
|
*
|
|
|
|
* @return string|null
|
|
|
|
*/
|
|
|
|
public function getNonceStyle()
|
|
|
|
{
|
|
|
|
return $this->nonceStyle;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the style of nonce-suffixes to use, or null to disable
|
|
|
|
* Currently only "mtime" is allowed
|
|
|
|
*
|
|
|
|
* @param string|null $nonceStyle The style of nonces to apply, or null to disable
|
2017-06-28 06:59:41 +02:00
|
|
|
* @return $this
|
2017-06-27 04:30:48 +02:00
|
|
|
*/
|
|
|
|
public function setNonceStyle($nonceStyle)
|
|
|
|
{
|
|
|
|
if ($nonceStyle && $nonceStyle !== 'mtime') {
|
|
|
|
throw new InvalidArgumentException('The only allowed NonceStyle is mtime');
|
|
|
|
}
|
|
|
|
$this->nonceStyle = $nonceStyle;
|
2017-06-28 06:59:41 +02:00
|
|
|
return $this;
|
2017-06-27 04:30:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the URL for a resource, prefixing with Director::baseURL() and suffixing with a nonce
|
|
|
|
*
|
2017-09-21 07:54:06 +02:00
|
|
|
* @param string|ModuleResource $relativePath File or directory path relative to BASE_PATH
|
2017-06-27 04:30:48 +02:00
|
|
|
* @return string Doman-relative URL
|
|
|
|
* @throws InvalidArgumentException If the resource doesn't exist
|
|
|
|
*/
|
|
|
|
public function urlForResource($relativePath)
|
|
|
|
{
|
2018-01-12 04:25:02 +01:00
|
|
|
$query = '';
|
2017-09-21 07:54:06 +02:00
|
|
|
if ($relativePath instanceof ModuleResource) {
|
2018-01-12 04:25:02 +01:00
|
|
|
list($exists, $absolutePath, $relativePath) = $this->resolveModuleResource($relativePath);
|
2018-01-23 21:04:22 +01:00
|
|
|
} elseif (Director::is_absolute_url($relativePath)) {
|
2018-01-23 05:31:43 +01:00
|
|
|
// Path is not relative, and probably not of this site
|
|
|
|
return $relativePath;
|
2017-09-21 07:54:06 +02:00
|
|
|
} else {
|
2018-01-12 04:25:02 +01:00
|
|
|
// 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);
|
|
|
|
}
|
2017-09-21 07:54:06 +02:00
|
|
|
}
|
|
|
|
if (!$exists) {
|
2017-10-16 05:11:42 +02:00
|
|
|
trigger_error("File {$relativePath} does not exist", E_USER_NOTICE);
|
2017-06-27 04:30:48 +02:00
|
|
|
}
|
|
|
|
|
2018-01-12 04:25:02 +01:00
|
|
|
// Switch slashes for URL
|
|
|
|
$relativeURL = Convert::slashes($relativePath, '/');
|
|
|
|
|
2017-09-21 07:54:06 +02:00
|
|
|
// Apply url rewrites
|
|
|
|
$rules = Config::inst()->get(static::class, 'url_rewrites') ?: [];
|
|
|
|
foreach ($rules as $from => $to) {
|
2018-01-12 04:25:02 +01:00
|
|
|
$relativeURL = preg_replace($from, $to, $relativeURL);
|
2017-09-21 07:54:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Apply nonce
|
2017-06-28 06:59:41 +02:00
|
|
|
// Don't add nonce to directories
|
2017-10-16 05:11:42 +02:00
|
|
|
if ($this->nonceStyle && $exists && is_file($absolutePath)) {
|
2017-06-27 04:30:48 +02:00
|
|
|
switch ($this->nonceStyle) {
|
|
|
|
case 'mtime':
|
2018-01-12 04:25:02 +01:00
|
|
|
if ($query) {
|
|
|
|
$query .= '&';
|
|
|
|
}
|
|
|
|
$query .= "m=" . filemtime($absolutePath);
|
2017-06-27 04:30:48 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-12 04:25:02 +01:00
|
|
|
// 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];
|
2017-06-27 04:30:48 +02:00
|
|
|
}
|
|
|
|
}
|