Add support for "soft" components (#4539)

This commit is contained in:
Dan Harrin 2020-04-04 18:02:43 +01:00 committed by GitHub
parent c7ba577fae
commit 903b5b01ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 301 additions and 45 deletions

View File

@ -134,6 +134,15 @@ div.control-componentlist div.components div.layout-cell.error-component > div {
div.control-componentlist div.components div.layout-cell.error-component > div:after {
color: #ab2a1c;
}
div.control-componentlist div.components div.layout-cell.warning-component {
background: #ffc107;
}
div.control-componentlist div.components div.layout-cell.warning-component > div {
color: #343a40;
}
div.control-componentlist div.components div.layout-cell.warning-component > div:after {
color: #ffc107;
}
div.control-componentlist div.components div.layout-cell:first-child {
border-bottom-left-radius: 3px;
border-top-left-radius: 3px;

View File

@ -436,7 +436,7 @@
*/
var editor = $('[data-control=codeeditor]', pane)
if (editor.length) {
var alias = $('input[name="component_aliases[]"]', component).val(),
var alias = $('input[name="component_aliases[]"]', component).val().replace(/^@/, ''),
codeEditor = editor.codeEditor('getEditorObject')
codeEditor.replace('', {
@ -715,4 +715,4 @@
}
$.oc.cmsPage = new CmsPage();
}(window.jQuery);
}(window.jQuery);

View File

@ -13,6 +13,8 @@
@color-group-bg: #f1f3f4;
@color-error-component-bg: #ab2a1c;
@color-error-component-text: #ffffff;
@color-warning-component-bg: #ffc107;
@color-warning-component-text: #343a40;
.component-lego-icon() {
position: absolute;
@ -139,6 +141,18 @@ div.control-componentlist {
}
}
&.warning-component {
background: @color-warning-component-bg;
> div {
color: @color-warning-component-text;
&:after {
color: @color-warning-component-bg;
}
}
}
&:first-child {
.border-left-radius(3px);
}

View File

@ -27,7 +27,7 @@ class ComponentHelpers
'title' => Lang::get('cms::lang.component.alias'),
'description' => Lang::get('cms::lang.component.alias_description'),
'type' => 'string',
'validationPattern' => '^[a-zA-Z]+[0-9a-z\_]*$',
'validationPattern' => '^(@)?[a-zA-Z]+[0-9a-z\_]*$',
'validationMessage' => Lang::get('cms::lang.component.validation_message'),
'required' => true,
'showExternalParam' => false

View File

@ -187,32 +187,39 @@ class ComponentManager
/**
* Makes a component object with properties set.
*
* @param string $name A component class name or code.
* @param CmsObject $cmsObject The Cms object that spawned this component.
* @param array $properties The properties set by the Page or Layout.
* @param bool $isSoftComponent Defines if this is a soft component.
*
* @return ComponentBase The component object.
* @throws SystemException If the (hard) component cannot be found or is not registered.
*/
public function makeComponent($name, $cmsObject = null, $properties = [])
public function makeComponent($name, $cmsObject = null, $properties = [], $isSoftComponent = false)
{
$className = $this->resolve($name);
if (!$className) {
$className = $this->resolve(ltrim($name, '@'));
if (!$className && !$isSoftComponent) {
throw new SystemException(sprintf(
'Class name is not registered for the component "%s". Check the component plugin.',
$name
));
}
if (!class_exists($className)) {
if (!class_exists($className) && !$isSoftComponent) {
throw new SystemException(sprintf(
'Component class not found "%s". Check the component plugin.',
$className
));
}
$component = App::make($className, [$cmsObject, $properties]);
$component->name = $name;
if (class_exists($className)) {
$component = App::make($className, [$cmsObject, $properties]);
$component->name = $name;
return $component;
return $component;
}
}
/**

View File

@ -11,6 +11,7 @@ use Session;
use Request;
use Response;
use Exception;
use SystemException;
use BackendAuth;
use Twig\Environment as TwigEnvironment;
use Twig\Cache\FilesystemCache as TwigCacheFilesystem;
@ -24,7 +25,6 @@ use System\Classes\CombineAssets;
use System\Twig\Extension as SystemTwigExtension;
use October\Rain\Exception\AjaxException;
use October\Rain\Exception\ValidationException;
use October\Rain\Exception\ApplicationException;
use October\Rain\Parse\Bracket as TextParser;
use Illuminate\Http\RedirectResponse;
@ -1403,36 +1403,56 @@ class Controller
//
/**
* Adds a component to the page object
* @param mixed $name Component class name or short name
* @param string $alias Alias to give the component
* @param array $properties Component properties
* @param bool $addToLayout Add to layout, instead of page
* @return ComponentBase Component object
* Adds a component to the page object.
*
* @param mixed $name Component class name or short name
* @param string $alias Alias to give the component
* @param array $properties Component properties
* @param bool $addToLayout Add to layout, instead of page
*
* @return ComponentBase|null Component object. Will return `null` if a soft component is used but not found.
*
* @throws CmsException if the (hard) component is not found.
* @throws SystemException if the (hard) component class is not found or is not registered.
*/
public function addComponent($name, $alias, $properties, $addToLayout = false)
{
$manager = ComponentManager::instance();
$isSoftComponent = $this->isSoftComponent($name);
if ($isSoftComponent) {
$name = $this->parseComponentLabel($name);
$alias = $this->parseComponentLabel($alias);
}
$componentObj = $manager->makeComponent(
$name,
($addToLayout) ? $this->layoutObj : $this->pageObj,
$properties,
$isSoftComponent
);
if (is_null($componentObj)) {
if (!$isSoftComponent) {
throw new CmsException(Lang::get('cms::lang.component.not_found', ['name' => $name]));
}
// A missing soft component will return null.
return null;
}
$componentObj->alias = $alias;
$this->vars[$alias] = $componentObj;
if ($addToLayout) {
if (!$componentObj = $manager->makeComponent($name, $this->layoutObj, $properties)) {
throw new CmsException(Lang::get('cms::lang.component.not_found', ['name'=>$name]));
}
$componentObj->alias = $alias;
$this->vars[$alias] = $this->layout->components[$alias] = $componentObj;
}
else {
if (!$componentObj = $manager->makeComponent($name, $this->pageObj, $properties)) {
throw new CmsException(Lang::get('cms::lang.component.not_found', ['name'=>$name]));
}
$componentObj->alias = $alias;
$this->vars[$alias] = $this->page->components[$alias] = $componentObj;
$this->layout->components[$alias] = $componentObj;
} else {
$this->page->components[$alias] = $componentObj;
}
$this->setComponentPropertiesFromParams($componentObj);
$componentObj->init();
return $componentObj;
}
@ -1546,4 +1566,27 @@ class Controller
}
}
}
/**
* Removes prefixed '@' from soft component name
* @param string $label
* @return string
*/
protected function parseComponentLabel($label)
{
if ($this->isSoftComponent($label)) {
return ltrim($label, '@');
}
return $label;
}
/**
* Checks if component name has @.
* @param string $label
* @return bool
*/
protected function isSoftComponent($label)
{
return starts_with($label, '@');
}
}

View File

@ -0,0 +1,33 @@
<?php namespace Cms\Components;
use Cms\Classes\ComponentBase;
class SoftComponent extends ComponentBase
{
/**
* @var string Message that is shown with this component.
*/
protected $message;
/**
* @inheritDoc
*/
public function __construct($properties)
{
$this->componentCssClass = 'warning-component';
$this->inspectorEnabled = false;
parent::__construct(null, $properties);
}
/**
* @return array
*/
public function componentDetails()
{
return [
'name' => 'cms::lang.component.soft_component',
'description' => 'cms::lang.component.soft_component_description'
];
}
}

View File

@ -191,7 +191,7 @@ class Index extends Controller
$templateData = [];
$settings = array_get($saveData, 'settings', []) + Request::input('settings', []);
$settings = $this->upgradeSettings($settings);
$settings = $this->upgradeSettings($settings, $template->settings);
if ($settings) {
$templateData['settings'] = $settings;
@ -556,7 +556,7 @@ class Index extends Controller
}
/**
* Reolves a template type to its class name
* Resolves a template type to its class name
* @param string $type
* @return string
*/
@ -687,11 +687,12 @@ class Index extends Controller
}
/**
* Processes the component settings so they are ready to be saved
* @param array $settings
* Processes the component settings so they are ready to be saved.
* @param array $settings The new settings for this template.
* @param array $prevSettings The previous settings for this template.
* @return array
*/
protected function upgradeSettings($settings)
protected function upgradeSettings($settings, $prevSettings)
{
/*
* Handle component usage
@ -714,14 +715,34 @@ class Index extends Controller
$componentName = $componentNames[$index];
$componentAlias = $componentAliases[$index];
$section = $componentName;
if ($componentAlias != $componentName) {
$section .= ' '.$componentAlias;
$isSoftComponent = (substr($componentAlias, 0, 1) === '@');
$componentName = ltrim($componentName, '@');
$componentAlias = ltrim($componentAlias, '@');
if ($componentAlias !== $componentName) {
$section = $componentName . ' ' . $componentAlias;
} else {
$section = $componentName;
}
if ($isSoftComponent) {
$section = '@' . $section;
}
$properties = json_decode($componentProperties[$index], true);
unset($properties['oc.alias'], $properties['inspectorProperty'], $properties['inspectorClassName']);
$settings[$section] = $properties;
if (!$properties) {
$oldComponentSettings = array_key_exists($section, $prevSettings['components'])
? $prevSettings['components'][$section]
: null;
if ($isSoftComponent && $oldComponentSettings) {
$settings[$section] = $oldComponentSettings;
} else {
$settings[$section] = $properties;
}
} else {
$settings[$section] = $properties;
}
}
}
@ -756,6 +777,35 @@ class Index extends Controller
return $dataHolder->settings;
}
/**
* Finds a given component by its alias.
*
* If found, this will return the component's name, alias and properties.
*
* @param string $aliasQuery The alias to search for
* @param array $components The array of components to look within.
* @return array|null
*/
protected function findComponentByAlias(string $aliasQuery, array $components = [])
{
$found = null;
foreach ($components as $name => $properties) {
list($name, $alias) = strpos($name, ' ') ? explode(' ', $name) : [$name, $name];
if (ltrim($alias, '@') === ltrim($aliasQuery, '@')) {
$found = [
'name' => ltrim($name, '@'),
'alias' => $alias,
'properties' => $properties
];
break;
}
}
return $found;
}
/**
* Binds the active form widget to the controller
* @return void

View File

@ -3,6 +3,7 @@
use Backend\Classes\FormWidgetBase;
use Cms\Classes\ComponentManager;
use Cms\Classes\ComponentHelpers;
use Cms\Components\SoftComponent;
use Cms\Components\UnknownComponent;
use Exception;
@ -41,8 +42,7 @@ class Components extends FormWidgetBase
try {
$componentObj = $manager->makeComponent($name, null, $properties);
$componentObj->alias = $alias;
$componentObj->alias = ((starts_with($name, '@') && $alias !== $name) ? '@' : '') . $alias;
$componentObj->pluginIcon = 'icon-puzzle-piece';
/*
@ -57,9 +57,16 @@ class Components extends FormWidgetBase
}
}
catch (Exception $ex) {
$componentObj = new UnknownComponent(null, $properties, $ex->getMessage());
$componentObj->alias = $alias;
$componentObj->pluginIcon = 'icon-bug';
if (starts_with($name, '@')) {
$componentObj = new SoftComponent($properties);
$componentObj->name = $name;
$componentObj->alias = (($alias !== $name) ? '@' : '') . $alias;
$componentObj->pluginIcon = 'icon-flag';
} else {
$componentObj = new UnknownComponent(null, $properties, $ex->getMessage());
$componentObj->alias = $alias;
$componentObj->pluginIcon = 'icon-bug';
}
}
$result[] = $componentObj;

View File

@ -250,6 +250,8 @@ return [
'no_records' => 'No components found',
'not_found' => "The component ':name' is not found.",
'method_not_found' => "The component ':name' does not contain a method ':method'.",
'soft_component' => 'Soft Component',
'soft_component_description' => 'This component is missing but optional.',
],
'template' => [
'invalid_type' => 'Unknown template type.',

View File

@ -0,0 +1,5 @@
url = "/no-soft-component-class"
[@PeterPan\Nevernever\Land noComponentExist]
==
<p>Hey</p>

View File

@ -0,0 +1,11 @@
url = "/with-soft-component-class-alias"
layout = "content"
[@testArchive someAlias]
posts-per-page = "69"
==
<p>This page uses components.</p>
{% for post in someAlias.posts %}
<h3>{{ post.title }}</h3>
<p>{{ post.content }}</p>
{% endfor %}

View File

@ -0,0 +1,11 @@
url = "/with-soft-component-class"
layout = "content"
[@testArchive]
posts-per-page = "69"
==
<p>This page uses components.</p>
{% for post in testArchive.posts %}
<h3>{{ post.title }}</h3>
<p>{{ post.content }}</p>
{% endfor %}

View File

@ -78,6 +78,7 @@ class CmsObjectQueryTest extends TestCase
"no-component-class",
"no-layout",
"no-partial",
"no-soft-component-class",
"optional-full-php-tags",
"optional-short-php-tags",
"throw-php",
@ -87,6 +88,8 @@ class CmsObjectQueryTest extends TestCase
"with-layout",
"with-partials",
"with-placeholder",
"with-soft-component-class",
"with-soft-component-class-alias",
], $pages);
$layouts = Layout::lists('baseFileName');

View File

@ -381,6 +381,67 @@ ESC;
$response = $controller->run('/no-component-class')->getContent();
}
public function testSoftComponentClassNotFound()
{
$theme = Theme::load('test');
$controller = new Controller($theme);
$response = $controller->run('/no-soft-component-class')->getContent();
$this->assertEquals('<p>Hey</p>', $response);
}
public function testSoftComponentClassFound()
{
$theme = Theme::load('test');
$controller = new Controller($theme);
$response = $controller->run('/with-soft-component-class')->getContent();
$page = $this->readAttribute($controller, 'page');
$this->assertArrayHasKey('testArchive', $page->components);
$component = $page->components['testArchive'];
$details = $component->componentDetails();
$content = <<<ESC
<div>LAYOUT CONTENT<p>This page uses components.</p>
<h3>Lorum ipsum</h3>
<p>Post Content #1</p>
<h3>La Playa Nudista</h3>
<p>Second Post Content</p>
</div>
ESC;
$this->assertEquals($content, $response);
$this->assertEquals(69, $component->property('posts-per-page'));
$this->assertEquals('Blog Archive Dummy Component', $details['name']);
$this->assertEquals('Displays an archive of blog posts.', $details['description']);
}
public function testSoftComponentWithAliasClassFound()
{
$theme = Theme::load('test');
$controller = new Controller($theme);
$response = $controller->run('/with-soft-component-class-alias')->getContent();
$page = $this->readAttribute($controller, 'page');
$this->assertArrayHasKey('someAlias', $page->components);
$component = $page->components['someAlias'];
$details = $component->componentDetails();
$content = <<<ESC
<div>LAYOUT CONTENT<p>This page uses components.</p>
<h3>Lorum ipsum</h3>
<p>Post Content #1</p>
<h3>La Playa Nudista</h3>
<p>Second Post Content</p>
</div>
ESC;
$this->assertEquals($content, $response);
$this->assertEquals(69, $component->property('posts-per-page'));
$this->assertEquals('Blog Archive Dummy Component', $details['name']);
$this->assertEquals('Displays an archive of blog posts.', $details['description']);
}
public function testComponentNotFound()
{
//