diff --git a/packages/admin/README.md b/packages/admin/README.md index 02224805a..1ccb5b5a8 100644 --- a/packages/admin/README.md +++ b/packages/admin/README.md @@ -314,12 +314,12 @@ Usage example : "pattern": "title", "choice_obj_map": { "value": "ident", - "label": "{{customLabelFunction}} - {{someAdditionalInfo }}" + "label": "{{ customLabelFunction }} - {{ someAdditionalInfo }}" }, "selectize_templates": { - "item": "project/selectize/custom-item-template", - "option": "project/selectize/custom-option-template", - "controller": "project/selectize/custom-template" + "item": "{{> project/selectize/custom-item-template }}", + "option": "{{> project/selectize/custom-option-template }}", + "controller": "{{> project/selectize/custom-template }}" }, "selectize_options": { "plugins": { @@ -339,26 +339,26 @@ Selectize templates examples :
 "selectize_templates": {
-    "item": "{{customLabelFunction}} - {{someAdditionalInfo }}",
-    "option": "{{customLabelFunction}} - {{someAdditionalInfo }}"
+    "item": "{{ customLabelFunction }} - {{ someAdditionalInfo }}",
+    "option": "{{ customLabelFunction }} - {{ someAdditionalInfo }}"
 },
 
 ---
 
-"selectize_templates": "{{customLabelFunction}} - {{someAdditionalInfo }}",
+"selectize_templates": "{{ customLabelFunction }} - {{ someAdditionalInfo }}",
 
 ---
 
-"selectize_templates": "project/selectize/custom-template",
+"selectize_templates": "{{> project/selectize/custom-template }}",
 
 ---
 
 "selectize_templates": {
-   "item": "project/selectize/custom-item-template",
-   "option": "project/selectize/custom-option-template",
-   "controller": "project/selectize/custom-template",
+   "item": "{{> project/selectize/custom-item-template }}",
+   "option": "{{> project/selectize/custom-option-template }}",
+   "controller": "{{> project/selectize/custom-template }}",
    "data": {
-        "category": "{{selectedCategory}}"
+        "category": "{{ selectedCategory }}"
    }
 },
 
diff --git a/packages/admin/src/Charcoal/Admin/Action/Widget/LoadAction.php b/packages/admin/src/Charcoal/Admin/Action/Widget/LoadAction.php index 8fcf6d9f9..707f0e160 100644 --- a/packages/admin/src/Charcoal/Admin/Action/Widget/LoadAction.php +++ b/packages/admin/src/Charcoal/Admin/Action/Widget/LoadAction.php @@ -151,7 +151,7 @@ public function run(RequestInterface $request, ResponseInterface $response) $widget->setData($widgetOptions); } - $widgetHtml = $widget->renderTemplate($widget->template()); + $widgetHtml = $widget->render($widget->template()); $sharedJs = $widget->renderTemplate('{{&jsRequirements}}'); $uniqueJs = $widget->renderTemplate('{{&js}}'); $widgetId = $widget->widgetId(); diff --git a/packages/admin/src/Charcoal/Admin/AdminModule.php b/packages/admin/src/Charcoal/Admin/AdminModule.php index 3d32be4f8..813f028b0 100644 --- a/packages/admin/src/Charcoal/Admin/AdminModule.php +++ b/packages/admin/src/Charcoal/Admin/AdminModule.php @@ -10,6 +10,7 @@ use Charcoal\App\Module\AbstractModule; // From 'charcoal-admin' use Charcoal\Admin\ServiceProvider\AdminServiceProvider; +use Charcoal\View\ViewAggregator; /** * Charcoal Administration Module @@ -46,6 +47,12 @@ public function setUp() } $container->register(new AdminServiceProvider()); + /* Last resort solution + if ($container['view'] instanceof ViewAggregator) { + $container['view']->using('mustache'); + } + */ + $module = $this; $container['charcoal/admin/module'] = function () use ($module) { return $module; diff --git a/packages/admin/src/Charcoal/Admin/Docs/Template/Object/DocTemplate.php b/packages/admin/src/Charcoal/Admin/Docs/Template/Object/DocTemplate.php index a3f8bdee5..a491e8ddf 100644 --- a/packages/admin/src/Charcoal/Admin/Docs/Template/Object/DocTemplate.php +++ b/packages/admin/src/Charcoal/Admin/Docs/Template/Object/DocTemplate.php @@ -114,7 +114,7 @@ public function title() } if ($this->isObjRenderable($obj)) { - $this->title = $obj->render((string)$objLabel, $obj); + $this->title = $obj->renderTemplate((string)$objLabel, $obj); } else { $this->title = (string)$objLabel; } diff --git a/packages/admin/src/Charcoal/Admin/Property/Display/MessageDisplay.php b/packages/admin/src/Charcoal/Admin/Property/Display/MessageDisplay.php index 2954d1480..1c0df73d4 100644 --- a/packages/admin/src/Charcoal/Admin/Property/Display/MessageDisplay.php +++ b/packages/admin/src/Charcoal/Admin/Property/Display/MessageDisplay.php @@ -4,9 +4,6 @@ // From Pimple use Pimple\Container; -// From 'charcoal-view' -use Charcoal\View\ViewableInterface; -use Charcoal\View\ViewableTrait; // From 'charcoal-translator' use Charcoal\Translator\Translation; // From 'charcoal-admin' @@ -32,11 +29,8 @@ * } * ``` */ -class MessageDisplay extends AbstractPropertyDisplay implements - ViewableInterface +class MessageDisplay extends AbstractPropertyDisplay { - use ViewableTrait; - /** * @var Translation|null */ diff --git a/packages/admin/src/Charcoal/Admin/Property/Display/StatusDisplay.php b/packages/admin/src/Charcoal/Admin/Property/Display/StatusDisplay.php index 819f09a77..094b54f1a 100644 --- a/packages/admin/src/Charcoal/Admin/Property/Display/StatusDisplay.php +++ b/packages/admin/src/Charcoal/Admin/Property/Display/StatusDisplay.php @@ -4,9 +4,6 @@ // from 'charcoal-admin' use Charcoal\Admin\Property\AbstractPropertyDisplay; -// from 'charcoal-view' -use Charcoal\View\ViewableInterface; -use Charcoal\View\ViewableTrait; // from 'pimple' use Pimple\Container; use UnexpectedValueException; @@ -16,10 +13,8 @@ * * The default display for most properties; only output {@see AbstractProperty::displayVal()}. */ -class StatusDisplay extends AbstractPropertyDisplay implements ViewableInterface +class StatusDisplay extends AbstractPropertyDisplay { - use ViewableTrait; - public const STATE_PRIMARY = 'primary'; public const STATE_SUCCESS = 'success'; public const STATE_INFO = 'info'; @@ -68,9 +63,6 @@ class StatusDisplay extends AbstractPropertyDisplay implements ViewableInterface protected function setDependencies(Container $container) { parent::setDependencies($container); - - // Fulfills the ViewableTrait dependencies - $this->setView($container['view']); } /** diff --git a/packages/admin/src/Charcoal/Admin/Property/Input/ReadonlyInput.php b/packages/admin/src/Charcoal/Admin/Property/Input/ReadonlyInput.php index 74b8fa780..2da2d8823 100644 --- a/packages/admin/src/Charcoal/Admin/Property/Input/ReadonlyInput.php +++ b/packages/admin/src/Charcoal/Admin/Property/Input/ReadonlyInput.php @@ -106,7 +106,7 @@ public function displayVal() $display->setData($propertyData); $display->setPropertyVal($propertyValue); - return $this->view()->renderTemplate($displayType, $display); + return $this->view()->render($displayType, $display); } /** diff --git a/packages/admin/src/Charcoal/Admin/Service/SelectizeRenderer.php b/packages/admin/src/Charcoal/Admin/Service/SelectizeRenderer.php index 310cc51a1..24d8b15e4 100644 --- a/packages/admin/src/Charcoal/Admin/Service/SelectizeRenderer.php +++ b/packages/admin/src/Charcoal/Admin/Service/SelectizeRenderer.php @@ -123,6 +123,6 @@ public function renderTemplate($templateIdent, $context, $controllerIdent = null $template = $context; } - return $this->view->render($templateIdent, $template); + return $this->view->renderTemplate($templateIdent, $template); } } diff --git a/packages/admin/src/Charcoal/Admin/ServiceProvider/AdminServiceProvider.php b/packages/admin/src/Charcoal/Admin/ServiceProvider/AdminServiceProvider.php index cc9ad9e20..12aeabaa4 100644 --- a/packages/admin/src/Charcoal/Admin/ServiceProvider/AdminServiceProvider.php +++ b/packages/admin/src/Charcoal/Admin/ServiceProvider/AdminServiceProvider.php @@ -2,51 +2,34 @@ namespace Charcoal\Admin\ServiceProvider; -// From Pimple -use Charcoal\Admin\AssetsConfig; -use Pimple\Container; -use Pimple\ServiceProviderInterface; use Assetic\Asset\AssetReference; -use Charcoal\Attachment\Object\File; -use Charcoal\Factory\GenericResolver; -// from 'kriswallsmith/assetic' use Assetic\AssetManager; -// From PSR-7 -use Psr\Http\Message\UriInterface; -// From Slim -use Slim\Http\Uri; -// From Mustache -use Mustache_LambdaHelper as LambdaHelper; -// From 'charcoal-config' +use Charcoal\Admin\AssetsConfig; +use Charcoal\Admin\Config as AdminConfig; +use Charcoal\Admin\Property\PropertyDisplayInterface; +use Charcoal\Admin\Property\PropertyInputInterface; +use Charcoal\Admin\Service\SelectizeRenderer; +use Charcoal\Admin\Ui\SecondaryMenu\GenericSecondaryMenuGroup; +use Charcoal\Admin\Ui\SecondaryMenu\SecondaryMenuGroupInterface; +use Charcoal\Admin\User; +use Charcoal\Admin\User\AuthToken; +use Charcoal\Attachment\Object\File; use Charcoal\Config\ConfigInterface; use Charcoal\Config\GenericConfig as Config; -// From 'charcoal-factory' +use Charcoal\Email\ServiceProvider\EmailServiceProvider; use Charcoal\Factory\FactoryInterface; -// From 'charcoal-core' +use Charcoal\Factory\GenericFactory as Factory; +use Charcoal\Factory\GenericResolver; use Charcoal\Model\Service\MetadataConfig; -// From 'charcoal-ui' use Charcoal\Ui\ServiceProvider\UiServiceProvider; -// From 'charcoal-email' -use Charcoal\Email\ServiceProvider\EmailServiceProvider; -// From 'charcoal-factory' -use Charcoal\Factory\GenericFactory as Factory; -// From 'charcoal-user' use Charcoal\User\Authenticator; use Charcoal\User\Authorizer; -// From 'charcoal-view' -use Charcoal\View\EngineInterface; -use Charcoal\View\GenericView; use Charcoal\View\ViewConfig; -use Charcoal\View\ViewInterface; -// From 'charcoal-admin' -use Charcoal\Admin\Config as AdminConfig; -use Charcoal\Admin\Property\PropertyInputInterface; -use Charcoal\Admin\Property\PropertyDisplayInterface; -use Charcoal\Admin\Service\SelectizeRenderer; -use Charcoal\Admin\Ui\SecondaryMenu\GenericSecondaryMenuGroup; -use Charcoal\Admin\Ui\SecondaryMenu\SecondaryMenuGroupInterface; -use Charcoal\Admin\User; -use Charcoal\Admin\User\AuthToken; +use Mustache_LambdaHelper as LambdaHelper; +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Psr\Http\Message\UriInterface; +use Slim\Http\Uri; /** * Charcoal Administration Service Provider @@ -174,25 +157,12 @@ protected function registerAdminServices(Container $container) }; } - /** - * Overwrite view instance. - * - * @param GenericView $view The view instance. - * @param Container $container A container instance. - * @return ViewInterface - */ - $container->extend('view', function (GenericView $view, Container $container): ViewInterface { - return new GenericView([ - 'engine' => $container['view/engine/mustache'] - ]); - }); - /** * Extend view/config. * * @param ConfigInterface $viewConfig The view config instance. * @param Container $container A container instance. - * @return ViewInterface + * @return ViewConfig */ $container->extend('view/config', function (ViewConfig $viewConfig, Container $container): ViewConfig { $adminConfig = $container['admin/config']; diff --git a/packages/admin/src/Charcoal/Admin/Template/Object/CollectionTemplate.php b/packages/admin/src/Charcoal/Admin/Template/Object/CollectionTemplate.php index d7e213814..c4bbd52a4 100644 --- a/packages/admin/src/Charcoal/Admin/Template/Object/CollectionTemplate.php +++ b/packages/admin/src/Charcoal/Admin/Template/Object/CollectionTemplate.php @@ -164,7 +164,7 @@ public function title() } if ($listIdent && $hasView) { - $listIdent = $model->render($listIdent); + $listIdent = $model->renderTemplate($listIdent); } if (isset($adminMetadata['lists'][$listIdent]['label'])) { @@ -191,7 +191,7 @@ public function title() } if ($hasView) { - $this->title = $model->render((string)$objLabel, $model); + $this->title = $model->renderTemplate((string)$objLabel); } else { $this->title = (string)$objLabel; } diff --git a/packages/admin/src/Charcoal/Admin/Template/Object/EditTemplate.php b/packages/admin/src/Charcoal/Admin/Template/Object/EditTemplate.php index 9aedc36f5..44225b3fb 100644 --- a/packages/admin/src/Charcoal/Admin/Template/Object/EditTemplate.php +++ b/packages/admin/src/Charcoal/Admin/Template/Object/EditTemplate.php @@ -212,7 +212,7 @@ protected function renderTitle($title) { $obj = $this->obj(); if ($this->isObjRenderable($obj)) { - return $obj->render((string)$title, $obj); + return $obj->renderTemplate((string)$title); } else { return (string)$title; } diff --git a/packages/admin/src/Charcoal/Admin/Ui/ActionContainerTrait.php b/packages/admin/src/Charcoal/Admin/Ui/ActionContainerTrait.php index b0fa09721..b6f1a9c37 100644 --- a/packages/admin/src/Charcoal/Admin/Ui/ActionContainerTrait.php +++ b/packages/admin/src/Charcoal/Admin/Ui/ActionContainerTrait.php @@ -228,7 +228,7 @@ protected function parseActionItem($action, $ident, $renderer = false) } if (isset($action['extraTemplate'])) { - $action['extraTemplate'] = $this->render($action['extraTemplate']); + $action['extraTemplate'] = $this->renderTemplate($action['extraTemplate']); } if (isset($action['dataAttributes']) && is_array($action['dataAttributes'])) { diff --git a/packages/admin/src/Charcoal/Admin/Ui/CollectionContainerTrait.php b/packages/admin/src/Charcoal/Admin/Ui/CollectionContainerTrait.php index f423d22ad..cab7773fd 100644 --- a/packages/admin/src/Charcoal/Admin/Ui/CollectionContainerTrait.php +++ b/packages/admin/src/Charcoal/Admin/Ui/CollectionContainerTrait.php @@ -830,7 +830,7 @@ public function objectRows() $displayType = $this->display->displayType(); $this->display->setPropertyVal($object->propertyValue($propertyIdent)); - $propertyValue = $this->view()->renderTemplate($displayType, $this->display); + $propertyValue = $this->view()->render($displayType, $this->display); $cell = $this->parsePropertyCell($object, $property, $propertyValue); $objectProperties[] = $cell; diff --git a/packages/admin/src/Charcoal/Admin/Ui/NestedWidgetContainerTrait.php b/packages/admin/src/Charcoal/Admin/Ui/NestedWidgetContainerTrait.php index f2b41e989..d30b670be 100644 --- a/packages/admin/src/Charcoal/Admin/Ui/NestedWidgetContainerTrait.php +++ b/packages/admin/src/Charcoal/Admin/Ui/NestedWidgetContainerTrait.php @@ -284,7 +284,7 @@ protected function renderData($data) // Make sure there's an "out" if ($obj instanceof ViewableInterface && ($obj->view() instanceof ViewInterface)) { - $data = $obj->view()->render($data, $obj->viewController()); + $data = $obj->view()->renderTemplate($data, $obj->viewController()); } else { $data = preg_replace_callback('~\{\{\s*(.*?)\s*\}\}~i', [ $this, 'parseDataToken' ], $data); } diff --git a/packages/admin/src/Charcoal/Admin/Widget/CollectionMapWidget.php b/packages/admin/src/Charcoal/Admin/Widget/CollectionMapWidget.php index a8232e52f..404a15a30 100644 --- a/packages/admin/src/Charcoal/Admin/Widget/CollectionMapWidget.php +++ b/packages/admin/src/Charcoal/Admin/Widget/CollectionMapWidget.php @@ -327,7 +327,7 @@ public function dataFromObject() } if ($collectionIdent && $this->isObjRenderable($proto)) { - $collectionIdent = $proto->render($collectionIdent); + $collectionIdent = $proto->renderTemplate($collectionIdent); } if (!$collectionIdent) { diff --git a/packages/admin/src/Charcoal/Admin/Widget/DocWidget.php b/packages/admin/src/Charcoal/Admin/Widget/DocWidget.php index 84a5aca4d..903dd6181 100644 --- a/packages/admin/src/Charcoal/Admin/Widget/DocWidget.php +++ b/packages/admin/src/Charcoal/Admin/Widget/DocWidget.php @@ -275,7 +275,7 @@ public function setNextUrl($url) $obj = $this->obj(); if ($obj && $this->isObjRenderable($obj)) { - $url = $obj->render($url); + $url = $obj->renderTemplate($url); } $this->nextUrl = $url; @@ -572,7 +572,7 @@ protected function dataFromObject() } if ($formIdent && $this->isObjRenderable($obj)) { - $formIdent = $obj->render($formIdent); + $formIdent = $obj->renderTemplate($formIdent); } if (isset($adminMetadata['forms'][$formIdent])) { diff --git a/packages/admin/src/Charcoal/Admin/Widget/FormGroup/NestedWidgetFormGroup.php b/packages/admin/src/Charcoal/Admin/Widget/FormGroup/NestedWidgetFormGroup.php index 00a71b994..74eabf7fd 100644 --- a/packages/admin/src/Charcoal/Admin/Widget/FormGroup/NestedWidgetFormGroup.php +++ b/packages/admin/src/Charcoal/Admin/Widget/FormGroup/NestedWidgetFormGroup.php @@ -9,6 +9,8 @@ use Charcoal\Factory\FactoryInterface; // From 'charcoal-ui' use Charcoal\Ui\FormGroup\AbstractFormGroup; +// From 'charcoal-translator' +use Charcoal\Translator\Translation; // From 'charcoal-admin' use Charcoal\Admin\Ui\NestedWidgetContainerInterface; use Charcoal\Admin\Ui\NestedWidgetContainerTrait; @@ -114,19 +116,26 @@ public function setWidgetId($widgetId) } /** - * @return Translation|string|null + * @param mixed $description The description attribute. + * @return FormGroupWidget Chainable */ - public function description() + public function setDescription($description) { - return $this->renderTemplate((string)parent::description()); + $description = $this->translator()->translate($description); + $description = $this->renderTemplate($description); + return parent::setDescription($description); } + /** - * @return Translation|string|null + * @param mixed $notes The notes attribute. + * @return FormGroupWidget Chainable */ - public function notes() + public function setNotes($notes) { - return $this->renderTemplate((string)parent::notes()); + $notes = $this->translator()->translate($notes); + $notes = $this->renderTemplate($notes); + return parent::setNotes($notes); } /** diff --git a/packages/admin/src/Charcoal/Admin/Widget/FormGroupWidget.php b/packages/admin/src/Charcoal/Admin/Widget/FormGroupWidget.php index 22be8741b..352b297ae 100644 --- a/packages/admin/src/Charcoal/Admin/Widget/FormGroupWidget.php +++ b/packages/admin/src/Charcoal/Admin/Widget/FormGroupWidget.php @@ -289,19 +289,26 @@ public function locale() } /** - * @return Translation|string|null + * @param mixed $description The description attribute. + * @return FormGroupWidget Chainable */ - public function description() + public function setDescription($description) { - return $this->renderTemplate((string)parent::description()); + $description = $this->translator()->translate($description); + $description = $this->renderTemplate($description); + return parent::setDescription($description); } + /** - * @return Translation|string|null + * @param mixed $notes The notes attribute. + * @return FormGroupWidget Chainable */ - public function notes() + public function setNotes($notes) { - return $this->renderTemplate((string)parent::notes()); + $notes = $this->translator()->translate($notes); + $notes = $this->renderTemplate($notes); + return parent::setNotes($notes); } /** diff --git a/packages/admin/src/Charcoal/Admin/Widget/FormPropertyWidget.php b/packages/admin/src/Charcoal/Admin/Widget/FormPropertyWidget.php index e690ff3f1..2ac78a75f 100644 --- a/packages/admin/src/Charcoal/Admin/Widget/FormPropertyWidget.php +++ b/packages/admin/src/Charcoal/Admin/Widget/FormPropertyWidget.php @@ -1163,7 +1163,6 @@ protected function setDependencies(Container $container) { parent::setDependencies($container); - $this->setView($container['view']); $this->setPropertyFactory($container['property/factory']); $this->setPropertyInputFactory($container['property/input/factory']); $this->setPropertyDisplayFactory($container['property/display/factory']); diff --git a/packages/admin/src/Charcoal/Admin/Widget/ObjectFormWidget.php b/packages/admin/src/Charcoal/Admin/Widget/ObjectFormWidget.php index a76dd8068..fa756d60a 100644 --- a/packages/admin/src/Charcoal/Admin/Widget/ObjectFormWidget.php +++ b/packages/admin/src/Charcoal/Admin/Widget/ObjectFormWidget.php @@ -157,7 +157,7 @@ public function setNextUrl($url) $obj = $this->obj(); if ($obj && $this->isObjRenderable($obj)) { - $url = $obj->render($url); + $url = $obj->renderTemplate($url); } $this->nextUrl = $url; @@ -506,7 +506,7 @@ protected function dataFromObject() } if ($formIdent && $this->isObjRenderable($obj)) { - $formIdent = $obj->render($formIdent); + $formIdent = $obj->renderTemplate($formIdent); } if (isset($adminMetadata['forms'][$formIdent])) { diff --git a/packages/admin/src/Charcoal/Admin/Widget/TableWidget.php b/packages/admin/src/Charcoal/Admin/Widget/TableWidget.php index ef0a59fcb..f694492dc 100644 --- a/packages/admin/src/Charcoal/Admin/Widget/TableWidget.php +++ b/packages/admin/src/Charcoal/Admin/Widget/TableWidget.php @@ -253,7 +253,7 @@ public function dataFromObject() } if ($collectionIdent && $this->isObjRenderable($proto)) { - $collectionIdent = $proto->render($collectionIdent); + $collectionIdent = $proto->renderTemplate($collectionIdent); } if (!$collectionIdent) { @@ -880,7 +880,7 @@ public function objectEditUrl() $url = 'object/edit?main_menu={{ main_menu }}&obj_type=' . $this->objType(); if ($this->isObjRenderable($model)) { - $url = $model->render((string)$url); + $url = $model->renderTemplate((string)$url); } else { $url = preg_replace('~{{\s*id\s*}}~', $this->currentObjId, $url); } @@ -901,7 +901,7 @@ public function objectCreateUrl() if (isset($action['url'])) { $model = $this->proto(); if ($this->isObjRenderable($model)) { - $action['url'] = $model->render((string)$action['url']); + $action['url'] = $model->renderTemplate((string)$action['url']); } else { $action['url'] = preg_replace('~{{\s*id\s*}}~', $this->currentObjId, $action['url']); } @@ -1046,7 +1046,6 @@ protected function setDependencies(Container $container) // Satisfies HttpAwareTrait dependencies $this->setHttpRequest($container['request']); - $this->setView($container['view']); $this->setCollectionLoader($container['model/collection/loader']); $this->setWidgetFactory($container['widget/factory']); $this->setPropertyFactory($container['property/factory']); diff --git a/packages/attachment/src/Charcoal/Attachment/Traits/AttachmentContainerTrait.php b/packages/attachment/src/Charcoal/Attachment/Traits/AttachmentContainerTrait.php index d3b6250c3..d0eb50070 100644 --- a/packages/attachment/src/Charcoal/Attachment/Traits/AttachmentContainerTrait.php +++ b/packages/attachment/src/Charcoal/Attachment/Traits/AttachmentContainerTrait.php @@ -117,7 +117,6 @@ public function attachmentGroup() if (!is_string($group)) { throw new UnexpectedValueException('The attachment grouping must be a string.'); } - $this->group = $group; } diff --git a/packages/attachment/src/Charcoal/Attachment/Traits/ConfigurableAttachmentsTrait.php b/packages/attachment/src/Charcoal/Attachment/Traits/ConfigurableAttachmentsTrait.php index f5455b41c..853f47fc5 100644 --- a/packages/attachment/src/Charcoal/Attachment/Traits/ConfigurableAttachmentsTrait.php +++ b/packages/attachment/src/Charcoal/Attachment/Traits/ConfigurableAttachmentsTrait.php @@ -120,10 +120,10 @@ private function mergePresetWidget(array $data) if ($this instanceof ObjectContainerInterface) { if ($this->hasObj()) { - $widgetIdent = $this->obj()->render($widgetIdent); + $widgetIdent = $this->obj()->renderTemplate($widgetIdent); } } elseif ($this instanceof ModelInterface) { - $widgetIdent = $this->render($widgetIdent); + $widgetIdent = $this->renderTemplate($widgetIdent); } $presetWidgets = $this->config('widgets'); @@ -160,10 +160,10 @@ private function mergePresetAttachableObjects($data) $groupIdent = $data; if ($this instanceof ObjectContainerInterface) { if ($this->hasObj()) { - $groupIdent = $this->obj()->render($groupIdent); + $groupIdent = $this->obj()->renderTemplate($groupIdent); } } elseif ($this instanceof ModelInterface) { - $groupIdent = $this->render($groupIdent); + $groupIdent = $this->renderTemplate($groupIdent); } $presetGroups = $this->config('groups'); diff --git a/packages/email/src/Charcoal/Email/ServiceProvider/EmailServiceProvider.php b/packages/email/src/Charcoal/Email/ServiceProvider/EmailServiceProvider.php index bbafb8c37..0db3420c5 100644 --- a/packages/email/src/Charcoal/Email/ServiceProvider/EmailServiceProvider.php +++ b/packages/email/src/Charcoal/Email/ServiceProvider/EmailServiceProvider.php @@ -66,17 +66,28 @@ public function register(Container $container): void ], 'base_class' => EmailInterface::class, 'default_class' => Email::class, - 'arguments' => [[ + 'arguments' => [ $container['email/dependencies'] ], + ]); + }; + + // The model dependencies might be already set from elsewhere; defines it if not. + if (!isset($container['email/dependencies'])) { + /** + * @param Container $container A Pimple DI container. + * @return array The model dependencies array. + */ + $container['email/dependencies'] = function (Container $container) { + return [ 'logger' => $container['logger'], 'config' => $container['email/config'], 'view' => $container['email/view'], 'template_factory' => $container['template/factory'], 'queue_item_factory' => $container['model/factory'], 'log_factory' => $container['model/factory'], - 'tracker' => $container['email/tracker'] - ]] - ]); - }; + 'tracker' => $container['email/tracker'], + ]; + }; + } /** * @return Parser diff --git a/packages/property/src/Charcoal/Property/SpriteProperty.php b/packages/property/src/Charcoal/Property/SpriteProperty.php index 86687c24a..2a4f50521 100644 --- a/packages/property/src/Charcoal/Property/SpriteProperty.php +++ b/packages/property/src/Charcoal/Property/SpriteProperty.php @@ -203,7 +203,6 @@ public function buildChoicesFromSprite() public function displayVal($val, array $options = []) { $val = parent::displayVal($val, $options); - if ($val !== '') { $label = $this->translator()->trans('Selected sprite icon "%icon%"', [ '%icon%' => $val, diff --git a/packages/view/src/Charcoal/View/AbstractEngine.php b/packages/view/src/Charcoal/View/AbstractEngine.php index c20bec027..8566f06a8 100644 --- a/packages/view/src/Charcoal/View/AbstractEngine.php +++ b/packages/view/src/Charcoal/View/AbstractEngine.php @@ -118,7 +118,7 @@ protected function cache() /** * @return LoaderInterface */ - protected function loader(): LoaderInterface + public function loader(): LoaderInterface { return $this->loader; } diff --git a/packages/view/src/Charcoal/View/AbstractLoader.php b/packages/view/src/Charcoal/View/AbstractLoader.php index 4e8152872..3685e3e5e 100644 --- a/packages/view/src/Charcoal/View/AbstractLoader.php +++ b/packages/view/src/Charcoal/View/AbstractLoader.php @@ -13,7 +13,7 @@ abstract class AbstractLoader implements LoaderInterface { private string $basePath = ''; private array $paths = []; - private array $dynamicTemplates = []; + private static array $dynamicTemplates = []; /** * The cache of searched template files. @@ -41,9 +41,7 @@ public function __construct(?array $data = null) public function load($ident) { // Handle dynamic template - if (substr($ident, 0, 1) === '$') { - $ident = $this->dynamicTemplate(substr($ident, 1)); - } + $ident = $this->resolveDynamicTemplate($ident); /** * Prevents the loader from passing a proper template through further @@ -61,17 +59,29 @@ public function load($ident) return file_get_contents($file); } + /** + * @param string $ident The template ident to load and render. + * @return string + */ + public function resolveDynamicTemplate($ident): string + { + if (substr($ident, 0, 1) === '$') { + return $this->dynamicTemplate(substr($ident, 1)); + } + return $ident; + } + /** * @param string $varName The name of the variable to get template ident from. * @return string */ public function dynamicTemplate(string $varName): string { - if (!isset($this->dynamicTemplates[$varName])) { + if (!isset(static::$dynamicTemplates[$varName])) { return ''; } - return $this->dynamicTemplates[$varName]; + return static::$dynamicTemplates[$varName]; } /** @@ -87,7 +97,7 @@ public function setDynamicTemplate(string $varName, ?string $templateIdent): voi return; } - $this->dynamicTemplates[$varName] = $templateIdent; + static::$dynamicTemplates[$varName] = $templateIdent; } /** @@ -96,7 +106,7 @@ public function setDynamicTemplate(string $varName, ?string $templateIdent): voi */ public function removeDynamicTemplate(string $varName): void { - unset($this->dynamicTemplates[$varName]); + unset(static::$dynamicTemplates[$varName]); } /** @@ -104,7 +114,7 @@ public function removeDynamicTemplate(string $varName): void */ public function clearDynamicTemplates(): void { - $this->dynamicTemplates = []; + static::$dynamicTemplates = []; } /** @@ -185,7 +195,7 @@ private function resolvePath(string $path): string * @return boolean Returns TRUE if the given value is most likely the template contents * as opposed to a template identifier (file path). */ - protected function isTemplateString(string $ident): bool + public function isTemplateString(string $ident): bool { return strpos($ident, PHP_EOL) !== false; } @@ -198,7 +208,7 @@ protected function isTemplateString(string $ident): bool * @param string $ident The template identifier to load.. * @return string|null The full path + filename of the found template. NULL if nothing was found. */ - protected function findTemplateFile(string $ident): ?string + public function findTemplateFile(string $ident): ?string { $key = hash('md5', $ident); diff --git a/packages/view/src/Charcoal/View/AbstractView.php b/packages/view/src/Charcoal/View/AbstractView.php index 19d9bf7b2..d67a15726 100644 --- a/packages/view/src/Charcoal/View/AbstractView.php +++ b/packages/view/src/Charcoal/View/AbstractView.php @@ -97,7 +97,7 @@ protected function engine(): EngineInterface * @param EngineInterface $engine The rendering engine. * @return void */ - private function setEngine(EngineInterface $engine): void + protected function setEngine(EngineInterface $engine): void { $this->engine = $engine; } diff --git a/packages/view/src/Charcoal/View/Mustache/MustacheLoader.php b/packages/view/src/Charcoal/View/Mustache/MustacheLoader.php index b2a4ead17..983cfad97 100644 --- a/packages/view/src/Charcoal/View/Mustache/MustacheLoader.php +++ b/packages/view/src/Charcoal/View/Mustache/MustacheLoader.php @@ -29,7 +29,7 @@ class MustacheLoader extends AbstractLoader implements * @param string $ident The template being evaluated. * @return boolean */ - protected function isTemplateString(string $ident): bool + public function isTemplateString(string $ident): bool { return strpos($ident, '{{') !== false || parent::isTemplateString($ident); } diff --git a/packages/view/src/Charcoal/View/Php/PhpEngine.php b/packages/view/src/Charcoal/View/Php/PhpEngine.php index 15fab1eb0..eaab040ae 100644 --- a/packages/view/src/Charcoal/View/Php/PhpEngine.php +++ b/packages/view/src/Charcoal/View/Php/PhpEngine.php @@ -4,8 +4,9 @@ namespace Charcoal\View\Php; -// From 'charcoal-view' use Charcoal\View\AbstractEngine; +use JsonException; +use UnexpectedValueException; /** * PHP view rendering engine @@ -27,7 +28,21 @@ public function type(): string */ public function renderTemplate(string $templateString, $context): string { - $arrayContext = json_decode(json_encode($context), true); + try { + $arrayContext = json_decode(json_encode($context, JSON_THROW_ON_ERROR), true); + } catch (JsonException $e) { + if (strlen($templateString) > 30) { + // Truncate the string to avoid polluting the logs with a long template. + $templateString = substr($templateString, 0, 29) . '…'; + } + + throw new UnexpectedValueException( + sprintf('PHP cannot render template [%s]: %s', $templateString, $e->getMessage()), + $e->getCode(), + $e + ); + } + // Prevents leaking global variable by forcing anonymous scope $render = function ($templateString, array $context) { extract($context); diff --git a/packages/view/src/Charcoal/View/Php/PhpLoader.php b/packages/view/src/Charcoal/View/Php/PhpLoader.php index 13b71a756..fe912d978 100644 --- a/packages/view/src/Charcoal/View/Php/PhpLoader.php +++ b/packages/view/src/Charcoal/View/Php/PhpLoader.php @@ -25,7 +25,7 @@ class PhpLoader extends AbstractLoader implements LoaderInterface * @param string $ident The template being evaluated. * @return boolean */ - protected function isTemplateString(string $ident): bool + public function isTemplateString(string $ident): bool { return strpos($ident, 'getMessage()), + $e->getCode(), + $e + ); + } + return $this->twig()->render($templateIdent, $arrayContext); } @@ -173,9 +184,23 @@ public function render(string $templateIdent, $context): string */ public function renderTemplate(string $templateString, $context): string { - $template = $this->twig()->createTemplate($templateString); - $arrayContext = json_decode(json_encode($context), true); - return $template->render($arrayContext); + try { + $arrayContext = json_decode(json_encode($context, JSON_THROW_ON_ERROR), true); + } catch (JsonException $e) { + if (strlen($templateString) > 30) { + // Truncate the string to avoid polluting the logs with a long template. + $templateString = substr($templateString, 0, 29) . '…'; + } + + throw new UnexpectedValueException( + sprintf('Twig cannot render template [%s]: %s', $templateString, $e->getMessage()), + $e->getCode(), + $e + ); + } + + $templateWrapper = $this->twig()->createTemplate($templateString); + return $templateWrapper->render($arrayContext); } /** diff --git a/packages/view/src/Charcoal/View/Twig/TwigLoader.php b/packages/view/src/Charcoal/View/Twig/TwigLoader.php index 11d3dbb25..af0064eef 100644 --- a/packages/view/src/Charcoal/View/Twig/TwigLoader.php +++ b/packages/view/src/Charcoal/View/Twig/TwigLoader.php @@ -28,7 +28,7 @@ class TwigLoader extends AbstractLoader implements * @param string $ident The template being evaluated. * @return boolean */ - protected function isTemplateString(string $ident): bool + public function isTemplateString(string $ident): bool { return strpos($ident, '{%') !== false || parent::isTemplateString($ident); } diff --git a/packages/view/src/Charcoal/View/ViewAggregator.php b/packages/view/src/Charcoal/View/ViewAggregator.php new file mode 100644 index 000000000..ed9628f5d --- /dev/null +++ b/packages/view/src/Charcoal/View/ViewAggregator.php @@ -0,0 +1,148 @@ + + */ + protected array $engines = []; + + /** + * @var callable(string, mixed, array, EngineInterface): EngineInterface + */ + protected $renderTemplateFileEngineDecider; + + /** + * @var callable(string, mixed, array, EngineInterface): EngineInterface + */ + protected $renderTemplateStringEngineDecider; + + /** + * @param array $data + */ + public function __construct($data) + { + parent::__construct($data); + + $this->setEngines($data['engines']); + $this->renderTemplateFileEngineDecider = $data['file_engine_decider']; + $this->renderTemplateStringEngineDecider = $data['string_engine_decider']; + } + + /** + * @param array $data + * @return void + */ + private function setEngines(array $engines): void + { + foreach ($engines as $ident => $engine) { + if ($engine instanceof EngineInterface) { + $this->engines[$ident] = $engine; + } else { + throw new InvalidArgumentException(sprintf( + 'Expected an instance of %s, received %s', + EngineInterface::class, + (is_object($engine) ? get_class($engine) : gettype($engine)) + )); + } + } + } + + /** + * @return array + */ + private function getEngines(): array + { + return $this->engines; + } + + /** + * Load a template (from identifier) and render it. + * + * @param string $templateIdent The template identifier, to load and render. + * @param mixed $context The view controller (rendering context). + * @return string + */ + public function render(string $templateIdent, $context = null): string + { + $engine = call_user_func($this->renderTemplateFileEngineDecider, $templateIdent, $context, $this->engines, $this->engine()); + return $engine->render($templateIdent, $context); + } + + /** + * Render a template (from string). + * + * @param string $templateString The full template string to render. + * @param mixed $context The view controller (rendering context). + * @return string + */ + public function renderTemplate(string $templateString, $context = null): string + { + $engine = call_user_func($this->renderTemplateStringEngineDecider, $templateString, $context, $this->engines, $this->engine()); + return $engine->renderTemplate($templateString, $context); + } + + /** + * Determine if the given engine is registered. + * + * @param string $ident The engine identifier. + */ + public function has(string $ident): bool + { + return isset($this->engines[$ident]); + } + + /** + * Retrieve the given engine if registered. + * + * @param string $ident The engine identifier. + * @throws InvalidArgumentException If the identifier is not registered. + * @return ViewInterface + */ + public function get(string $ident): ViewInterface + { + if (!$this->has($ident)) { + throw new InvalidArgumentException( + sprintf('Engine [%s] not registered', $ident) + ); + } + + return new GenericView([ + 'engine' => $this->engines[$ident], + ]); + } + + /** + * Change the default engine. + * + * @param string $ident The engine identifier. + * @throws InvalidArgumentException If the identifier is not registered. + * @return self + */ + public function using(string $ident) + { + if (!$this->has($ident)) { + throw new InvalidArgumentException(sprintf( + 'Engine [%s] not registered, must be one of %s', + $ident, + implode(', ', array_keys($this->engines)) + )); + } + + $this->setEngine($this->engines[$ident]); + return $this; + } +} diff --git a/packages/view/src/Charcoal/View/ViewServiceProvider.php b/packages/view/src/Charcoal/View/ViewServiceProvider.php index 78d4c9395..e6b4a0d89 100644 --- a/packages/view/src/Charcoal/View/ViewServiceProvider.php +++ b/packages/view/src/Charcoal/View/ViewServiceProvider.php @@ -22,6 +22,7 @@ use Charcoal\View\Twig\TranslatorHelpers as TwigTranslatorHelpers; use Charcoal\View\Twig\TwigEngine; use Charcoal\View\Twig\TwigLoader; +use Charcoal\View\ViewAggregator; /** * View Service Provider @@ -194,6 +195,20 @@ protected function registerEngineServices(Container $container): void ]); }; + /** + * The view engines. + * + * @param Container $container A container instance. + * @return array + */ + $container['view/engines'] = function (Container $container): array { + return [ + 'mustache' => $container['view/engine/mustache'], + 'php' => $container['view/engine/php'], + 'twig' => $container['view/engine/twig'], + ]; + }; + /** * The default view engine. * @@ -354,8 +369,11 @@ protected function registerViewServices(Container $container) * @return ViewInterface */ $container['view'] = function (Container $container): ViewInterface { - return new GenericView([ - 'engine' => $container['view/engine'] + return new ViewAggregator([ + 'engines' => $container['view/engines'], + 'engine' => $container['view/engine'], + 'file_engine_decider' => $container['view/engine/decider/file'], + 'string_engine_decider' => $container['view/engine/decider/string'], ]); }; @@ -381,5 +399,29 @@ protected function registerViewServices(Container $container) $parsedown->setSafeMode(true); return $parsedown; }; + + $container['view/engine/decider/string'] = $container->protect( + function (string $templateString, $context, array $engines, EngineInterface $defaultEngine): EngineInterface { + foreach ($engines as $engine) { + $string = $engine->loader()->resolveDynamicTemplate($templateString); + if ($engine->loader()->isTemplateString($string)) { + return $engine; + } + } + return $defaultEngine; + } + ); + + $container['view/engine/decider/file'] = $container->protect( + function (string $templateFile, $context, array $engines, EngineInterface $defaultEngine): EngineInterface { + foreach ($engines as $engine) { + $file = $engine->loader()->resolveDynamicTemplate($templateFile); + if ($engine->loader()->findTemplateFile($file)) { + return $engine; + } + } + return $defaultEngine; + } + ); } }