class NavigationLinkBlock

Defines a link navigation block.

@internal

Hierarchy

Expanded class hierarchy of NavigationLinkBlock

File

core/modules/navigation/src/Plugin/Block/NavigationLinkBlock.php, line 20

Namespace

Drupal\navigation\Plugin\Block
View source
final class NavigationLinkBlock extends BlockBase {
    
    /**
     * {@inheritdoc}
     */
    public function defaultConfiguration() : array {
        return [
            'title' => '',
            'uri' => '',
            'icon_class' => '',
        ];
    }
    
    /**
     * {@inheritdoc}
     */
    public function blockForm($form, FormStateInterface $form_state) : array {
        $config = $this->configuration;
        $display_uri = NULL;
        if (!empty($config['uri'])) {
            try {
                // The current field value could have been entered by a different user.
                // However, if it is inaccessible to the current user, do not display it
                // to them.
                $url = Url::fromUri($config['uri']);
                if (\Drupal::currentUser()->hasPermission('link to any page') || $url->access()) {
                    $display_uri = static::getUriAsDisplayableString($config['uri']);
                }
            } catch (\InvalidArgumentException) {
                // If $item->uri is invalid, show value as is, so the user can see what
                // to edit.
                $display_uri = $config['uri'];
            }
        }
        // @todo Logic related to the uri component has been borrowed from
        //   Drupal\link\Plugin\Field\FieldWidget\LinkWidget.
        //   Will be fixed in https://www.drupal.org/project/drupal/issues/3450518.
        $form['uri'] = [
            '#type' => 'entity_autocomplete',
            '#title' => $this->t('URL'),
            '#default_value' => $display_uri,
            '#element_validate' => [
                [
                    static::class,
                    'validateUriElement',
                ],
            ],
            '#attributes' => [
                'data-autocomplete-first-character-blacklist' => '/#?',
            ],
            // @todo The user should be able to select an entity type. Will be fixed
            //   in https://www.drupal.org/node/2423093.
'#target_type' => 'node',
            '#maxlength' => 2048,
            '#required' => TRUE,
            '#process_default_value' => FALSE,
        ];
        $form['title'] = [
            '#type' => 'textfield',
            '#title' => $this->t('Link text'),
            '#default_value' => $config['title'],
            '#required' => TRUE,
            '#maxlength' => 255,
        ];
        $form['icon_class'] = [
            '#type' => 'textfield',
            '#title' => $this->t('Icon CSS class'),
            '#default_value' => $config['icon_class'],
            '#element_validate' => [
                [
                    static::class,
                    'validateIconClassElement',
                ],
            ],
            '#required' => TRUE,
            '#maxlength' => 64,
        ];
        return $form;
    }
    
    /**
     * Form element validation handler for the 'icon_class' element.
     *
     * Disallows saving invalid class values.
     */
    public static function validateIconClassElement(array $element, FormStateInterface $form_state, array $form) : void {
        $icon = $element['#value'];
        if (!preg_match('/^[a-z0-9_-]+$/', $icon)) {
            $form_state->setError($element, t('The machine-readable name must contain only lowercase letters, numbers, underscores and hyphens.'));
        }
    }
    
    /**
     * Form element validation handler for the 'uri' element.
     *
     * Disallows saving inaccessible or untrusted URLs.
     */
    public static function validateUriElement($element, FormStateInterface $form_state, $form) : void {
        $uri = static::getUserEnteredStringAsUri($element['#value']);
        $form_state->setValueForElement($element, $uri);
        // If getUserEnteredStringAsUri() mapped the entered value to an 'internal:'
        // URI , ensure the raw value begins with '/', '?' or '#'.
        // @todo '<front>' is valid input for BC reasons, may be removed by
        //   https://www.drupal.org/node/2421941
        if (parse_url($uri, PHP_URL_SCHEME) === 'internal' && !in_array($element['#value'][0], [
            '/',
            '?',
            '#',
        ], TRUE) && !str_starts_with($element['#value'], '<front>')) {
            $form_state->setError($element, new TranslatableMarkup('Manually entered paths should start with one of the following characters: / ? #'));
            return;
        }
    }
    
    /**
     * Gets the user-entered string as a URI.
     *
     * The following two forms of input are mapped to URIs:
     * - entity autocomplete ("label (entity id)") strings: to 'entity:' URIs;
     * - strings without a detectable scheme: to 'internal:' URIs.
     *
     * This method is the inverse of ::getUriAsDisplayableString().
     *
     * @param string $string
     *   The user-entered string.
     *
     * @return string
     *   The URI, if a non-empty $uri was passed.
     *
     * @see static::getUriAsDisplayableString()
     */
    protected static function getUserEnteredStringAsUri($string) : string {
        // By default, assume the entered string is a URI.
        $uri = trim($string);
        // Detect entity autocomplete string, map to 'entity:' URI.
        $entity_id = EntityAutocomplete::extractEntityIdFromAutocompleteInput($string);
        if ($entity_id !== NULL) {
            // @todo Support entity types other than 'node'. Will be fixed in
            //   https://www.drupal.org/node/2423093.
            $uri = 'entity:node/' . $entity_id;
        }
        elseif (in_array($string, [
            '<nolink>',
            '<none>',
            '<button>',
        ], TRUE)) {
            $uri = 'route:' . $string;
        }
        elseif (!empty($string) && parse_url($string, PHP_URL_SCHEME) === NULL) {
            // @todo '<front>' is valid input for BC reasons, may be removed by
            //   https://www.drupal.org/node/2421941
            // - '<front>' -> '/'
            // - '<front>#foo' -> '/#foo'
            if (str_starts_with($string, '<front>')) {
                $string = '/' . substr($string, strlen('<front>'));
            }
            $uri = 'internal:' . $string;
        }
        return $uri;
    }
    
    /**
     * Gets the URI without the 'internal:' or 'entity:' scheme.
     *
     * The following two forms of URIs are transformed:
     * - 'entity:' URIs: to entity autocomplete ("label (entity id)") strings;
     * - 'internal:' URIs: the scheme is stripped.
     *
     * This method is the inverse of ::getUserEnteredStringAsUri().
     *
     * @param string $uri
     *   The URI to get the displayable string for.
     *
     * @return string
     *
     * @see static::getUserEnteredStringAsUri()
     */
    protected static function getUriAsDisplayableString($uri) : string {
        $scheme = parse_url($uri, PHP_URL_SCHEME);
        // By default, the displayable string is the URI.
        $displayable_string = $uri;
        // A different displayable string may be chosen in case of the 'internal:'
        // or 'entity:' built-in schemes.
        if ($scheme === 'internal') {
            $uri_reference = explode(':', $uri, 2)[1];
            // @todo '<front>' is valid input for BC reasons, may be removed by
            //   https://www.drupal.org/node/2421941
            $path = parse_url($uri, PHP_URL_PATH);
            if ($path === '/') {
                $uri_reference = '<front>' . substr($uri_reference, 1);
            }
            $displayable_string = $uri_reference;
        }
        elseif ($scheme === 'entity') {
            [
                $entity_type,
                $entity_id,
            ] = explode('/', substr($uri, 7), 2);
            // Show the 'entity:' URI as the entity autocomplete would.
            // @todo Support entity types other than 'node'. Will be fixed in
            //   https://www.drupal.org/node/2423093.
            if ($entity_type == 'node' && ($entity = \Drupal::entityTypeManager()->getStorage($entity_type)
                ->load($entity_id))) {
                $displayable_string = EntityAutocomplete::getEntityLabels([
                    $entity,
                ]);
            }
        }
        elseif ($scheme === 'route') {
            $displayable_string = ltrim($displayable_string, 'route:');
        }
        return $displayable_string;
    }
    
    /**
     * {@inheritdoc}
     */
    public function blockSubmit($form, FormStateInterface $form_state) : void {
        $this->configuration['uri'] = $form_state->getValue('uri');
        $this->configuration['title'] = $form_state->getValue('title');
        $this->configuration['icon_class'] = $form_state->getValue('icon_class');
    }
    
    /**
     * {@inheritdoc}
     */
    public function build() : array {
        $config = $this->configuration;
        $build = [];
        // Ensure that user has access to link before rendering it.
        try {
            $url = Url::fromUri($config['uri']);
            $access = $url->access(NULL, TRUE);
            if (!$access->isAllowed()) {
                // Cacheable dependency is explicitly added when access is not granted.
                // It is bubbled when the link is rendered.
                $cacheable_metadata = new CacheableMetadata();
                $cacheable_metadata->addCacheableDependency($access);
                $cacheable_metadata->applyTo($build);
                return $build;
            }
        } catch (\InvalidArgumentException) {
            return $build;
        }
        return $build + [
            '#title' => $config['label'],
            '#theme' => 'navigation_menu',
            '#menu_name' => 'link',
            '#items' => [
                [
                    'title' => $config['title'],
                    'class' => $config['icon_class'],
                    'url' => $url,
                ],
            ],
        ];
    }

}

Members

Title Sort descending Modifiers Object type Summary Member alias Overriden Title Overrides
BlockBase::buildConfigurationForm public function Overrides PluginFormInterface::buildConfigurationForm 2
BlockPluginInterface::BLOCK_LABEL_VISIBLE constant Indicates the block label (title) should be displayed to end users.
BlockPluginTrait::$inPreview protected property Whether the plugin is being rendered in preview mode.
BlockPluginTrait::$transliteration protected property The transliteration service.
BlockPluginTrait::access public function
BlockPluginTrait::baseConfigurationDefaults protected function Returns generic default configuration for block plugins.
BlockPluginTrait::blockAccess protected function Indicates whether the block should be shown. 18
BlockPluginTrait::blockValidate public function 3
BlockPluginTrait::buildConfigurationForm public function Creates a generic configuration form for all block types. Individual
block plugins can add elements to this form by overriding
BlockBase::blockForm(). Most block plugins should not override this
method unless they need to alter the generic form elements.
Aliased as: traitBuildConfigurationForm
BlockPluginTrait::calculateDependencies public function 1
BlockPluginTrait::getConfiguration public function 1
BlockPluginTrait::getMachineNameSuggestion public function 1
BlockPluginTrait::getPreviewFallbackString public function 3
BlockPluginTrait::label public function
BlockPluginTrait::setConfiguration public function
BlockPluginTrait::setConfigurationValue public function
BlockPluginTrait::setInPreview public function
BlockPluginTrait::setTransliteration public function Sets the transliteration service.
BlockPluginTrait::submitConfigurationForm public function Most block plugins should not override this method. To add submission
handling for a specific block type, override BlockBase::blockSubmit().
BlockPluginTrait::transliteration protected function Wraps the transliteration service.
BlockPluginTrait::validateConfigurationForm public function Most block plugins should not override this method. To add validation
for a specific block type, override BlockBase::blockValidate().
1
BlockPluginTrait::__construct public function 25
ContextAwarePluginAssignmentTrait::addContextAssignmentElement protected function Builds a form element for assigning a context to a given slot.
ContextAwarePluginAssignmentTrait::contextHandler protected function Wraps the context handler.
ContextAwarePluginTrait::$context protected property The data objects representing the context of this plugin.
ContextAwarePluginTrait::getCacheContexts public function 10
ContextAwarePluginTrait::getCacheMaxAge public function 6
ContextAwarePluginTrait::getCacheTags public function 3
ContextAwarePluginTrait::getContext public function
ContextAwarePluginTrait::getContextDefinition public function
ContextAwarePluginTrait::getContextDefinitions public function
ContextAwarePluginTrait::getContextMapping public function 1
ContextAwarePluginTrait::getContexts public function
ContextAwarePluginTrait::getContextValue public function
ContextAwarePluginTrait::getContextValues public function
ContextAwarePluginTrait::getPluginDefinition abstract public function 1
ContextAwarePluginTrait::setContext public function 1
ContextAwarePluginTrait::setContextMapping public function
ContextAwarePluginTrait::setContextValue public function
ContextAwarePluginTrait::validateContexts public function
DerivativeInspectionInterface::getBaseId public function Gets the base_plugin_id of the plugin instance. 1
DerivativeInspectionInterface::getDerivativeId public function Gets the derivative_id of the plugin instance. 1
MessengerTrait::$messenger protected property The messenger. 16
MessengerTrait::messenger public function Gets the messenger. 16
MessengerTrait::setMessenger public function Sets the messenger.
NavigationLinkBlock::blockForm public function Overrides BlockPluginTrait::blockForm
NavigationLinkBlock::blockSubmit public function Overrides BlockPluginTrait::blockSubmit
NavigationLinkBlock::build public function Overrides BlockPluginInterface::build
NavigationLinkBlock::defaultConfiguration public function Overrides BlockPluginTrait::defaultConfiguration
NavigationLinkBlock::getUriAsDisplayableString protected static function Gets the URI without the &#039;internal:&#039; or &#039;entity:&#039; scheme.
NavigationLinkBlock::getUserEnteredStringAsUri protected static function Gets the user-entered string as a URI.
NavigationLinkBlock::validateIconClassElement public static function Form element validation handler for the &#039;icon_class&#039; element.
NavigationLinkBlock::validateUriElement public static function Form element validation handler for the &#039;uri&#039; element.
PluginInspectionInterface::getPluginId public function Gets the plugin ID of the plugin instance. 2
PluginWithFormsTrait::getFormClass public function Implements \Drupal\Core\Plugin\PluginWithFormsInterface::getFormClass().
PluginWithFormsTrait::hasFormClass public function Implements \Drupal\Core\Plugin\PluginWithFormsInterface::hasFormClass().
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.