Skip to content
Snippets Groups Projects
Commit dd4e47f4 authored by berliner's avatar berliner Committed by Jakob P
Browse files

Issue #2810329 by manuel.adan, saniyat, stBorchert, andyg5000, joelpittet,...

Issue #2810329 by manuel.adan, saniyat, stBorchert, andyg5000, joelpittet, berliner: Page Manager paths should support optional arguments as well
parent cc894523
Branches
Tags
No related merge requests found
......@@ -42,6 +42,9 @@ page_manager.page.*:
type:
type: string
label: 'Context type'
optional:
type: boolean
label: 'Optional'
page_manager.page_variant.*:
type: config_entity
......
......@@ -36,32 +36,33 @@ class PageParametersForm extends FormBase {
$this->t('Machine name'),
$this->t('Label'),
$this->t('Type'),
$this->t('Optional'),
$this->t('Operations'),
],
'#rows' => $this->renderRows($cached_values),
'#rows' => $this->renderRows($cached_values, $form, $form_state),
'#empty' => $this->t('There are no parameters defined for this page.'),
];
return $form;
}
protected function renderRows($cached_values) {
protected function renderRows($cached_values, array &$form, FormStateInterface $form_state) {
$rows = [];
/** @var $page \Drupal\page_manager\Entity\Page */
/** @var \Drupal\page_manager\Entity\Page $page */
$page = $cached_values['page'];
/**
* @var string $parameter
*/
foreach ($page->getParameterNames() as $parameter_name) {
$parameter = $page->getParameter($parameter_name);
// Use parameter defaults for new parameters.
if (empty($parameter)) {
$parameter = (array) $page->parameterDefaults();
}
$parameter += ['optional' => FALSE];
$row = [];
$row['machine_name'] = $parameter['machine_name'] ?? '';
if ($label = $parameter['label'] ?? '') {
$row['label'] = $label;
}
else {
$row['type']['colspan'] = 2;
}
$row['type']['data'] = isset($parameter['type']) ?: $this->t('<em>No context assigned</em>');
$row['label'] = $parameter['label'] ?? '';
$row['type']['data'] = $parameter['type'] ?: $this->t('<em>No context assigned</em>');
$row['optional'] = $parameter['optional'] ? $this->t('Optional') : $this->t('Required');
[$route_partial, $route_parameters] = $this->getOperationsRouteInfo($cached_values, $cached_values['id'], $parameter_name);
$build = [
......
......@@ -136,7 +136,7 @@ class ParameterEditForm extends FormBase {
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#default_value' => isset($parameter['label']) ?: ucfirst($parameter['machine_name'] ?? ''),
'#default_value' => !empty($parameter['label']) ? $parameter['label'] : (ucfirst($parameter['machine_name'] ?? '')),
'#states' => [
'invisible' => [
':input[name="type"]' => ['value' => static::NO_CONTEXT_KEY],
......@@ -152,6 +152,12 @@ class ParameterEditForm extends FormBase {
'#default_value' => $parameter['type'] ?? '',
];
$form['optional'] = [
'#type' => 'checkbox',
'#title' => $this->t('Optional'),
'#default_value' => !empty($parameter['optional']),
];
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
......@@ -162,6 +168,36 @@ class ParameterEditForm extends FormBase {
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$cached_values = $this->getTempstore();
/** @var \Drupal\page_manager\PageInterface $page */
$page = $cached_values['page'];
$edited_parameter_name = $form_state->getValue('machine_name');
$edited_parameter_optional = $form_state->getValue('optional');
// Checks that no optional parameter is before a required one.
$required_allowed = TRUE;
foreach ($page->getParameterNames() as $parameter_name) {
$parameter = $page->getParameter($parameter_name);
$parameter_optional = $parameter_name == $edited_parameter_name
? $edited_parameter_optional
: !empty($parameter['optional']);
if ($parameter_optional) {
$required_allowed = FALSE;
}
elseif (!$required_allowed) {
$form_state->setErrorByName('optional', $this->t('Optional path parameters not allowed before required parameters.'));
}
}
parent::validateForm($form, $form_state);
}
/**
* Builds an array of options for the parameter type.
*
......@@ -201,13 +237,14 @@ class ParameterEditForm extends FormBase {
$page = $cache_values['page'];
$name = $form_state->getValue('machine_name');
$type = $form_state->getValue('type');
$optional = $form_state->getValue('optional');
if ($type === static::NO_CONTEXT_KEY) {
$page->removeParameter($name);
$label = NULL;
}
else {
$label = $form_state->getValue('label');
$page->setParameter($name, $type, $label);
$page->setParameter($name, $type, $label, $optional);
}
$this->setTempstore($cache_values);
......
......@@ -47,6 +47,14 @@ class PageParametersTest extends BrowserTestBase {
* Tests page parameters when adding a page and when editing it.
*/
public function testParameters() {
$this->doTestAddParameter();
$this->doTestOptionalParameters();
}
/**
* Tests page parameters when adding a page and when editing it.
*/
public function doTestAddParameter() {
$node = $this->drupalCreateNode(['type' => 'article']);
// Create a page.
......@@ -100,4 +108,64 @@ class PageParametersTest extends BrowserTestBase {
$this->assertSession()->pageTextContains($node->getTitle());
}
/**
* Tests optional parameters.
*
* @param string $path
* The path this step is supposed to be at.
* @param bool|TRUE $redirect
* Whether or not to redirect to the path.
*/
protected function doTestOptionalParameters($path = 'admin/structure/page_manager/manage/foo/general', $redirect = TRUE) {
if ($this->getUrl() !== $path && $redirect) {
$this->drupalGet($path);
}
$this->assertTitle('Page information | Drupal');
$node = $this->drupalCreateNode(['type' => 'article']);
// Add extra parameter.
$edit = [
'path' => 'admin/foo/{node}/{extra}',
];
$this->drupalPostForm(NULL, $edit, 'Update and save');
$this->assertText('The page Foo has been updated.');
$this->drupalGet('admin/structure/page_manager/manage/foo/parameter/edit/extra');
$edit = [
'label' => 'Extra',
'type' => 'string',
'optional' => FALSE,
];
$this->drupalPostForm(NULL, $edit, 'Update parameter');
$this->assertText('The Extra parameter has been updated.');
$this->drupalPostForm(NULL, [], 'Update and save');
$this->assertText('The page Foo has been updated.');
// Check the required extra parameter.
$this->drupalGet('admin/foo/' . $node->id());
$this->assertResponse(404);
$this->drupalGet('admin/foo/' . $node->id() . '/' . $this->randomMachineName());
$this->assertResponse(200);
$this->assertText($node->getTitle());
// Set the extra parameter as optional.
$this->drupalGet('admin/structure/page_manager/manage/foo/parameter/edit/extra');
$edit = [
'optional' => TRUE,
];
$this->drupalPostForm(NULL, $edit, 'Update parameter');
$this->assertText('The Extra parameter has been updated.');
$this->drupalPostForm(NULL, [], 'Update and save');
$this->assertText('The page Foo has been updated.');
// Check the extra parameter is optional.
$this->drupalGet('admin/foo/' . $node->id());
$this->assertResponse(200);
$this->assertText($node->getTitle());
$this->drupalGet('admin/foo/' . $node->id() . '/' . $this->randomMachineName());
$this->assertResponse(200);
$this->assertText($node->getTitle());
}
}
......@@ -121,6 +121,7 @@ class Page extends ConfigEntityBase implements PageInterface {
* - machine_name: Machine-readable context name.
* - label: Human-readable context name.
* - type: Context type.
* - optional: Whether the parameter is optional.
*
* @var array[]
*/
......@@ -243,6 +244,21 @@ class Page extends ConfigEntityBase implements PageInterface {
return [];
}
/**
* Defaults for new parameters.
*
* @return array
* An array of default parameter values.
*/
public function parameterDefaults() {
return [
'type' => '',
'machine_name' => '',
'label' => '',
'optional' => FALSE,
];
}
/**
* {@inheritdoc}
*/
......@@ -263,11 +279,12 @@ class Page extends ConfigEntityBase implements PageInterface {
/**
* {@inheritdoc}
*/
public function setParameter($name, $type, $label = '') {
public function setParameter($name, $type, $label = '', $optional = FALSE) {
$this->parameters[$name] = [
'machine_name' => $name,
'type' => $type,
'label' => $label,
'optional' => $optional,
];
// Reset contexts when a parameter is added or changed.
$this->contexts = [];
......@@ -352,7 +369,8 @@ class Page extends ConfigEntityBase implements PageInterface {
$cacheability->setCacheContexts(['route']);
$context_definition = ContextDefinitionFactory::create($configuration['type'])
->setLabel($configuration['label']);
->setLabel($configuration['label'])
->setRequired(empty($configuration['optional']));
$context = new Context($context_definition);
$context->addCacheableDependency($cacheability);
$this->contexts[$machine_name] = $context;
......
......@@ -168,10 +168,12 @@ interface PageInterface extends ConfigEntityInterface, EntityWithPluginCollectio
* The parameter context type.
* @param string $label
* (optional) The parameter context label.
* @param bool $optional
* (optional) If the parameter context is optional.
*
* @return $this
*/
public function setParameter($name, $type, $label = '');
public function setParameter($name, $type, $label = '', $optional = FALSE);
/**
* Removes a specific parameter context.
......
......@@ -80,6 +80,9 @@ class PageManagerRoutes extends RouteSubscriberBase {
foreach ($entity->getParameters() as $parameter_name => $parameter) {
if (!empty($parameter['type'])) {
$parameters[$parameter_name]['type'] = $parameter['type'];
if (!empty($parameter['optional'])) {
$parameters[$parameter_name]['optional'] = TRUE;
}
}
}
......@@ -108,6 +111,11 @@ class PageManagerRoutes extends RouteSubscriberBase {
'_admin_route' => $entity->usesAdminTheme(),
]
);
foreach ($parameters as $key => $parameter) {
if (!empty($parameter['optional'])) {
$route->addDefaults([$key => NULL]);
}
}
$collection->add($route_name . '_' . $variant_id, $route);
}
......
......@@ -227,6 +227,10 @@ class VariantRouteFilter implements FilterInterface {
$raw_attributes = RouteAttributes::extractRawAttributes($route, $name, $path);
$attributes = NestedArray::mergeDeep($attributes, $raw_attributes);
$attributes = array_filter($attributes, function($attribute){
return !is_null($attribute);
});
// Run the route enhancers on the raw attributes. This performs the same
// functionality as \Symfony\Cmf\Component\Routing\DynamicRouter::match().
foreach ($this->getRouteEnhancers() as $enhancer) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment