route_ui-1.0.0-alpha2/src/Element/Route.php
src/Element/Route.php
<?php
namespace Drupal\route_ui\Element;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\ParamConverter\ParamConverterManagerInterface;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Render\Element\Details;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\RouteObjectInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/**
* Provides an route element.
*
* Properties:
* - #route: Sets the value of the inner form elements. An array with the
* following keys:
* - 'route_name': A string for the route name.
* - 'route_parameters': An array of route parameters.
* - 'link_title': A string used as the link text.
* - #route_access_check: Determines if the route should be access checked.
* Defaults to TRUE.
* - #title: The title of the details container. Defaults to "Details".
* - #open: Indicates whether the container should be open by default.
* Defaults to FALSE.
* - #summary_attributes: An array of attributes to apply to the <summary>
* element.
*
* @RenderElement("route_ui")
*/
class Route extends Details {
/**
* {@inheritdoc}
*/
public function getInfo(): array {
$class = static::class;
$info = parent::getInfo();
$info['#input'] = TRUE;
$info['#route_access_check'] = TRUE;
array_unshift($info['#process'], [$class, 'processRoute']);
$info['#element_validate'][] = [$class, 'validateRoute'];
$info['#value_callback'] = [$class, 'valueCallback'];
unset($info['#value']);
return $info;
}
/**
* Expands a route_ui element type into input elements.
*
* @param array $element
* The form element whose value is being processed.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The form element whose value has been processed.
*/
public static function processRoute(array &$element, FormStateInterface $form_state, array &$complete_form): array {
// The value callback has populated the #value array.
$values = $element['#value'];
unset($element['#value']);
$user_token = Crypt::hmacBase64(self::getCurrentUser()->id(), Settings::getHashSalt() . self::getPrivateKey());
$element['route_name'] = [
'#type' => 'textfield',
'#title' => new TranslatableMarkup('Route name'),
'#default_value' => $values['route_name'],
'#description' => new TranslatableMarkup('The route name. For example: "node.add". To remove the route delete the value.'),
'#autocomplete_route_name' => 'route_ui.route_names.autocomplete',
'#autocomplete_route_parameters' => ['user_token' => $user_token],
];
$element['route_parameters'] = [
'#type' => 'textfield',
'#title' => new TranslatableMarkup('The route parameters'),
'#default_value' => self::paramArrayToString($values['route_parameters']),
'#description' => new TranslatableMarkup('The route parameter values separate by a comma. For example, "node_type=page".'),
];
$element['link_title'] = [
'#type' => 'textfield',
'#title' => new TranslatableMarkup('The text for link'),
'#default_value' => $values['link_title'],
];
$element['add_destination'] = [
'#type' => 'checkbox',
'#title' => new TranslatableMarkup('Adds the destination to the query string. If this is not already set it will use the current page.'),
'#default_value' => $values['add_destination'] ?? FALSE,
];
return $element;
}
/**
* Validation callback for a route_ui element.
*
* @param array $element
* The form element whose value is being validated.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*/
public static function validateRoute(array &$element, FormStateInterface $form_state, array &$complete_form) {
$input_exists = FALSE;
$values = $form_state->getValues();
$input = NestedArray::getValue($values, $element['#parents'], $input_exists);
if ($input_exists && !empty($input['route_name'])) {
if (empty($input['link_title'])) {
$form_state->setError($element['link_title'], new TranslatableMarkup('The text for link is required.'));
}
try {
$route = self::getRouteProvider()->getRouteByName($input['route_name']);
// ParamConverterManager relies on the route name and object being
// available from the parameters array.
$input['route_parameters'][RouteObjectInterface::ROUTE_NAME] = $input['route_name'];
$input['route_parameters'][RouteObjectInterface::ROUTE_OBJECT] = $route;
$upcasted_parameters = self::getParamConverter()->convert($input['route_parameters'] + $route->getDefaults());
$route_match = new RouteMatch($input['route_name'], $route, $upcasted_parameters, $input['route_parameters']);
if ($element['#route_access_check'] && !self::getAccessManager()->check($route_match)) {
$form_state->setError($element['route_name'], new TranslatableMarkup('Access to the route is denied.'));
}
}
catch (RouteNotFoundException $e) {
$form_state->setError($element['route_name'], new TranslatableMarkup('The route does not exist.'));
}
catch (ParamNotConvertedException $e) {
$form_state->setError($element['route_parameters'], new TranslatableMarkup('The route parameters are incorrect: %message', ['%message' => $e->getMessage()]));
}
}
}
/**
* Determines how user input is mapped to an element's #value property.
*
* @param array $element
* An associative array containing the properties of the element.
* @param mixed $input
* The incoming input to populate the form element. If this is FALSE,
* the element's default value should be returned.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* The value to assign to the element.
*
* @see \Drupal\Core\Render\Element\FormElementInterface::valueCallback()
*/
public static function valueCallback(array &$element, $input, FormStateInterface $form_state): array {
if ($input) {
$input['route_parameters'] = self::paramStringToArray($input['route_parameters']);
}
else {
$input = $element['#route'] ?? [
'route_name' => '',
'route_parameters' => [],
'link_title' => '',
'add_destination' => FALSE,
];
}
return $input;
}
/**
* Converts a route parameter string into an array.
*
* @param string|null $param_string
* The route parameter string.
*
* @return array
* The array representation.
*/
private static function paramStringToArray(string $param_string = NULL) : array {
$params = [];
$params_to_process = !empty($param_string) ? explode(',', $param_string) : [];
foreach ($params_to_process as $param) {
[$value1, $value2] = explode('=', $param, 2);
if (!empty($value2)) {
$params[$value1] = $value2;
}
else {
$params[] = $value1;
}
}
return $params;
}
/**
* Converts an array of route parameters into a string.
*
* @param array $params
* The array of route parameters.
*
* @return string
* The string representation.
*/
private static function paramArrayToString(array $params = []) : string {
$strings = [];
foreach ($params as $key => $param) {
$strings[] = "$key=$param";
}
return implode(', ', $strings);
}
/**
* Gets the access manager for route checking.
*
* @return \Drupal\Core\Access\AccessManagerInterface
*/
private static function getAccessManager(): AccessManagerInterface {
return \Drupal::service('access_manager');
}
/**
* Gets the route provider.
*
* @return \Drupal\Core\Routing\RouteProviderInterface
*/
private static function getRouteProvider(): RouteProviderInterface {
return \Drupal::service('router.route_provider');
}
/**
* Gets the param convertor.
*
* @return \Drupal\Core\ParamConverter\ParamConverterManagerInterface
*/
private static function getParamConverter(): ParamConverterManagerInterface {
return \Drupal::service('paramconverter_manager');
}
/**
* The current user.
*
* @return \Drupal\Core\Session\AccountProxyInterface
*/
private static function getCurrentUser(): AccountProxyInterface {
return \Drupal::currentUser();
}
/**
* Gets the private key.
*
* @return string
* The private key.
*/
private static function getPrivateKey(): string {
return \Drupal::service('private_key')->get();
}
}
