Problem/Motivation

Blocking #2418017: Implement autocomplete UI for the link widget

Now that we have the entity: URI scheme supported by the \Drupal\Core\Url API (see #2411333: Create standard logic to handle a entity: URI scheme , #2412509: Change LinkItem.uri to type 'uri' rather than 'string' and introduce user-path: scheme, #2417333: Add support for user-path: scheme to Url class), we also want to be able to actually use it in the UI.

  1. LinkItem::getUrl() didn't support entity: because it called the PathValidator directly rather than using Url::fromUri().
  2. LinkTypeConstraint performed access checking; disallowing users to store links that they couldn't access. However, that should be a widget-level decision.

Proposed resolution

Fix all the problematic points listed above.

Remaining tasks

None.

User interface changes

In HEAD, for menus, the link field validation error message is "The path 'foo' is either invalid or you do not have access to it.", while for shortcuts and regular link fields it's "The URL foo is not valid.". With the patch, it's the former in all cases.

API changes

Comments

wim leers’s picture

Title: Allow entity: and user-path:Update LinkTypeConstraint to allow for » Allow entity: and user-path: to be entered in all link fields
Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new8.59 KB

Again pair programming with effulgentsia!

This fixes configurable link fields. Next steps: fix Shortcut and MenuLinkContent.

wim leers’s picture

StatusFileSize
new9.94 KB
new2.22 KB

Now allowing node/add as input again, instead of only allowing user-path:node/add.

Still pair programming with effulgentsia.

yesct’s picture

Issue summary: View changes
Issue tags: +blocker

The last submitted patch, 1: link_field_validation-2417793-1.patch, failed testing.

wim leers’s picture

StatusFileSize
new13.32 KB
new3.45 KB

MenuLinkContent is now fully working, no longer doing its own validation! Yay :)

(Still pair programming with effulgentsia!)

wim leers’s picture

Actually, Shortcut is now also working completely correctly! :)

Now this issue is down to fixing test failures.

The last submitted patch, 2: link_field_validation-2417793-2.patch, failed testing.

yched’s picture

Minor :

  1. +++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
    @@ -60,6 +61,18 @@ protected function getUriAsDisplayableString($uri) {
    +  public function elementValidate($element, FormStateInterface $form_state, $form) {
    

    Can we make that a static method, and add it as '#element_validate' => [[get_class($this), 'elementValidate']] ?
    Nicer on form serialization :-)

    (would mean the getUserEnteredStringAsUri() helper has to be static as well, but AFAICT it can ?)

  2. +++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
    @@ -219,19 +231,34 @@ public function validateTitle(&$element, FormStateInterface $form_state, $form)
    +  protected function getUserEnteredStringAsUri($uri) {
    

    Can we move that one next to the existing getUriAsDisplayableString() ? they look kind of related :-)

  3. +++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
    @@ -244,15 +244,6 @@ protected function actions(array $form, FormStateInterface $form_state) {
    -  public function validate(array $form, FormStateInterface $form_state) {
    
    @@ -287,23 +278,4 @@ public function save(array $form, FormStateInterface $form_state) {
    -  protected function doValidate(array $form, FormStateInterface $form_state) {
    

    Oh yeah !

Status: Needs review » Needs work

The last submitted patch, 5: link_field_validation-2417793-5.patch, failed testing.

yched’s picture

Also, really minor :

+++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
@@ -219,19 +231,34 @@ public function validateTitle(&$element, FormStateInterface $form_state, $form)
+  protected function getUserEnteredStringAsUri($uri) {

getFooAsBar($bar) is a bit weird - that $uri param is not a URI yet, that's the whole point :-)
--> s/$uri/$string/ ? $input ?

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new14.2 KB
new2.62 KB

Fixing failures! (Still pair programming!)

Status: Needs review » Needs work

The last submitted patch, 11: link_field_validation-2417793-11.patch, failed testing.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new19.32 KB
new5.19 KB

This will bring it down to a few dozen failures at most! (Still PP.)

wim leers’s picture

StatusFileSize
new20.11 KB
new867 bytes

The last submitted patch, 13: link_field_validation-2417793-13.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 14: link_field_validation-2417793-14.patch, failed testing.

effulgentsia’s picture

Assigned: wim leers » effulgentsia

Wim left the sprint and is on his way back home, so I'll finish fixing the remaining failures.

dawehner’s picture

  1. +++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
    @@ -60,6 +61,32 @@ protected function getUriAsDisplayableString($uri) {
    +      // Disallow external URLs using untrusted protocols.
    +      $disallowed = $disallowed || ($url->isExternal() && !in_array(parse_url($uri, PHP_URL_SCHEME), \Drupal::config('system.filter')->get('protocols')));
    

    I was wondering whether we should introduce UrlHelper::getDangerousProtocols and use it, which calls out to static::$allowedProtocols

  2. +++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
    @@ -60,6 +61,32 @@ protected function getUriAsDisplayableString($uri) {
    +      if ($disallowed) {
    +        $form_state->setError($element, $this->t('The URL %uri is not valid.', ['%uri' => $this->getUriAsDisplayableString($uri)]));
    +      }
    

    I'm wondering whether we could / should provide a more specific message? What means valid here / should we care?

  3. +++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
    @@ -244,15 +244,6 @@ protected function actions(array $form, FormStateInterface $form_state) {
        */
    -  public function validate(array $form, FormStateInterface $form_state) {
    -    $this->doValidate($form, $form_state);
    -
    -    parent::validate($form, $form_state);
    -  }
    -
    -  /**
    -   * {@inheritdoc}
    -   */
    

    Afaik you have to update validateConfigurationForm to point to something existing ... doValidate() removing should break it.

effulgentsia’s picture

Assigned: effulgentsia » Unassigned
Status: Needs work » Needs review
StatusFileSize
new26.07 KB
new8.48 KB

Hopefully green.

Status: Needs review » Needs work

The last submitted patch, 19: link_field_validation-2417793-19.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new26.02 KB

reroll

Status: Needs review » Needs work

The last submitted patch, 21: link_field_validation-2417793-21.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new31.33 KB
new9.91 KB

Addresses remaining feedback from yched and dawehner and fixes the failures.

Status: Needs review » Needs work

The last submitted patch, 23: link_field_validation-2417793-23.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new32.82 KB
new3 KB

Unifying the violation message to match the one from menu UI, cause it's better.

effulgentsia’s picture

Title: Allow entity: and user-path: to be entered in all link fields » Allow entity: URIs to be entered in link fields
Issue summary: View changes
Related issues: +#2418017: Implement autocomplete UI for the link widget
StatusFileSize
new447 bytes
new33.02 KB
new383 bytes

Using user-path: in the UI explicitly is pointless, so removing that from the issue purpose. Added a test for entity:, along with the test-only version to demonstrate the failure in HEAD. Also updated the summary to include the only UI change that I think is in here.

I think this is ready now, unless there's further feedback.

Would be great for this to land before tomorrow morning's sprint, to unblock #2418017: Implement autocomplete UI for the link widget.

The last submitted patch, 26: link_field_validation-2417793-26-test-only.patch, failed testing.

yesct’s picture

Status: Needs review » Needs work

I tried it manually. allows saving with entity: and also allows user-path: (but user-path will never be entered by a user so it's kinda irrelevant. after save it displays user-path:one as one so it's ok either way)

but

two concerns yet to be addressed. discussed in person.

undelete doValidate()

move one of getUserEnteredStringAsUri() or getUriAsDisplayableString()

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new32.63 KB
new3.02 KB

Addresses #28. Adds a @todo linking to the issue in #29.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

I'm convinced that this is RTBC at that given point in time.

webchick’s picture

Assigned: Unassigned » webchick

Reviewing whilst we wait for testbot. If anyone's still awake, I'm in #drupal-nj.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

I tried this out in the following way:

- Created a node/1 called "bananas" with a path of "bananas"
- Created two menu links: one to entity:node/1 and one to "bananas"
- Changed the path of node/1 to "bananaz"
- Expected result: first menu link continues to work, second one breaks.
- Actual result: Both menu links continue to work (?!?!)

Apparently our path system is doing something kinda magic here like remembering old paths, not sure?

At any rate, though, the patch is doing what it says, which is letting you enter entity:node/1 and it's linking to something. Also tried entity:user/1 for kicks and confirmed that there were no regressions in linking to e.g. <front>. It's a little funny that the patch doesn't also introduce an addition to the link field help text explaining you can now type entity:node/1 here, but I guess the thinking is that the autocomplete will be done soon enough we won't need to bother people with doing this from the UI (which obviously would be preferable).

Code-wise, only a couple of things.

  1. +++ b/core/modules/link/src/Plugin/Validation/Constraint/LinkTypeConstraint.php
    @@ -48,23 +48,34 @@ public function validatedBy() {
    -      $url_is_valid = FALSE;
    +      $uri_is_valid = TRUE;
    

    This was the only part that gave me pause, because we've moved from pessimistic validation to optimistic validation. I seem to remember this being raised in another issue as well... maybe by larowlan? Not going to block commit on it, but flagging it so once everyone has got a good night's rest, we can revisit this to make sure we're capturing all cases and didn't inadvertently introduce a sechole here.

  2. +++ b/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/UserPathUri.php
    @@ -0,0 +1,35 @@
    + * Contains \Drupal\migrate_drupal\Plugin\migrate\process\d6\UserPatbUri.
    

    Patb? ;) Will fix on commit. OTOH, great to see that the migration path is already taken care of! That was one of the concerns I had about all of this URI transmogrification stuff.

Rock on, folks! Onward!

Committed and pushed to 8.0.x. Thanks!

  • webchick committed a504201 on 8.0.x
    Issue #2417793 by effulgentsia, Wim Leers, YesCT, yched, dawehner: Allow...
wim leers’s picture

Just landed in Brussels. Awesome to see this landed!

larowlan’s picture

Great work!

yched’s picture

Issue ping pong :-)

#2417783: Remove widget specific logic in MenuLinkContentForm was closed as a dupe of #2417367: Use the new entity: URI scheme,
which in turn was closed as a dupe of this issue here,
which is now fixed,
but didn't address the issue described in #2417783: Remove widget specific logic in MenuLinkContentForm :-)

So, should we re-open #2417783: Remove widget specific logic in MenuLinkContentForm ?

dawehner’s picture

What you described was removed in #2417793: Allow entity: URIs to be entered in link fields IMHO.

yched’s picture

Trivial followup for #10, which was left unadressed :-)
#2418109: Misleading param name in LinkWidget::getUserEnteredStringAsUri()

yched’s picture

@dawehner : no, the lines mentioned in #2417783: Remove widget specific logic in MenuLinkContentForm in doValidate() and extractFormValues(), hardcoding logic on the widget structure, are still there.

yched’s picture

Also, wondering if the widget validation contains stuff that would rather belong to the field/TyedData constraint (LinkTypeConstraint) ?

It's always a tricky split, but roughly the rule of thumb is: if some check needs to run on non-UI saves (like REST operations), it belongs to field constraints. Widget FAPI validation is typically about stuff specific to the $form shape of that widget (and that would not necessarily make sense for a different widget shaped differently), or input errors that prevent forming a valid FieldItem to begin with.

The LinkWidget's #element_validate currently:

- forbids unrouted internal URLs (i.e. disallow 'base:' URIs)
is that form/UI specific ? Why don't we want to prevent that on REST operations too ?

- forbids linking to a URL the current user doesn't have access to
REST operations are behind user authentication too, right ? Am I allowed to POST a link value that I would be denied in the UI ?

- Disallow external URLs using untrusted protocols.
Likewise, untrusted protocols don't seem like something we'd want to allow on REST ?

Maybe this is valid, just wondering - so, not opening a followup for now :-)

wim leers’s picture

All three of those restrictions were put in place to retain the current UX in HEAD. None of them make sense to enforce at the field validation constraint level:

- disallowing base: URIs is merely an oversight/omission in HEAD, that this patch didn't want to change

- disallowing inaccessible (from the current user's POV) is a relic from the D6/D7 past; and it prevents a use case that is desired by more than a few: the ability to create design the IA/URL design of a site by creating the desired menu tree structure before those URLs actually serve the desired content

- disallowing untrusted external URLs: just like the filter system, we filter away security problems on output; i.e. strip dangerous protocols when rendering links (also applies to the previous point: access checking is performed on links at render time anyway, so there is no harm in allowing users to link to inaccessible URLs)

This is the reasoning we came to, resulting from a discussion with effulgentsia, pwolanin and I.

yched’s picture

Thanks for the explanations @Wim.

- disallowing base: URIs is merely an oversight/omission in HEAD, that this patch didn't want to change

Not sure I get this - does it mean this will be further adjusted in a separate issue ?

- disallowing inaccessible (from the current user's POV) is a relic from the D6/D7 past; and it prevents a use case that is desired by more than a few: the ability to create design the IA/URL design of a site by creating the desired menu tree structure before those URLs actually serve the desired content

OK - that desired use case seems more about non-yet-existing rather than non-accessible for a given user ? But right, if this keeps D7 behavior, changing that would be a separate feature request if anything I guess.

webchick’s picture

effulgentsia’s picture

effulgentsia’s picture

- Created a node/1 called "bananas" with a path of "bananas"
- Created two menu links: one to entity:node/1 and one to "bananas"
- Changed the path of node/1 to "bananaz"
- Expected result: first menu link continues to work, second one breaks.
- Actual result: Both menu links continue to work (?!?!)

Apparently our path system is doing something kinda magic here like remembering old paths, not sure?

The expected behavior happens correctly (well, after a a cache clear, so we're missing a cache tag for url aliases, which makes sense since those are neither content nor config entities) if you do the same scenario via a link field on a node (or any other entity) rather than a menu link. For a menu link, even a cache clear doesn't fix it, because of the materialized {menu_tree} table, but #2417837: Update menu link definitions when aliases change is for fixing that.

effulgentsia’s picture

This was the only part that gave me pause, because we've moved from pessimistic validation to optimistic validation.

Not really, because highlighting a few more lines from the committed patch:

+++ b/core/modules/link/src/Plugin/Validation/Constraint/LinkTypeConstraint.php
@@ -48,23 +48,34 @@ public function validatedBy() {
-      $url_is_valid = FALSE;
+      $uri_is_valid = TRUE;
-      $url = $link_item->getUrl(TRUE);
-      if ($url) {
-        $url_is_valid = TRUE;

The old code was also becoming optimistic so long as getUrl() returned some URL, and then proceeded to make it false later based on specific checks about that URL. So both prior HEAD and current HEAD follow similar "assume valid until you find a problem" logic. But that's the same with a lot of our validators. For example, in HEAD, UserNameConstraintValidator contains a bunch of if statements that add violations. If none of those statements evaluate to true, then no violation is added, and therefore, the constraint is satisfied. The difference with LinkTypeConstraint is just that the violation message is the same in all cases, so we have a local variable to let us keep it in one place.

I seem to remember this being raised in another issue as well... maybe by larowlan? Not going to block commit on it, but flagging it so once everyone has got a good night's rest, we can revisit this to make sure we're capturing all cases and didn't inadvertently introduce a sechole here.

If someone spots a flaw in the above reasoning, please open an issue for it.

webchick’s picture

Assigned: webchick » Unassigned

Fair enough! Thanks for checking into it.

effulgentsia’s picture

so we're missing a cache tag for url aliases

I thought about adding a new issue for that, but for now, just noted it in #2335661-23: Outbound path & route processors must specify cacheability metadata, and would like to leave it to people working on that issue to decide whether to spin off a specific sub-issue for that.

yched’s picture

[Wim #42] Disallowing base: URIs is merely an oversight/omission in HEAD, that this patch didn't want to change
[yched #43] Not sure I get this - does it mean this will be further adjusted in a separate issue ?

So, not sure if we still need a followup for this ?

[edit: never mind, #2418181: Remove 404 validation from link creation does take care of this. @effulgentsia++]

yched’s picture

FYI : #2076321-9: Link "title" validation should use a static method is about making the pre-existing LinkWidget::validateTitle() consistent with the validateUriElement() that was added here.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.