Skip to content

Commit 90c0db1

Browse files
committed
Added viewhelper to import data from another fixture
1 parent 63f8686 commit 90c0db1

File tree

1 file changed

+228
-0
lines changed

1 file changed

+228
-0
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Sitegeist\FluidStyleguide\ViewHelpers\Component;
5+
6+
use Sitegeist\FluidStyleguide\Domain\Model\Component;
7+
use Sitegeist\FluidStyleguide\Domain\Repository\ComponentRepository;
8+
use Sitegeist\FluidStyleguide\Exception\RequiredComponentArgumentException;
9+
use Sitegeist\FluidStyleguide\Service\StyleguideConfigurationManager;
10+
use SMS\FluidComponents\Fluid\ViewHelper\ComponentRenderer;
11+
use TYPO3\CMS\Core\Utility\GeneralUtility;
12+
use TYPO3\CMS\Extbase\Object\ObjectManager;
13+
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
14+
use TYPO3Fluid\Fluid\Core\Variables\StandardVariableProvider;
15+
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
16+
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
17+
18+
class IncludeFixtureViewHelper extends AbstractViewHelper
19+
{
20+
use CompileWithRenderStatic;
21+
22+
/**
23+
* @var boolean
24+
*/
25+
protected $escapeOutput = false;
26+
27+
public function initializeArguments()
28+
{
29+
$this->registerArgument('component', 'string', 'Name of the component that should be rendered', true);
30+
$this->registerArgument('fixtureName', 'string', 'Name of the fixture that the component should be rendered with', false, 'default');
31+
$this->registerArgument('fixtureData', 'array', 'Additional dynamic fixture data that should be used');
32+
}
33+
34+
/**
35+
* Renders fluid example code for the specified component
36+
*
37+
* @param array $arguments
38+
* @param \Closure $renderChildrenClosure
39+
* @param RenderingContextInterface $renderingContext
40+
* @return array
41+
*/
42+
public static function renderStatic(
43+
array $arguments,
44+
\Closure $renderChildrenClosure,
45+
RenderingContextInterface $renderingContext
46+
): array {
47+
48+
$componentIdentifier = self::sanitizeComponentIdentifier($arguments['component'] ?? '');
49+
50+
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
51+
$componentRepository = $objectManager->get(ComponentRepository::class);
52+
53+
$component = $componentRepository->findWithFixturesByIdentifier($componentIdentifier);
54+
if (!$component) {
55+
return sprintf('Component %s not found', $componentIdentifier);
56+
}
57+
58+
if (!isset($arguments['fixtureName']) && !isset($arguments['fixtureData'])) {
59+
throw new \InvalidArgumentException(sprintf(
60+
'A fixture name or fixture data has to be specified to render the component %s.',
61+
$arguments['component']
62+
), 1566377563);
63+
}
64+
65+
$fixtureData = $arguments['fixtureData'] ?? [];
66+
67+
$fixtureName = self::sanitizeFixtureName($arguments['fixtureName'] ?? 'default');
68+
69+
if (isset($fixtureName)) {
70+
$componentFixture = $component->getFixture($fixtureName);
71+
if (!$componentFixture) {
72+
throw new \InvalidArgumentException(sprintf(
73+
'Invalid fixture name "%s" specified for component %s.',
74+
$fixtureName,
75+
$componentIdentifier
76+
), 1566377564);
77+
}
78+
79+
// Merge static fixture data with manually edited data
80+
$fixtureData = array_replace($componentFixture->getData(), $fixtureData);
81+
}
82+
83+
$renderingContext->getViewHelperResolver()->addNamespace('fsv', 'Sitegeist\FluidStyleguide\ViewHelpers');
84+
85+
// Parse fluid code in fixtures
86+
$fixtureData = self::renderFluidInExampleData($fixtureData, $renderingContext);
87+
88+
return $fixtureData;
89+
}
90+
91+
/**
92+
* Make sure that the component identifier doesn't include any malicious characters
93+
*
94+
* @param string $componentIdentifier
95+
* @return string
96+
*/
97+
protected static function sanitizeComponentIdentifier(string $componentIdentifier): string
98+
{
99+
return trim(preg_replace('#[^a-z0-9_\\\\]#i', '', $componentIdentifier), '\\');
100+
}
101+
102+
/**
103+
* Make sure that the fixture name doesn't include any malicious characters
104+
*
105+
* @param string $fixtureName
106+
* @return string
107+
*/
108+
protected static function sanitizeFixtureName(string $fixtureName): string
109+
{
110+
return preg_replace('#[^a-z0-9_]#i', '', $fixtureName);
111+
}
112+
113+
/**
114+
* Calls a component with the supplied example data
115+
*
116+
* @param Component $component
117+
* @param array $data
118+
* @param RenderingContextInterface $renderingContext
119+
* @return string
120+
*/
121+
public static function renderComponent(
122+
Component $component,
123+
array $data,
124+
RenderingContextInterface $renderingContext
125+
): string {
126+
// Check if all required arguments were supplied to the component
127+
foreach ($component->getArguments() as $expectedArgument) {
128+
if ($expectedArgument->isRequired() && !isset($data[$expectedArgument->getName()])) {
129+
throw new RequiredComponentArgumentException(sprintf(
130+
'Required argument "%s" was not supplied for component %s.',
131+
$expectedArgument->getName(),
132+
$component->getName()->getIdentifier()
133+
), 1566636254);
134+
}
135+
}
136+
137+
return ComponentRenderer::renderComponent(
138+
$data,
139+
function () {
140+
return '';
141+
},
142+
$renderingContext,
143+
$component->getName()->getIdentifier()
144+
);
145+
}
146+
147+
/**
148+
* Renders inline fluid code in a fixture array that will be provided as example data to a component
149+
*
150+
* @param mixed $data
151+
* @param RenderingContextInterface $renderingContext
152+
* @return mixed
153+
*/
154+
public static function renderFluidInExampleData($data, RenderingContextInterface $renderingContext)
155+
{
156+
if (is_string($data)) {
157+
return $renderingContext->getTemplateParser()->parse($data)->render($renderingContext);
158+
} elseif (is_array($data)) {
159+
return array_map(function ($value) use ($renderingContext) {
160+
return self::renderFluidInExampleData($value, $renderingContext);
161+
}, $data);
162+
} else {
163+
return $data;
164+
}
165+
}
166+
167+
/**
168+
* Wraps component markup in the specified component context (HTML markup)
169+
* The component markup will replace all pipe characters (|) in the context string
170+
* Optionally, a renderingContext and template data can be provided, in which case
171+
* the context markup will be treated as fluid markup
172+
*
173+
* @param string $componentMarkup
174+
* @param string $context
175+
* @param RenderingContextInterface $renderingContext
176+
* @param array $data
177+
* @return string
178+
*/
179+
public static function applyComponentContext(
180+
string $componentMarkup,
181+
string $context,
182+
RenderingContextInterface $renderingContext = null,
183+
array $data = []
184+
): string {
185+
// Check if the context should be fetched from a file
186+
$context = self::checkObtainComponentContextFromFile($context);
187+
188+
if (isset($renderingContext)) {
189+
// Use unique value as component markup marker
190+
$marker = '###COMPONENT_MARKUP_' . mt_rand() . '###';
191+
$context = str_replace('|', $marker, $context);
192+
193+
// Parse fluid tags in context string
194+
$originalVariableContainer = $renderingContext->getVariableProvider();
195+
$renderingContext->setVariableProvider(new StandardVariableProvider($data));
196+
$context = $renderingContext->getTemplateParser()->parse($context)->render($renderingContext);
197+
$renderingContext->setVariableProvider($originalVariableContainer);
198+
199+
// Wrap component markup
200+
return str_replace($marker, $componentMarkup, $context);
201+
} else {
202+
return str_replace('|', $componentMarkup, $context);
203+
}
204+
}
205+
206+
/**
207+
* Checks if the provided component context is a file path and returns its contents;
208+
* falls back to the specified context string.
209+
*
210+
* @param string $context
211+
* @return string
212+
*/
213+
protected static function checkObtainComponentContextFromFile(string $context): string
214+
{
215+
// Probably not a file path
216+
if (strpos($context, '|') !== false) {
217+
return $context;
218+
}
219+
220+
// Check if the value is a valid file
221+
$path = GeneralUtility::getFileAbsFileName($context);
222+
if (!file_exists($path)) {
223+
return $context;
224+
}
225+
226+
return file_get_contents($path);
227+
}
228+
}

0 commit comments

Comments
 (0)