mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge branch '4' into 5
This commit is contained in:
commit
715415d5c8
@ -40,6 +40,7 @@
|
||||
"swiftmailer/swiftmailer": "^6.3.0",
|
||||
"symfony/cache": "^4.4.44",
|
||||
"symfony/config": "^4.4.44",
|
||||
"symfony/filesystem": "^6.0",
|
||||
"symfony/translation": "^4.4.44",
|
||||
"symfony/yaml": "^4.4.44",
|
||||
"ext-ctype": "*",
|
||||
|
@ -338,6 +338,7 @@ abstract class BaseKernel implements Kernel
|
||||
}
|
||||
|
||||
/**
|
||||
>>>>>>> 4
|
||||
* @return bool
|
||||
*/
|
||||
protected function getIncludeTests()
|
||||
|
@ -123,6 +123,10 @@ class ClassLoader
|
||||
*/
|
||||
public function init($includeTests = false, $forceRegen = false)
|
||||
{
|
||||
if (!empty($ignoredCIConfigs)) {
|
||||
Deprecation::notice('5.0.0', 'The $ignoredCIConfigs parameter will be removed in CMS 5');
|
||||
}
|
||||
|
||||
foreach ($this->manifests as $manifest) {
|
||||
/** @var ClassManifest $instance */
|
||||
$instance = $manifest['instance'];
|
||||
|
@ -11,6 +11,7 @@ use PhpParser\ParserFactory;
|
||||
use PhpParser\ErrorHandler\ErrorHandler;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Core\Cache\CacheFactory;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
|
||||
/**
|
||||
@ -279,6 +280,10 @@ class ClassManifest
|
||||
*/
|
||||
public function init($includeTests = false, $forceRegen = false)
|
||||
{
|
||||
if (!empty($ignoredCIConfigs)) {
|
||||
Deprecation::notice('5.0.0', 'The $ignoredCIConfigs parameter will be removed in CMS 5');
|
||||
}
|
||||
|
||||
$this->cache = $this->buildCache($includeTests);
|
||||
|
||||
// Check if cache is safe to use
|
||||
@ -541,6 +546,10 @@ class ClassManifest
|
||||
*/
|
||||
public function regenerate($includeTests)
|
||||
{
|
||||
if (!empty($ignoredCIConfigs)) {
|
||||
Deprecation::notice('5.0.0', 'The $ignoredCIConfigs parameter will be removed in CMS 5');
|
||||
}
|
||||
|
||||
// Reset the manifest so stale info doesn't cause errors.
|
||||
$this->loadState([]);
|
||||
$this->roots = [];
|
||||
@ -551,7 +560,7 @@ class ClassManifest
|
||||
'name_regex' => '/^[^_].*\\.php$/',
|
||||
'ignore_files' => ['index.php', 'cli-script.php'],
|
||||
'ignore_tests' => !$includeTests,
|
||||
'file_callback' => function ($basename, $pathname, $depth) use ($includeTests, $finder) {
|
||||
'file_callback' => function ($basename, $pathname, $depth) use ($includeTests) {
|
||||
$this->handleFile($basename, $pathname, $includeTests);
|
||||
},
|
||||
]);
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
/**
|
||||
* Module manifest holder
|
||||
*/
|
||||
@ -94,6 +96,10 @@ class ModuleLoader
|
||||
*/
|
||||
public function init($includeTests = false, $forceRegen = false)
|
||||
{
|
||||
if (!empty($ignoredCIConfigs)) {
|
||||
Deprecation::notice('5.0.0', 'The $ignoredCIConfigs parameter will be removed in CMS 5');
|
||||
}
|
||||
|
||||
foreach ($this->manifests as $manifest) {
|
||||
$manifest->init($includeTests, $forceRegen);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Core\Cache\CacheFactory;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
/**
|
||||
* A utility class which builds a manifest of configuration items
|
||||
@ -124,6 +125,10 @@ class ModuleManifest
|
||||
*/
|
||||
public function init($includeTests = false, $forceRegen = false)
|
||||
{
|
||||
if (!empty($ignoredCIConfigs)) {
|
||||
Deprecation::notice('5.0.0', 'The $ignoredCIConfigs parameter will be removed in CMS 5');
|
||||
}
|
||||
|
||||
// build cache from factory
|
||||
if ($this->cacheFactory) {
|
||||
$this->cache = $this->cacheFactory->create(
|
||||
@ -165,6 +170,10 @@ class ModuleManifest
|
||||
*/
|
||||
public function regenerate($includeTests = false)
|
||||
{
|
||||
if (!empty($ignoredCIConfigs)) {
|
||||
Deprecation::notice('5.0.0', 'The $ignoredCIConfigs parameter will be removed in CMS 5');
|
||||
}
|
||||
|
||||
$this->modules = [];
|
||||
|
||||
$finder = new ManifestFileFinder();
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Dev\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Dev\SSListExporter;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\SS_List;
|
||||
@ -29,6 +30,8 @@ class SSListContains extends Constraint implements TestOnly
|
||||
|
||||
public function __construct(array $matches)
|
||||
{
|
||||
Deprecation::notice('5.0.0', 'This class will be removed in CMS 5', Deprecation::SCOPE_CLASS);
|
||||
|
||||
$this->exporter = new SSListExporter();
|
||||
|
||||
$this->matches = $matches;
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Dev\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Dev\SSListExporter;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\SS_List;
|
||||
@ -26,6 +27,7 @@ class SSListContainsOnlyMatchingItems extends Constraint implements TestOnly
|
||||
|
||||
public function __construct($match)
|
||||
{
|
||||
Deprecation::notice('5.0.0', 'This class will be removed in CMS 5', Deprecation::SCOPE_CLASS);
|
||||
$this->exporter = new SSListExporter();
|
||||
|
||||
$this->constraint = new ViewableDataContains($match);
|
||||
|
@ -72,7 +72,6 @@ class ViewableDataContains extends Constraint implements TestOnly
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of the object.
|
||||
*
|
||||
|
@ -83,6 +83,8 @@ abstract class FunctionalTest extends SapphireTest implements TestOnly
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
Deprecation::notice('5.0.0', 'This class will be removed in CMS 5', Deprecation::SCOPE_CLASS);
|
||||
|
||||
parent::setUp();
|
||||
|
||||
// Skip calling FunctionalTest directly.
|
||||
|
@ -264,6 +264,8 @@ abstract class SapphireTest extends TestCase implements TestOnly
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
Deprecation::notice('5.0.0', 'This class will be removed in CMS 5', Deprecation::SCOPE_CLASS);
|
||||
|
||||
if (!defined('FRAMEWORK_PATH')) {
|
||||
trigger_error(
|
||||
'Missing constants, did you remember to include the test bootstrap in your phpunit.xml file?',
|
||||
|
@ -123,7 +123,7 @@ class ConfirmedPasswordField extends FormField
|
||||
* @param string $name
|
||||
* @param string $title
|
||||
* @param mixed $value
|
||||
* @param Form $form
|
||||
* @param Form $form Ignored for ConfirmedPasswordField.
|
||||
* @param boolean $showOnClick
|
||||
* @param string $titleConfirmField Alternate title (not localizeable)
|
||||
*/
|
||||
|
@ -589,7 +589,7 @@ class FormField extends RequestHandler
|
||||
//
|
||||
// CSS class needs to be different from the one rendered through {@link FieldHolder()}.
|
||||
if ($this->getMessage()) {
|
||||
$classes[] .= 'holder-' . $this->getMessageType();
|
||||
$classes[] = 'holder-' . $this->getMessageType();
|
||||
}
|
||||
|
||||
return implode(' ', $classes);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Forms\GridField;
|
||||
|
||||
use LogicException;
|
||||
use SilverStripe\Admin\LeftAndMain;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
@ -328,7 +329,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
||||
/** @var GridFieldDetailForm $component */
|
||||
$component = $this->gridField->getConfig()->getComponentByType(GridFieldDetailForm::class);
|
||||
$paginator = $this->getGridField()->getConfig()->getComponentByType(GridFieldPaginator::class);
|
||||
$gridState = $this->getStateManager()->getStateFromRequest($this->gridField, $this->getRequest());
|
||||
$gridState = $this->getGridField()->getState();
|
||||
if ($component && $paginator && $component->getShowPagination()) {
|
||||
$previousIsDisabled = !$this->getPreviousRecordID();
|
||||
$nextIsDisabled = !$this->getNextRecordID();
|
||||
@ -337,8 +338,8 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
||||
LiteralField::create(
|
||||
'previous-record',
|
||||
HTML::createTag($previousIsDisabled ? 'span' : 'a', [
|
||||
'href' => $previousIsDisabled ? '#' : $this->getEditLink($this->getPreviousRecordID()),
|
||||
'data-grid-state' => $gridState,
|
||||
'href' => $previousIsDisabled ? '#' : $this->getEditLinkForAdjacentRecord(-1),
|
||||
'data-grid-state' => $previousIsDisabled ? $gridState : $this->getGridStateForAdjacentRecord(-1),
|
||||
'title' => _t(__CLASS__ . '.PREVIOUS', 'Go to previous record'),
|
||||
'aria-label' => _t(__CLASS__ . '.PREVIOUS', 'Go to previous record'),
|
||||
'class' => 'btn btn-secondary font-icon-left-open action--previous discard-confirmation'
|
||||
@ -351,8 +352,8 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
||||
LiteralField::create(
|
||||
'next-record',
|
||||
HTML::createTag($nextIsDisabled ? 'span' : 'a', [
|
||||
'href' => $nextIsDisabled ? '#' : $this->getEditLink($this->getNextRecordID()),
|
||||
'data-grid-state' => $gridState,
|
||||
'href' => $nextIsDisabled ? '#' : $this->getEditLinkForAdjacentRecord(+1),
|
||||
'data-grid-state' => $nextIsDisabled ? $gridState : $this->getGridStateForAdjacentRecord(+1),
|
||||
'title' => _t(__CLASS__ . '.NEXT', 'Go to next record'),
|
||||
'aria-label' => _t(__CLASS__ . '.NEXT', 'Go to next record'),
|
||||
'class' => 'btn btn-secondary font-icon-right-open action--next discard-confirmation'
|
||||
@ -413,8 +414,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
||||
->addExtraClass('btn-outline-danger btn-hide-outline font-icon-trash-bin action--delete'));
|
||||
}
|
||||
|
||||
$gridState = $manager->getStateFromRequest($this->gridField, $this->getRequest());
|
||||
$this->gridField->getState(false)->setValue($gridState);
|
||||
$gridState = $this->gridField->getState(false);
|
||||
$actions->push(HiddenField::create($manager->getStateKey($this->gridField), null, $gridState));
|
||||
|
||||
$actions->push($this->getRightGroupField());
|
||||
@ -561,7 +561,88 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
||||
$id
|
||||
);
|
||||
|
||||
return $this->getStateManager()->addStateToURL($this->gridField, $link);
|
||||
return $this->gridField->addAllStateToUrl($link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of GridField items on current page plus
|
||||
* first item on the next page and last item on the previous page
|
||||
*/
|
||||
private function getGridFieldItemAdjacencies(): array
|
||||
{
|
||||
$list = $this->getGridField()->getManipulatedList();
|
||||
$paginator = $this->getGridFieldPaginatorState();
|
||||
if (!$paginator) {
|
||||
return [];
|
||||
}
|
||||
$currentPage = $paginator->getData('currentPage');
|
||||
$itemsPerPage = $paginator->getData('itemsPerPage');
|
||||
|
||||
$limit = $itemsPerPage + 2;
|
||||
$limitOffset = max(0, $itemsPerPage * ($currentPage-1) -1);
|
||||
|
||||
return $list->limit($limit, $limitOffset)->column('ID');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current paginator state
|
||||
*/
|
||||
private function getGridFieldPaginatorState(): GridState_Data
|
||||
{
|
||||
$state = $this->getGridField()->getState(false);
|
||||
$gridStateStr = $this->getStateManager()->getStateFromRequest($this->gridField, $this->getRequest());
|
||||
if (!empty($gridStateStr)) {
|
||||
$state->setValue($gridStateStr);
|
||||
}
|
||||
|
||||
return $state->getData()->getData('GridFieldPaginator');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the grid state for an adjacent record
|
||||
*/
|
||||
private function getGridStateForAdjacentRecord(int $offset): GridState_Data
|
||||
{
|
||||
$gridField = $this->getGridField();
|
||||
$map = $this->getGridFieldItemAdjacencies();
|
||||
if (empty($map)) {
|
||||
throw new LogicException('No adjacent records exist');
|
||||
}
|
||||
|
||||
$state = clone $gridField->getState();
|
||||
$index = array_search($this->record->ID, $map);
|
||||
$position = $index + $offset;
|
||||
|
||||
$currentPage = $this->getGridFieldPaginatorState()->getData('currentPage');
|
||||
$itemsPerPage = $this->getGridFieldPaginatorState()->getData('itemsPerPage');
|
||||
$page = $currentPage;
|
||||
$hasMorePages = $this->getNumPages($gridField) > $currentPage;
|
||||
|
||||
if ($position === 0 && $currentPage > 1) {
|
||||
$page = $currentPage - 1;
|
||||
} elseif ($hasMorePages && $position >= $itemsPerPage + 1) {
|
||||
$page = $currentPage + 1;
|
||||
}
|
||||
$state->GridFieldPaginator->currentPage = (int)$page;
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edit link for an adjacent record
|
||||
*/
|
||||
private function getEditLinkForAdjacentRecord(int $offset): string
|
||||
{
|
||||
$link = Controller::join_links(
|
||||
$this->gridField->Link(),
|
||||
'item',
|
||||
$this->getAdjacentRecordID($offset)
|
||||
);
|
||||
$state = $this->getGridStateForAdjacentRecord($offset);
|
||||
// Get a dummy gridfield so we can set some future state without affecting the current gridfield
|
||||
$gridField = clone $this->gridField;
|
||||
$gridField->getState(false)->setValue($state);
|
||||
return $gridField->addAllStateToUrl($link);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -570,28 +651,25 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
|
||||
*/
|
||||
private function getAdjacentRecordID($offset)
|
||||
{
|
||||
$gridField = $this->getGridField();
|
||||
$list = $gridField->getManipulatedList();
|
||||
$state = $gridField->getState(false);
|
||||
$gridStateStr = $this->getStateManager()->getStateFromRequest($this->gridField, $this->getRequest());
|
||||
if (!empty($gridStateStr)) {
|
||||
$state->setValue($gridStateStr);
|
||||
}
|
||||
$data = $state->getData();
|
||||
$paginator = $data->getData('GridFieldPaginator');
|
||||
if (!$paginator) {
|
||||
$map = $this->getGridFieldItemAdjacencies();
|
||||
if (empty($map)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentPage = $paginator->getData('currentPage');
|
||||
$itemsPerPage = $paginator->getData('itemsPerPage');
|
||||
|
||||
$limit = $itemsPerPage + 2;
|
||||
$limitOffset = max(0, $itemsPerPage * ($currentPage-1) -1);
|
||||
|
||||
$map = $list->limit($limit, $limitOffset)->column('ID');
|
||||
$index = array_search($this->record->ID, $map ?? []);
|
||||
return isset($map[$index+$offset]) ? $map[$index+$offset] : false;
|
||||
$position = $index + $offset;
|
||||
return isset($map[$position]) ? $map[$position] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of GridField pages
|
||||
*/
|
||||
private function getNumPages(GridField $gridField): int
|
||||
{
|
||||
return $gridField
|
||||
->getConfig()
|
||||
->getComponentByType(GridFieldPaginator::class)
|
||||
->getTemplateParameters($gridField)
|
||||
->toMap()['NumPages'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace SilverStripe\Forms\GridField;
|
||||
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* Allows GridField_ActionMenuItem to act as a link
|
||||
*/
|
||||
|
@ -40,6 +40,11 @@ class GridState_Data
|
||||
return $this->getData($name, $default);
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->data = $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the defaults values for the grid field state
|
||||
* These values won't be included in getChangesArray()
|
||||
|
@ -169,7 +169,7 @@ class NullableField extends FormField
|
||||
public function debug()
|
||||
{
|
||||
$result = sprintf(
|
||||
'%s (%s: $s : <span style="color: red">%s</span>) = ',
|
||||
'%s (%s: %s : <span style="color: red">%s</span>) = ',
|
||||
static::class,
|
||||
$this->name,
|
||||
$this->title,
|
||||
|
@ -6,6 +6,7 @@ use SilverStripe\Assets\File;
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\ORM\PaginatedList;
|
||||
use SilverStripe\ORM\DataList;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
|
@ -78,23 +78,23 @@ class MySQLSchemaManager extends DBSchemaManager
|
||||
|
||||
if ($newFields) {
|
||||
foreach ($newFields as $k => $v) {
|
||||
$alterList[] .= "ADD \"$k\" $v";
|
||||
$alterList[] = "ADD \"$k\" $v";
|
||||
}
|
||||
}
|
||||
if ($newIndexes) {
|
||||
foreach ($newIndexes as $k => $v) {
|
||||
$alterList[] .= "ADD " . $this->getIndexSqlDefinition($k, $v);
|
||||
$alterList[] = "ADD " . $this->getIndexSqlDefinition($k, $v);
|
||||
}
|
||||
}
|
||||
if ($alteredFields) {
|
||||
foreach ($alteredFields as $k => $v) {
|
||||
$alterList[] .= "CHANGE \"$k\" \"$k\" $v";
|
||||
$alterList[] = "CHANGE \"$k\" \"$k\" $v";
|
||||
}
|
||||
}
|
||||
if ($alteredIndexes) {
|
||||
foreach ($alteredIndexes as $k => $v) {
|
||||
$alterList[] .= "DROP INDEX \"$k\"";
|
||||
$alterList[] .= "ADD " . $this->getIndexSqlDefinition($k, $v);
|
||||
$alterList[] = "DROP INDEX \"$k\"";
|
||||
$alterList[] = "ADD " . $this->getIndexSqlDefinition($k, $v);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -849,7 +849,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function exists()
|
||||
{
|
||||
return (isset($this->record['ID']) && $this->record['ID'] > 0);
|
||||
return $this->isInDB();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,7 +12,6 @@ use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Dev\DevelopmentAdmin;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\Connect\DatabaseException;
|
||||
use SilverStripe\ORM\Connect\TableBuilder;
|
||||
use SilverStripe\ORM\FieldType\DBClassName;
|
||||
|
@ -99,7 +99,7 @@ class ShortcodeParser
|
||||
* - An associative array of extra information about the shortcode being parsed.
|
||||
*
|
||||
* @param string $shortcode The shortcode tag to map to the callback - normally in lowercase_underscore format.
|
||||
* @param callback $callback The callback to replace the shortcode with.
|
||||
* @param callable $callback The callback to replace the shortcode with.
|
||||
* @return $this
|
||||
*/
|
||||
public function register($shortcode, $callback)
|
||||
|
@ -19,6 +19,7 @@ use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use Symfony\Component\Filesystem\Path as FilesystemPath;
|
||||
|
||||
class Requirements_Backend
|
||||
{
|
||||
@ -52,6 +53,20 @@ class Requirements_Backend
|
||||
*/
|
||||
private static $combine_in_dev = false;
|
||||
|
||||
/**
|
||||
* Determine if relative urls in the combined files should be converted to absolute.
|
||||
*
|
||||
* By default combined files will be parsed for relative URLs to image/font assets and those
|
||||
* URLs will be changed to absolute to accomodate the fact that the combined css is placed
|
||||
* in a totally different folder than the source css files.
|
||||
*
|
||||
* Turn this off if you see some unexpected results.
|
||||
*
|
||||
* @config
|
||||
* @var bool
|
||||
*/
|
||||
private static $resolve_relative_css_refs = false;
|
||||
|
||||
/**
|
||||
* Paths to all required JavaScript files relative to docroot
|
||||
*
|
||||
@ -1395,6 +1410,10 @@ MESSAGE
|
||||
throw new InvalidArgumentException("Combined file {$file} does not exist");
|
||||
}
|
||||
$fileContent = file_get_contents($filePath ?? '');
|
||||
if ($type == 'css') {
|
||||
// resolve relative paths for css files
|
||||
$fileContent = $this->resolveCSSReferences($fileContent, $file);
|
||||
}
|
||||
// Use configured minifier
|
||||
if ($minify) {
|
||||
$fileContent = $this->minifier->minify($fileContent, $type, $file);
|
||||
@ -1421,6 +1440,32 @@ MESSAGE
|
||||
return $combinedURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves relative paths in CSS files which are lost when combining them
|
||||
*
|
||||
* @param string $content
|
||||
* @param string $filePath
|
||||
* @return string New content with paths resolved
|
||||
*/
|
||||
protected function resolveCSSReferences($content, $filePath)
|
||||
{
|
||||
$doResolving = Config::inst()->get(__CLASS__, 'resolve_relative_css_refs');
|
||||
if (!$doResolving) {
|
||||
return $content;
|
||||
}
|
||||
$fileUrl = Injector::inst()->get(ResourceURLGenerator::class)->urlForResource($filePath);
|
||||
$fileUrlDir = dirname($fileUrl);
|
||||
$content = preg_replace_callback('#(url\([\n\r\s\'"]*)([^\s\)\?\'"]+)#i', function ($match) use ($fileUrlDir) {
|
||||
[ $fullMatch, $prefix, $relativePath ] = $match;
|
||||
if ($relativePath[0] === '/' || false !== strpos($relativePath, '://')) {
|
||||
return $fullMatch;
|
||||
}
|
||||
$substitute = FilesystemPath::canonicalize(FilesystemPath::join($fileUrlDir, $relativePath));
|
||||
return $prefix . $substitute;
|
||||
}, $content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a filename and list of files, generate a new filename unique to these files
|
||||
*
|
||||
|
@ -6,6 +6,7 @@ use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Core\Cache\CacheFactory;
|
||||
use SilverStripe\Core\Manifest\ManifestFileFinder;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
/**
|
||||
* A class which builds a manifest of all themes (which is really just a directory called "templates")
|
||||
@ -76,6 +77,10 @@ class ThemeManifest implements ThemeList
|
||||
*/
|
||||
public function init($includeTests = false, $forceRegen = false)
|
||||
{
|
||||
if (!empty($ignoredCIConfigs)) {
|
||||
Deprecation::notice('5.0.0', 'The $ignoredCIConfigs parameter will be removed in CMS 5');
|
||||
}
|
||||
|
||||
// build cache from factory
|
||||
if ($this->cacheFactory) {
|
||||
$this->cache = $this->cacheFactory->create(
|
||||
@ -118,7 +123,7 @@ class ThemeManifest implements ThemeList
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \string[]
|
||||
* @return string[]
|
||||
*/
|
||||
public function getThemes()
|
||||
{
|
||||
@ -132,6 +137,10 @@ class ThemeManifest implements ThemeList
|
||||
*/
|
||||
public function regenerate($includeTests = false)
|
||||
{
|
||||
if (!empty($ignoredCIConfigs)) {
|
||||
Deprecation::notice('5.0.0', 'The $ignoredCIConfigs parameter will be removed in CMS 5');
|
||||
}
|
||||
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOptions([
|
||||
'include_themes' => false,
|
||||
|
@ -13,6 +13,7 @@ use Silverstripe\Assets\Dev\TestAssetStore;
|
||||
use SilverStripe\View\Requirements_Backend;
|
||||
use SilverStripe\Core\Manifest\ResourceURLGenerator;
|
||||
use SilverStripe\Control\SimpleResourceURLGenerator;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
|
||||
@ -77,6 +78,219 @@ class RequirementsTest extends SapphireTest
|
||||
$this->assertStringContainsString('http://www.mydomain.com:3000/test.css', $html, 'Load external with port');
|
||||
}
|
||||
|
||||
public function testResolveCSSReferencesDisabled()
|
||||
{
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
Config::forClass(get_class($backend))->set('resolve_relative_css_refs', false);
|
||||
|
||||
$backend->combineFiles(
|
||||
'RequirementsTest_pc.css',
|
||||
[
|
||||
'css/RequirementsTest_d.css',
|
||||
'css/deep/deeper/RequirementsTest_p.css'
|
||||
]
|
||||
);
|
||||
|
||||
$backend->includeInHTML(self::$html_template);
|
||||
|
||||
// we get the file path here
|
||||
$allCSS = $backend->getCSS();
|
||||
$this->assertCount(
|
||||
1,
|
||||
$allCSS,
|
||||
'only one combined file'
|
||||
);
|
||||
|
||||
$files = array_keys($allCSS);
|
||||
$combinedFileName = $files[0];
|
||||
$combinedFileName = str_replace('/' . ASSETS_DIR . '/', '/', $combinedFileName);
|
||||
|
||||
$combinedFilePath = TestAssetStore::base_path() . $combinedFileName;
|
||||
|
||||
$content = file_get_contents($combinedFilePath);
|
||||
|
||||
/* DISABLED COMBINED CSS URL RESOLVER IGNORED ONE DOT */
|
||||
$this->assertStringContainsString(
|
||||
".p0 { background: url(./zero.gif); }",
|
||||
$content,
|
||||
'disabled combined css url resolver ignored one dot'
|
||||
);
|
||||
|
||||
/* DISABLED COMBINED CSS URL RESOLVER IGNORED DOUBLE-DOT */
|
||||
$this->assertStringContainsString(
|
||||
".p1 { background: url(../one.gif); }",
|
||||
$content,
|
||||
'disabled combined css url resolver ignored double-dot'
|
||||
);
|
||||
}
|
||||
|
||||
public function testResolveCSSReferences()
|
||||
{
|
||||
/** @var Requirements_Backend $backend */
|
||||
$backend = Injector::inst()->create(Requirements_Backend::class);
|
||||
$this->setupRequirements($backend);
|
||||
Config::forClass(get_class($backend))->set('resolve_relative_css_refs', true);
|
||||
|
||||
$backend->combineFiles(
|
||||
'RequirementsTest_pc.css',
|
||||
[
|
||||
'css/RequirementsTest_d.css',
|
||||
'css/deep/deeper/RequirementsTest_p.css'
|
||||
]
|
||||
);
|
||||
|
||||
$backend->includeInHTML(self::$html_template);
|
||||
|
||||
// we get the file path here
|
||||
$allCSS = $backend->getCSS();
|
||||
$this->assertCount(
|
||||
1,
|
||||
$allCSS,
|
||||
'only one combined file'
|
||||
);
|
||||
$files = array_keys($allCSS);
|
||||
$combinedFileName = $files[0];
|
||||
$combinedFileName = str_replace('/' . ASSETS_DIR . '/', '/', $combinedFileName);
|
||||
|
||||
$combinedFilePath = TestAssetStore::base_path() . $combinedFileName;
|
||||
|
||||
/* COMBINED JAVASCRIPT FILE EXISTS */
|
||||
$this->assertTrue(
|
||||
file_exists($combinedFilePath),
|
||||
'combined css file exists'
|
||||
);
|
||||
|
||||
$content = file_get_contents($combinedFilePath);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER IGNORE FULL URLS */
|
||||
$this->assertStringContainsString(
|
||||
".url { background: url(http://example.com/zero.gif); }",
|
||||
$content,
|
||||
'combined css url resolver ignore full urls'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED ONE DOT */
|
||||
$this->assertStringContainsString(
|
||||
".p0 { background: url(/css/deep/deeper/zero.gif); }",
|
||||
$content,
|
||||
'combined css url resolver decoded one dot'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED NO DOTS */
|
||||
$this->assertStringContainsString(
|
||||
".p0-plain { background: url(/css/deep/deeper/zero.gif); }",
|
||||
$content,
|
||||
'combined css url resolver decoded no dots'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DAMAGED A QUERYSTRING */
|
||||
$this->assertStringContainsString(
|
||||
".p0-qs { background: url(/css/deep/deeper/zero.gif?some=param); }",
|
||||
$content,
|
||||
'combined css url resolver damaged a querystring'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED ONE DOT WITH SINGLE QUOTES */
|
||||
$this->assertStringContainsString(
|
||||
".p0sq { background: url('/css/deep/deeper/zero-sq.gif'); }",
|
||||
$content,
|
||||
'combined css url resolver decoded one dot with single quotes'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED ONE DOT WITH DOUBLE QUOTES */
|
||||
$this->assertStringContainsString(
|
||||
".p0dq { background: url(\"/css/deep/deeper/zero-dq.gif\"); }",
|
||||
$content,
|
||||
'combined css url resolver decoded one dot with double quotes'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED ONE DOT WITH DOUBLE QUOTES AND SPACES NEW LINE */
|
||||
$this->assertStringContainsString(
|
||||
"\n \"/css/deep/deeper/zero-dq-nls.gif\"\n",
|
||||
$content,
|
||||
'combined css url resolver decoded one dot with double quotes and spaces new line'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED ONE DOT WITH DOUBLE QUOTES NEW LINE */
|
||||
$this->assertStringContainsString(
|
||||
"\"/css/deep/deeper/zero-dq-nl.gif\"",
|
||||
$content,
|
||||
'combined css url resolver decoded one dot with double quotes new line'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED ONE DOT WITH DOUBLE QUOTES NEW LINE WITH SPACES */
|
||||
$this->assertStringContainsString(
|
||||
"\"/css/deep/deeper/zero-dq-nls.gif\"",
|
||||
$content,
|
||||
'combined css url resolver decoded one dot with double quotes new line with spaces'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED 1 DOUBLE-DOT */
|
||||
$this->assertStringContainsString(
|
||||
".p1 { background: url(/css/deep/one.gif); }",
|
||||
$content,
|
||||
'combined css url resolver decoded 1 double-dot'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED 2 DOUBLE-DOT */
|
||||
$this->assertStringContainsString(
|
||||
".p2 { background: url(/css/two.gif); }",
|
||||
$content,
|
||||
'combined css url resolver decoded 2 double-dot'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED 2 DOUBLE-DOT SINGLE QUOTES */
|
||||
$this->assertStringContainsString(
|
||||
".p2sq { background: url('/css/two-sq.gif'); }",
|
||||
$content,
|
||||
'combined css url resolver decoded 2 double-dot single quotes'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED 2 DOUBLE-DOT DOUBLE QUOTES */
|
||||
$this->assertStringContainsString(
|
||||
".p2dq { background: url(\"/css/two-dq.gif\"); }",
|
||||
$content,
|
||||
'combined css url resolver decoded 2 double-dot double quotes'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER SHOULD NOT TOUCH ABSOLUTE PATH */
|
||||
$this->assertStringContainsString(
|
||||
".p2abs { background: url(/foo/bar/../../two-abs.gif); }",
|
||||
$content,
|
||||
'combined css url resolver should not touch absolute path'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER SHOULD NOT TOUCH ABSOLUTE PATH ON NEW LINE */
|
||||
$this->assertStringContainsString(
|
||||
"\n /foo/bar/../../two-abs-ln.gif\n",
|
||||
$content,
|
||||
'combined css url resolver should not touch absolute path on new line'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED 3 DOUBLE-DOT */
|
||||
$this->assertStringContainsString(
|
||||
".p3 { background: url(/three.gif); }",
|
||||
$content,
|
||||
'combined css url resolver decoded 3 double-dot'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER DECODED 4 DOUBLE-DOT WHEN ONLY 3 LEVELS AVAILABLE*/
|
||||
$this->assertStringContainsString(
|
||||
".p4 { background: url(/four.gif); }",
|
||||
$content,
|
||||
'combined css url resolver decoded 4 double-dot when only 3 levels available'
|
||||
);
|
||||
|
||||
/* COMBINED CSS URL RESOLVER MODIFIED AN ARBITRARY VALUE */
|
||||
$this->assertStringContainsString(
|
||||
".weird { content: \"./keepme.gif\"; }",
|
||||
$content,
|
||||
'combined css url resolver modified an arbitrary value'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup new backend
|
||||
*
|
||||
|
@ -0,0 +1,26 @@
|
||||
.url { background: url(http://example.com/zero.gif); } /* don't touch this */
|
||||
.p0 { background: url(./zero.gif); }
|
||||
.p0-plain { background: url(zero.gif); }
|
||||
.p0-qs { background: url(./zero.gif?some=param); } /* keep the query string */
|
||||
.p0-nl { background: url(
|
||||
./zero-nl.gif );
|
||||
}
|
||||
.p0sq { background: url('./zero-sq.gif'); }
|
||||
.p0dq { background: url("./zero-dq.gif"); }
|
||||
.p0dq-nl { background: url(
|
||||
"./zero-dq-nl.gif"
|
||||
); }
|
||||
.p0dq-nls { background: url(
|
||||
"./zero-dq-nls.gif"
|
||||
); }
|
||||
.p1 { background: url(../one.gif); }
|
||||
.p2 { background: url(../../two.gif); }
|
||||
.p2abs { background: url(/foo/bar/../../two-abs.gif); } /* don't touch this */
|
||||
.p2abs-ln { background: url(
|
||||
/foo/bar/../../two-abs-ln.gif
|
||||
); } /* don't touch this */
|
||||
.p2sq { background: url('../../two-sq.gif'); }
|
||||
.p2dq { background: url("../../two-dq.gif"); }
|
||||
.p3 { background: url(../../../three.gif); }
|
||||
.p4 { background: url(../../../../four.gif); }
|
||||
.weird { content: "./keepme.gif"; }
|
Loading…
Reference in New Issue
Block a user