final class KeyEntityFormEnhancer in Apigee Edge 8
Enhances Apigee Edge related Key entity add/edit forms.
This service only exists because it allows us to use dependency injection and unit test our customizations.
@internal This service should be replaced or decorated. This is the reason why it does not define an interface either.
Hierarchy
- class \Drupal\apigee_edge\KeyEntityFormEnhancer uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of KeyEntityFormEnhancer
1 string reference to 'KeyEntityFormEnhancer'
1 service uses KeyEntityFormEnhancer
File
- src/
KeyEntityFormEnhancer.php, line 58
Namespace
Drupal\apigee_edgeView source
final class KeyEntityFormEnhancer {
use MessengerTrait;
use StringTranslationTrait;
// Required to be able to call validateForm() and similar methods with
// defining them as static.
use DependencySerializationTrait;
/**
* The SDK connector service.
*
* @var \Drupal\apigee_edge\SDKConnectorInterface
*/
private $connector;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
private $entityTypeManager;
/**
* The OAuth token storage.
*
* @var \Drupal\apigee_edge\OauthTokenStorageInterface
*/
private $oauthTokenStorage;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
private $configFactory;
/**
* The email validator.
*
* @var \Drupal\Component\Utility\EmailValidatorInterface
*/
private $emailValidator;
/**
* KeyEntityFormEnhancer constructor.
*
* @param \Drupal\apigee_edge\SDKConnectorInterface $connector
* The SDK connector service.
* @param \Drupal\apigee_edge\OauthTokenStorageInterface $oauth_token_storage
* The OAuth token storage.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager serivce.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Component\Utility\EmailValidatorInterface $email_validator
* The email validator.
*/
public function __construct(SDKConnectorInterface $connector, OauthTokenStorageInterface $oauth_token_storage, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, EmailValidatorInterface $email_validator) {
$this->connector = $connector;
$this->entityTypeManager = $entity_type_manager;
$this->oauthTokenStorage = $oauth_token_storage;
$this->configFactory = $config_factory;
$this->emailValidator = $email_validator;
}
/**
* Alters entity forms that defines an Apigee Edge authentication key.
*
* @param array $form
* Form render array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state object.
*/
public function alterForm(array &$form, FormStateInterface $form_state) : void {
// Sanity check, form must be a Key form.
if (!$form_state
->getFormObject() instanceof KeyFormBase) {
return;
}
/** @var \Drupal\key\KeyInterface $key */
$key = $form_state
->getFormObject()
->getEntity();
// Do not alter the confirmation step of the key edit form.
if (!$key
->isNew() && isset($form['confirm_edit'])) {
return;
}
$form['#prefix'] = '<div id="apigee-edge-key-form-enhancer">';
$form['#suffix'] = '</div>';
// We can not add this when AJAX reloads the page and it is sure that this
// is an Apigee Edge Authentication key but it only validates Apigee Edge
// Authentication keys.
$form['#validate'][] = [
$this,
'validateForm',
];
// Add enhancements to Apigee Edge Authentication keys.
if ($this
->isApigeeKeyTypeAuthForm($form_state)) {
/** @var \Drupal\apigee_edge\Plugin\KeyProviderRequirementsInterface $key_provider */
$key_provider = $key
->getKeyProvider();
// Warn user about key provider pre-requirement issues before form
// submission.
if ($key_provider instanceof KeyProviderRequirementsInterface) {
try {
$key_provider
->checkRequirements($key);
} catch (KeyProviderRequirementsException $exception) {
// Report key provider errors inline. This also allows us to clear
// these error messages when the provider changes.
$form['settings']['provider_section']['key_provider_error'] = [
'#theme' => 'status_messages',
'#message_list' => [
'error' => [
$this
->t('The requirements of the selected %key_provider key provider are not fulfilled. Fix errors described below or change the key provider.', [
'%key_provider' => $key_provider
->getPluginDefinition()['label'],
]),
$exception
->getTranslatableMarkupMessage(),
],
],
// Display it on the top of the section.
'#weight' => -100,
];
}
}
// Placeholder for messages. This also must be part of the form always
// because without it we could not render messages on the top of the form.
$form['settings']['messages'] = [
'#theme' => 'status_messages',
'#message_list' => [],
'#weight' => -100,
];
// Placeholder for debug.
$form['settings']['debug_placeholder'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => [
'id' => 'apigee-edge-auth-form-debug-info',
],
];
$form['settings']['debug'] = [
'#type' => 'details',
'#title' => $this
->t('Debug information'),
'#access' => FALSE,
'#open' => FALSE,
'#theme_wrappers' => [
'details' => [],
'container' => [
'#attributes' => [
'id' => 'apigee-edge-auth-form-debug-info',
],
],
],
];
$form['settings']['debug']['debug_text'] = [
'#type' => 'textarea',
'#disabled' => TRUE,
'#rows' => 20,
];
$form['settings']['test_connection'] = [
'#type' => 'details',
'#title' => $this
->t('Test connection'),
'#description' => $this
->t('Send request using the given API credentials.'),
'#open' => TRUE,
'#theme_wrappers' => [
'details' => [],
'container' => [
'#attributes' => [
'id' => 'apigee-edge-connection-info',
],
],
],
];
if (!$this
->keyIsWritable($key)) {
if ($key
->isNew()) {
$form['settings']['test_connection']['#description'] = $this
->t('Send request using the stored credentials in the key provider');
}
else {
$form['settings']['test_connection']['#description'] = $this
->t("Send request using the <a href=':key_config_uri' target='_blank'>active authentication key</a>.", [
':key_config_uri' => $key
->toUrl()
->toString(),
]);
}
}
$form['settings']['test_connection']['test_connection_submit'] = [
'#type' => 'submit',
'#executes_submit_callback' => FALSE,
'#value' => $this
->t('Send request'),
'#name' => 'test_connection',
'#ajax' => [
'callback' => [
$this,
'testConnectionAjaxCallback',
],
'wrapper' => 'apigee-edge-key-form-enhancer',
'progress' => [
'type' => 'throbber',
'message' => $this
->t('Waiting for response...'),
],
],
'#states' => [
'enabled' => [
[
':input[name="key_input_settings[organization]"]' => [
'empty' => FALSE,
],
':input[name="key_input_settings[password]"]' => [
'empty' => FALSE,
],
':input[name="key_input_settings[username]"]' => [
'empty' => FALSE,
],
],
[
':input[name="key_input_settings[instance_type]"]' => [
'value' => EdgeKeyTypeInterface::INSTANCE_TYPE_HYBRID,
],
':input[name="key_input_settings[organization]"]' => [
'empty' => FALSE,
],
':input[name="key_input_settings[account_json_key]"]' => [
'empty' => FALSE,
],
],
],
],
];
}
}
/**
* Additional validation handler for Apigee Edge authentication key forms.
*
* @param array $form
* Form render array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state object.
*/
public function validateForm(array &$form, FormStateInterface $form_state) : void {
// Sanity check, if this is not a Apigee Edge Authentication key form
// do nothing.
if (!$this
->isApigeeKeyTypeAuthForm($form_state)) {
return;
}
// Only call this when form is saved or "Test connection" is clicked.
if (!in_array($form_state
->getTriggeringElement()['#name'] ?? [], [
'test_connection',
'op',
])) {
return;
}
// If there is a form error already do not continue.
if (!empty($form_state
->getErrors())) {
return;
}
/** @var \Drupal\key\KeyInterface $key */
$key = $form_state
->getFormObject()
->getEntity();
// Check whether or not we know how to write to this key.
if ($this
->keyIsWritable($key)) {
// When form gets saved, key values are already processed.
if (isset($form_state
->getStorage()['key_value']['processed_submitted'])) {
$key_value = $form_state
->getStorage()['key_value']['processed_submitted'];
}
else {
// When "Test connection" reloads the page they are not yet processed.
// @see \Drupal\key\Form\KeyFormBase::createPluginFormState()
$key_input_plugin_form_state = clone $form_state;
$key_input_plugin_form_state
->setValues($form_state
->getValue('key_input_settings', []));
// @see \Drupal\key\Form\KeyFormBase::validateForm()
$key_input_processed_values = $key
->getKeyInput()
->processSubmittedKeyValue($key_input_plugin_form_state);
$key_value = $key_input_processed_values['processed_submitted'];
// Although, if the key exists, the key_value storage contains the
// saved key. We must remove that from the storage otherwise an
// invalid combination of the saved key values and the new values from
// the input fields gets sent to Apigee Edge.
$key_value_from_storage =& $form_state
->get('key_value');
unset($key_value_from_storage['original'], $key_value_from_storage['processed_original']);
}
// Create a temp key for testing without saving it.
$random = new Random();
/** @var \Drupal\key\KeyInterface $test_key */
$test_key = $this->entityTypeManager
->getStorage('key')
->create([
'id' => strtolower($random
->name(16)),
'key_type' => $key
->getKeyType()
->getPluginID(),
'key_input' => $key
->getKeyInput()
->getPluginID(),
'key_provider' => 'config',
'key_provider_settings' => [
'key_value' => $key_value,
],
]);
}
else {
// There will be no input form for the key since it's not writable so
// just use the actual key for testing.
$test_key = clone $key;
}
/** @var \Drupal\apigee_edge\Plugin\KeyType\ApigeeAuthKeyType $test_key_type */
$test_key_type = $test_key
->getKeyType();
$test_auth_type = $test_key_type
->getAuthenticationType($test_key);
try {
if (in_array($test_auth_type, [
EdgeKeyTypeInterface::EDGE_AUTH_TYPE_OAUTH,
EdgeKeyTypeInterface::EDGE_AUTH_TYPE_JWT,
])) {
// Check the requirements first.
$this->oauthTokenStorage
->checkRequirements();
// Clear existing OAuth token data.
$this
->cleanUpOauthTokenData();
}
// Test the connection.
$this->connector
->testConnection($test_key);
$this
->messenger()
->addStatus($this
->t('Connection successful.'));
// Based on type of organization, cache needs to clear.
drupal_flush_all_caches();
} catch (\Exception $exception) {
watchdog_exception('apigee_edge', $exception);
$form_state
->setError($form, $this
->t('@suggestion Error message: %response', [
'@suggestion' => $this
->createSuggestion($exception, $test_key),
'%response' => $exception
->getMessage(),
]));
// Display debug information.
$form['settings']['debug']['#access'] = $form['settings']['debug']['debug_text']['#access'] = TRUE;
$form['settings']['debug']['debug_text']['#value'] = $this
->createDebugText($exception, $test_key);
// In case of a form validation error the password field should
// still not clear the submitted value.
// \Drupal\apigee_edge\Plugin\KeyInput\ApigeeAuthKeyInput::buildConfigurationForm()
// does not get called in this case.
$is_provider_env = $test_key && $test_key
->getKeyProvider()
->getPluginId() === 'env';
$is_provider_file = $test_key && $test_key
->getKeyProvider()
->getPluginId() === 'file';
if ($test_key_type
->getInstanceType($test_key) != EdgeKeyTypeInterface::INSTANCE_TYPE_HYBRID && !$is_provider_env && !$is_provider_file) {
$form['settings']['input_section']['key_input_settings']['password']['#attributes']['value'] = $test_key_type
->getPassword($test_key);
}
} finally {
// Clear Oauth token data that may have been saved during testing
// connection or left behind when authentication type changed from OAuth
// to basic.
$this
->cleanUpOauthTokenData();
}
}
/**
* Removes Oauth token data.
*/
private function cleanUpOauthTokenData() : void {
if ($this->oauthTokenStorage instanceof OauthTokenFileStorage) {
$this->oauthTokenStorage
->removeTokenFile();
}
else {
$this->oauthTokenStorage
->removeToken();
}
}
/**
* Checks whether a key form is an Apigee Edge authentication form.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state object.
*
* @return bool
* TRUE or FALSE.
*/
private function isApigeeKeyTypeAuthForm(FormStateInterface $form_state) : bool {
// Sanity check, form must be a Key form.
if (!$form_state
->getFormObject() instanceof KeyFormBase) {
FALSE;
}
/** @var \Drupal\key\KeyInterface $key */
$key = $form_state
->getFormObject()
->getEntity();
// When Ajax reloads the form - for example when Key provider changes -
// the type of the entity falls back to the default "Authentication" type
// on the Key add form.
$key_type_from_user_input = $form_state
->getUserInput()['key_type'] ?? '';
return $key
->getKeyType() instanceof ApigeeAuthKeyType || $key_type_from_user_input === 'apigee_auth';
}
/**
* Ajax callback for test connection.
*
* @param array $form
* Form render array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state object.
*
* @return array
* Render array.
*/
public static function testConnectionAjaxCallback(array $form, FormStateInterface $form_state) : array {
// Capture and re-render all messages as part of the form so when AJAX
// rebuilds the form all previous messages get removed from the UI.
$original_weight = $form['settings']['messages']['#weight'] ?? 0;
$form['settings']['messages'] = StatusMessages::renderMessages();
$form['settings']['messages']['#weight'] = $original_weight;
return $form;
}
/**
* Checks whether we know how to write to a key.
*
* @param \Drupal\key\KeyInterface $key
* The key to test.
*
* @return bool
* Whether the key is writable.
*/
private function keyIsWritable(KeyInterface $key) : bool {
return $key
->getKeyProvider() instanceof KeyProviderSettableValueInterface;
}
/**
* Creates a suggestion text to be displayed in the connection failed message.
*
* @param \Exception $exception
* The thrown exception during form validation.
* @param \Drupal\key\KeyInterface $key
* The used key during form validation.
*
* @return \Drupal\Component\Render\MarkupInterface
* The suggestion text to be displayed.
*/
private function createSuggestion(\Exception $exception, KeyInterface $key) : MarkupInterface {
$fail_text = $this
->t('Failed to connect to Apigee Edge.');
// General error message.
$suggestion = $this
->t('@fail_text', [
'@fail_text' => $fail_text,
]);
/** @var \Drupal\apigee_edge\Plugin\KeyType\ApigeeAuthKeyType $key_type */
$key_type = $key
->getKeyType();
if ($exception instanceof AuthenticationKeyException) {
$suggestion = $this
->t('@fail_text Verify the Apigee Edge connection settings.', [
'@fail_text' => $fail_text,
]);
}
elseif ($exception instanceof ApigeeOnGcpOauth2AuthenticationException) {
$fail_text = $this
->t('Failed to connect to the authorization server.');
// General error message.
$suggestion = $this
->t('@fail_text Check the debug information below for more details.', [
'@fail_text' => $fail_text,
]);
// Invalid key / OpenSSL unable to sign data.
if ($exception
->getPrevious() && $exception
->getPrevious() instanceof \DomainException) {
$suggestion = $this
->t('@fail_text The private key in the GCP service account key JSON is invalid.', [
'@fail_text' => $fail_text,
]);
}
}
elseif ($exception instanceof OauthAuthenticationException) {
$fail_text = $this
->t('Failed to connect to the OAuth authorization server.');
// General error message.
$suggestion = $this
->t('@fail_text Check the debug information below for more details.', [
'@fail_text' => $fail_text,
]);
// Invalid credentials.
if ($exception
->getCode() === 401) {
// Invalid credentials using defined client_id/client_secret.
if ($key_type
->getClientId($key) !== Oauth::DEFAULT_CLIENT_ID || $key_type
->getClientSecret($key) !== Oauth::DEFAULT_CLIENT_SECRET) {
$suggestion = $this
->t('@fail_text The given username (%username) or password or client ID (%client_id) or client secret is incorrect.', [
'@fail_text' => $fail_text,
'%client_id' => $key_type
->getClientId($key),
'%username' => $key_type
->getUsername($key),
]);
}
else {
$suggestion = $this
->t('@fail_text The given username (%username) or password is incorrect.', [
'@fail_text' => $fail_text,
'%username' => $key_type
->getUsername($key),
]);
}
}
elseif ($exception
->getCode() === 0) {
if ($exception
->getPrevious() instanceof ApiRequestException && $exception
->getPrevious()
->getPrevious() instanceof NetworkException && $exception
->getPrevious()
->getPrevious()
->getPrevious() instanceof ConnectException) {
/** @var \GuzzleHttp\Exception\ConnectException $curl_exception */
$curl_exception = $exception
->getPrevious()
->getPrevious()
->getPrevious();
// Resolving timed out.
if ($curl_exception
->getHandlerContext()['errno'] === CURLE_OPERATION_TIMEDOUT) {
$suggestion = $this
->t('@fail_text The connection timeout threshold (%connect_timeout) or the request timeout (%timeout) is too low or something is wrong with the connection.', [
'@fail_text' => $fail_text,
'%connect_timeout' => $this
->config('apigee_edge.client')
->get('http_client_connect_timeout'),
'%timeout' => $this
->config('apigee_edge.client')
->get('http_client_timeout'),
]);
}
// The remote host was not resolved (authorization server).
if ($curl_exception
->getHandlerContext()['errno'] === CURLE_COULDNT_RESOLVE_HOST) {
$suggestion = $this
->t('@fail_text The given authorization server (%authorization_server) is incorrect or something is wrong with the connection.', [
'@fail_text' => $fail_text,
'%authorization_server' => $key_type
->getAuthorizationServer($key),
]);
}
}
}
}
else {
// Invalid credentials.
// TODO Remove the second condition which is a workaround for a
// regression bug in the Apigee Edge for Public Cloud 19.03.01 release. If
// valid organization name and username provided with an invalid password
// the MGMT server returns HTTP 500 with an error instead of HTTP 401.
if ($exception
->getCode() === 401 || $exception
->getCode() === 500 && $exception
->getEdgeErrorCode() === 'usersandroles.SsoInternalServerError') {
// If on public cloud, the username should be an email.
if ($key_type
->getInstanceType($key) === EdgeKeyTypeInterface::INSTANCE_TYPE_PUBLIC && !$this->emailValidator
->isValid($key_type
->getUsername($key))) {
$suggestion = $this
->t('@fail_text The organization username should be a valid email.', [
'@fail_text' => $fail_text,
]);
}
else {
$suggestion = $this
->t('@fail_text The given username (%username) or password is incorrect.', [
'@fail_text' => $fail_text,
'%username' => $key_type
->getUsername($key),
]);
}
}
elseif ($exception
->getCode() === 404) {
$suggestion = $this
->t('@fail_text The given organization name (%organization) is incorrect.', [
'@fail_text' => $fail_text,
'%organization' => $key_type
->getOrganization($key),
]);
}
elseif ($exception
->getCode() === 0) {
if ($exception
->getPrevious() instanceof NetworkException && $exception
->getPrevious()
->getPrevious() instanceof ConnectException) {
/** @var \GuzzleHttp\Exception\ConnectException $curl_exception */
$curl_exception = $exception
->getPrevious()
->getPrevious();
// Resolving timed out.
if ($curl_exception
->getHandlerContext()['errno'] === CURLE_OPERATION_TIMEDOUT) {
$suggestion = $this
->t('@fail_text The connection timeout threshold (%connect_timeout) or the request timeout (%timeout) is too low or something is wrong with the connection.', [
'@fail_text' => $fail_text,
'%connect_timeout' => $this
->config('apigee_edge.client')
->get('http_client_connect_timeout'),
'%timeout' => $this
->config('apigee_edge.client')
->get('http_client_timeout'),
]);
}
elseif ($curl_exception
->getHandlerContext()['errno'] === CURLE_COULDNT_RESOLVE_HOST) {
$suggestion = $this
->t('@fail_text The given endpoint (%endpoint) is incorrect or something is wrong with the connection.', [
'@fail_text' => $fail_text,
'%endpoint' => $key_type
->getEndpoint($key),
]);
}
}
elseif ($exception instanceof InvalidArgumentException) {
$suggestion = $this
->t('@fail_text The given endpoint (%endpoint) is incorrect or something is wrong with the connection.', [
'@fail_text' => $fail_text,
'%endpoint' => $key_type
->getEndpoint($key),
]);
}
}
}
return $suggestion;
}
/**
* Creates debug text if there was an error during form validation.
*
* @param \Exception $exception
* The thrown exception during form validation.
* @param \Drupal\key\KeyInterface $key
* The used key during form validation.
*
* @return string
* The debug text to be displayed.
*/
private function createDebugText(\Exception $exception, KeyInterface $key) : string {
$key_type = $key
->getKeyType();
$credentials = [];
$keys = [
'auth_type' => $key_type instanceof EdgeKeyTypeInterface ? $key_type
->getAuthenticationType($key) : 'invalid credentials',
'key_provider' => get_class($key
->getKeyProvider()),
];
if ($key_type instanceof EdgeKeyTypeInterface) {
$credentials = [
'endpoint' => $key_type
->getEndpoint($key),
'organization' => $key_type
->getOrganization($key),
];
if ($key_type
->getInstanceType($key) != EdgeKeyTypeInterface::INSTANCE_TYPE_HYBRID) {
$credentials['username'] = $key_type
->getUsername($key);
}
if ($key_type
->getAuthenticationType($key) === EdgeKeyTypeInterface::EDGE_AUTH_TYPE_OAUTH) {
$credentials['authorization_server'] = $key_type
->getAuthorizationServer($key);
$credentials['client_id'] = $key_type
->getClientId($key);
$credentials['client_secret'] = $key_type
->getClientSecret($key) === Oauth::DEFAULT_CLIENT_SECRET ? Oauth::DEFAULT_CLIENT_SECRET : '***client-secret***';
}
}
// Sanitize exception text.
$exception_text = preg_replace([
'/(.*refresh_token=)([^\\&\\r\\n]+)(.*)/',
'/(.*mfa_token=)([^\\&\\r\\n]+)(.*)/',
'/(.*password=)([^\\&\\r\\n]+)(.*)/',
'/(Authorization: (Basic|Bearer) ).*/',
], [
'$1***refresh-token***$3',
'$1***mfa-token***$3',
'$1***password***$3',
'$1***credentials***',
], (string) $exception);
// Filter out any private values from config.
$client_config = array_filter($this->configFactory
->get('apigee_edge.client')
->get(), static function ($key) {
return !is_string($key) || $key[0] !== '_';
}, ARRAY_FILTER_USE_KEY);
return json_encode($credentials, JSON_PRETTY_PRINT) . PHP_EOL . json_encode($keys, JSON_PRETTY_PRINT) . PHP_EOL . json_encode($client_config, JSON_PRETTY_PRINT) . PHP_EOL . $exception_text;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
KeyEntityFormEnhancer:: |
private | property | The config factory. | |
KeyEntityFormEnhancer:: |
private | property | The SDK connector service. | |
KeyEntityFormEnhancer:: |
private | property | The email validator. | |
KeyEntityFormEnhancer:: |
private | property | The entity type manager. | |
KeyEntityFormEnhancer:: |
private | property | The OAuth token storage. | |
KeyEntityFormEnhancer:: |
public | function | Alters entity forms that defines an Apigee Edge authentication key. | |
KeyEntityFormEnhancer:: |
private | function | Removes Oauth token data. | |
KeyEntityFormEnhancer:: |
private | function | Creates debug text if there was an error during form validation. | |
KeyEntityFormEnhancer:: |
private | function | Creates a suggestion text to be displayed in the connection failed message. | |
KeyEntityFormEnhancer:: |
private | function | Checks whether a key form is an Apigee Edge authentication form. | |
KeyEntityFormEnhancer:: |
private | function | Checks whether we know how to write to a key. | |
KeyEntityFormEnhancer:: |
public static | function | Ajax callback for test connection. | |
KeyEntityFormEnhancer:: |
public | function | Additional validation handler for Apigee Edge authentication key forms. | |
KeyEntityFormEnhancer:: |
public | function | KeyEntityFormEnhancer constructor. | |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |