Merge branch '5' into 6

This commit is contained in:
github-actions 2024-05-22 22:41:41 +00:00
commit 57886b76f5
17 changed files with 149 additions and 87 deletions

View File

@ -1,9 +1,11 @@
name: Dispatch CI
on:
# At 2:20 PM UTC, only on Tuesday and Wednesday
# At 8:40 AM UTC, only on Wednesday and Thursday
schedule:
- cron: '20 14 * * 2,3'
- cron: '40 8 * * 3,4'
permissions: {}
jobs:
dispatch-ci:
@ -11,6 +13,9 @@ jobs:
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
steps:
- name: Dispatch CI
uses: silverstripe/gha-dispatch-ci@v1

View File

@ -1,17 +1,21 @@
name: Keepalive
on:
# At 1:05 PM UTC, on day 22 of the month
# At 5:25 AM UTC, on day 18 of the month
schedule:
- cron: '5 13 22 * *'
- cron: '25 5 18 * *'
workflow_dispatch:
permissions: {}
jobs:
keepalive:
name: Keepalive
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Keepalive
uses: silverstripe/gha-keepalive@v1

View File

@ -1,17 +1,22 @@
name: Merge-up
on:
# At 2:20 PM UTC, only on Saturday
# At 8:40 AM UTC, only on Sunday
schedule:
- cron: '20 14 * * 6'
- cron: '40 8 * * 0'
workflow_dispatch:
permissions: {}
jobs:
merge-up:
name: Merge-up
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
permissions:
contents: write
actions: write
steps:
- name: Merge-up
uses: silverstripe/gha-merge-up@v1

View File

@ -1,15 +0,0 @@
# Contributing
Any open source product is only as good as the community behind it. You can participate by sharing code, ideas, or simply helping others. No matter what your skill level is, every contribution counts.
See our [high level overview](http://silverstripe.org/contributing-to-silverstripe) on silverstripe.org on how you can help out.
## Copyright
**IMPORTANT: By supplying code to the SilverStripe core team in patches, tickets and pull requests, you agree to assign copyright of that code to SilverStripe Limited, on the condition that SilverStripe Limited releases that code under the BSD license.**
We ask for this so that the ownership in the license is clear and unambiguous, and so that community involvement doesn't stop us from being able to continue supporting these projects. By releasing this code under a permissive license, this copyright assignment won't prevent you from using the code in any way you see fit.
## Contributing code
See [contributing code](https://docs.silverstripe.org/en/4/contributing/code/)

View File

@ -1,39 +0,0 @@
# Support
The issue tracker is reserved for reporting bugs and we don't provide support through it. Support tickets opened via the issue tracker will be closed.
If you require support there are two types of support available: community support and commercial support.
## Community support
Community support is provided by members of our open source community and is provided out of their good will. Please be kind, polite and corteous to those that try to help you; you may wish to refer to our [code of conduct](https://docs.silverstripe.org/en/4/contributing/code_of_conduct/).
You can find support via:
- [Stack Overflow (remember to tag as silverstripe)](http://stackoverflow.com/questions/tagged/silverstripe)
- [Slack](https://silverstripe.org/slack)
- [Forum](https://forum.silverstripe.org)
We recommend choosing the support forum based on the nature of your question:
### Do you have some code you've tried that isn't working?
Your question will likely be best on Stack Overflow. You can add some detail about what you're trying to achieve and include some code that you have tried. Stack Overflow is a good forum for answers to be detailed and become available in the future for other developers in your position. Remember to [follow the guidance on Stack Overflow](https://stackoverflow.com/help/how-to-ask) for asking good questions!
### Are you looking for an answer to a quick question or a recommendation for an approach or suitable SilverStripe module?
The SilverStripe community Slack has a wealth of active SilverStripe developers active daily. Remember that those who contribute usually do so in their free time so be considerate when messaging people directly. Consider if your issue might benefit another developer before choosing Slack. An alternate forum like StackOverflow may help other developers that are experiencing the same issue find a resolution.
### Are you looking for detailed feedback on a suggestion or module you have developed?
Try starting a discussion in the community forum. Developers and users alike use the forum to keep up to date with the SilverStripe community and contributions that people are making or discussing.
### More information
For more information please see our [community page](http://www.silverstripe.org/community/#introduction).
## Commercial support
If you require a higher level of support with an SLA, you may want to consider commercial support provided by SilverStripe Ltd, a Professional Partner or another SilverStripe agency.
Please see the [SilverStripe Developer Directory](https://www.silverstripe.org/community/developer-and-partner-directory/) for more information.

3
sake
View File

@ -9,7 +9,8 @@ Executes a SilverStripe command"
exit 1
fi
if ! [ -x "$(command -v which)" ]; then
command -v which >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Error: sake requires the 'which' command to operate." >&2
exit 1
fi

View File

@ -7,6 +7,7 @@ use RuntimeException;
use SilverStripe\Forms\Tab;
use SilverStripe\Forms\TabSet;
use SilverStripe\ORM\ArrayList;
use SilverStripe\Dev\Deprecation;
/**
* A list designed to hold form field instances.
@ -211,6 +212,10 @@ class FieldList extends ArrayList
*/
public function addFieldsToTab(string $tabName, array $fields, ?string $insertBefore = null): static
{
if (!is_array($fields)) {
Deprecation::notice('5.3.0', '$fields will need to be passed as an array in CMS 6', Deprecation::SCOPE_METHOD);
}
$this->flushFieldsCache();
// Find the tab
@ -256,6 +261,10 @@ class FieldList extends ArrayList
*/
public function removeFieldsFromTab(string $tabName, array $fields): static
{
if (!is_array($fields)) {
Deprecation::notice('5.3.0', '$fields will need to be passed as an array in CMS 6', Deprecation::SCOPE_METHOD);
}
$this->flushFieldsCache();
// Find the tab

View File

@ -276,9 +276,18 @@ class GridField extends FormField
}
}
// If the edit button has been removed, replace it with a view button
// If the edit button has been removed, replace it with a view button if one is allowed
if ($hadEditButton && !$copyConfig->getComponentByType(GridFieldViewButton::class)) {
$copyConfig->addComponent(GridFieldViewButton::create());
$viewButtonClass = null;
foreach ($allowedComponents as $componentClass) {
if (is_a($componentClass, GridFieldViewButton::class, true)) {
$viewButtonClass = $componentClass;
break;
}
}
if ($viewButtonClass) {
$copyConfig->addComponent($viewButtonClass::create());
}
}
$copy->extend('afterPerformReadonlyTransformation', $this);

View File

@ -192,7 +192,19 @@ class HTMLEditorField extends TextareaField
*/
public function ValueEntities()
{
return htmlentities($this->Value() ?? '', ENT_COMPAT, 'UTF-8', false);
$entities = get_html_translation_table(HTML_ENTITIES);
foreach ($entities as $key => $value) {
$entities[$key] = "/" . $value . "/";
}
$value = preg_replace_callback($entities, function ($matches) {
// Don't apply double encoding to ampersand
$doubleEncoding = $matches[0] != '&';
return htmlentities($matches[0], ENT_COMPAT, 'UTF-8', $doubleEncoding);
}, $this->Value() ?? '');
return $value;
}
/**

View File

@ -1031,13 +1031,13 @@ class SSTemplateParser extends Parser implements TemplateParser
function ClosedBlock_Handle_Loop(&$res)
{
if ($res['ArgumentCount'] > 1) {
throw new SSTemplateParseException('Either no or too many arguments in control block. Must be one ' .
'argument only.', $this);
throw new SSTemplateParseException('Too many arguments in control block. Must be one or no' .
'arguments only.', $this);
}
//loop without arguments loops on the current scope
if ($res['ArgumentCount'] == 0) {
$on = '$scope->obj(\'Up\', null)->obj(\'Foo\', null)';
$on = '$scope->locally()->obj(\'Me\', null, true)';
} else { //loop in the normal way
$arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') {

View File

@ -1886,6 +1886,8 @@ class SSTemplateParser extends Parser implements TemplateParser
$res['php'] .= '((bool)'.$sub['php'].')';
} else {
$php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']);
// TODO: kinda hacky - maybe we need a way to pass state down the parse chain so
// Lookup_LastLookupStep and Argument_BareWord can produce hasValue instead of XML_val
$res['php'] .= str_replace('$$FINAL', 'hasValue', $php ?? '');
}
}
@ -4257,13 +4259,13 @@ class SSTemplateParser extends Parser implements TemplateParser
function ClosedBlock_Handle_Loop(&$res)
{
if ($res['ArgumentCount'] > 1) {
throw new SSTemplateParseException('Either no or too many arguments in control block. Must be one ' .
'argument only.', $this);
throw new SSTemplateParseException('Too many arguments in control block. Must be one or no' .
'arguments only.', $this);
}
//loop without arguments loops on the current scope
if ($res['ArgumentCount'] == 0) {
$on = '$scope->obj(\'Up\', null)->obj(\'Foo\', null)';
$on = '$scope->locally()->obj(\'Me\', null, true)';
} else { //loop in the normal way
$arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') {
@ -5290,6 +5292,8 @@ class SSTemplateParser extends Parser implements TemplateParser
$text = stripslashes($text ?? '');
$text = addcslashes($text ?? '', '\'\\');
// TODO: This is pretty ugly & gets applied on all files not just html. I wonder if we can make this
// non-dynamically calculated
$code = <<<'EOC'
(\SilverStripe\View\SSViewer::getRewriteHashLinksDefault()
? \SilverStripe\Core\Convert::raw2att( preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI'] ) )
@ -5328,7 +5332,8 @@ EOC;
$this->includeDebuggingComments = $includeDebuggingComments;
// Ignore UTF8 BOM at beginning of string.
// Ignore UTF8 BOM at beginning of string. TODO: Confirm this is needed, make sure SSViewer handles UTF
// (and other encodings) properly
if (substr($string ?? '', 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf)) {
$this->pos = 3;
}

View File

@ -171,6 +171,10 @@ class EmbedShortcodeProvider implements ShortcodeHandler
$arguments['style'] = 'width: ' . intval($arguments['width']) . 'px;';
}
if (!empty($arguments['caption'])) {
$arguments['caption'] = htmlentities($arguments['caption'], ENT_QUOTES, 'UTF-8', false);
}
// override iframe dimension attributes provided by webservice with ones specified in shortcode arguments
foreach (['width', 'height'] as $attr) {
if (!($value = $arguments[$attr] ?? false)) {

View File

@ -3,6 +3,6 @@
>
{$Content}
<% if $Arguments.caption %>
<p class="caption">{$Arguments.caption}</p>
<p class="caption">{$Arguments.caption.RAW}</p>
<% end_if %>
</div>

View File

@ -20,6 +20,8 @@ use SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader;
use SilverStripe\Forms\Tests\GridField\GridFieldTest\Team;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldViewButton;
use SilverStripe\Forms\Tests\GridField\GridFieldReadonlyTest\GridFieldViewButtonReplacement;
use SilverStripe\Versioned\VersionedGridFieldState\VersionedGridFieldState;
class GridFieldReadonlyTest extends SapphireTest
@ -31,11 +33,28 @@ class GridFieldReadonlyTest extends SapphireTest
Cheerleader::class,
];
public function provideReadOnlyTransformation(): array
{
return [
[
'viewButtonClass' => null,
],
[
'viewButtonClass' => GridFieldViewButton::class,
],
[
'viewButtonClass' => GridFieldViewButtonReplacement::class,
],
];
}
/**
* The CMS can set the value of a GridField to be a hasMany relation, which needs a readonly state.
* This test ensures GridField has a readonly transformation.
*
* @dataProvider provideReadOnlyTransformation
*/
public function testReadOnlyTransformation()
public function testReadOnlyTransformation(?string $viewButtonClass)
{
// Build a hasMany Relation via getComponents like ModelAdmin does.
$components = Team::get_one(Team::class)
@ -67,6 +86,18 @@ class GridFieldReadonlyTest extends SapphireTest
$gridConfig
);
if ($viewButtonClass !== GridFieldViewButton::class) {
$allowedComponents = $gridField->getReadonlyComponents();
$viewButtonIndex = array_search(GridFieldViewButton::class, $allowedComponents);
if ($viewButtonIndex !== false) {
unset($allowedComponents[$viewButtonIndex]);
}
if ($viewButtonClass !== null) {
$allowedComponents[] = $viewButtonClass;
}
$gridField->setReadonlyComponents($allowedComponents);
}
// Model Admin sets the value of the GridField directly to the relation, which doesn't have a forTemplate()
// function, if we rely on FormField to render into a ReadonlyField we'll get an error as HasManyRelation
// doesn't have a forTemplate() function.

View File

@ -0,0 +1,11 @@
<?php
namespace SilverStripe\Forms\Tests\GridField\GridFieldReadonlyTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Forms\GridField\GridFieldViewButton;
class GridFieldViewButtonReplacement extends GridFieldViewButton implements TestOnly
{
}

View File

@ -75,7 +75,7 @@ class HTMLEditorFieldTest extends FunctionalTest
$inputText = "These are some unicodes: ä, ö, & ü";
$field = new HTMLEditorField("Test", "Test");
$field->setValue($inputText);
$this->assertStringContainsString('These are some unicodes: &auml;, &ouml;, &amp; &uuml;', $field->Field());
$this->assertStringContainsString('These are some unicodes: ä, ö, & ü', $field->Field());
// Test shortcodes
$inputText = "Shortcode: [file_link id=4]";
$field = new HTMLEditorField("Test", "Test");
@ -211,23 +211,34 @@ EOS
);
}
public function testValueEntities()
public function provideTestValueEntities()
{
return [
"ampersand" => [
"The company &amp; partners",
"The company &amp; partners"
],
"double ampersand" => [
"The company &amp;amp; partners",
"The company &amp;amp; partners"
],
"left arrow and right arrow" => [
"<p>&lt;strong&gt;The company &amp;amp; partners&lt;/strong&gt;</p>",
"<p>&amp;lt;strong&amp;gt;The company &amp;amp; partners&amp;lt;/strong&amp;gt;</p>"
],
];
}
/**
* @dataProvider provideTestValueEntities
*/
public function testValueEntities(string $input, string $result)
{
$inputText = "The company &amp; partners";
$field = new HTMLEditorField("Content");
$field->setValue($inputText);
$field->setValue($input);
$this->assertEquals(
"The company &amp; partners",
$field->obj('ValueEntities')->forTemplate()
);
$inputText = "The company &amp;&amp; partners";
$field = new HTMLEditorField("Content");
$field->setValue($inputText);
$this->assertEquals(
"The company &amp;&amp; partners",
$result,
$field->obj('ValueEntities')->forTemplate()
);
}

View File

@ -500,6 +500,15 @@ SS;
);
}
public function testCurrentScopeLoop(): void
{
$data = new ArrayList([['Val' => 'one'], ['Val' => 'two'], ['Val' => 'three']]);
$this->assertEqualIgnoringWhitespace(
'one two three',
$this->render('<% loop %>$Val<% end_loop %>', $data)
);
}
public function testCurrentScopeLoopWith()
{
// Data to run the loop tests on - one sequence of three items, each with a subitem