Add support for "soft" components (#4539)
This commit is contained in:
parent
c7ba577fae
commit
903b5b01ea
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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, '@');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
url = "/no-soft-component-class"
|
||||
|
||||
[@PeterPan\Nevernever\Land noComponentExist]
|
||||
==
|
||||
<p>Hey</p>
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
//
|
||||
|
|
|
|||
Loading…
Reference in New Issue