Postponed on #2407505: [meta] Finalize the menu links (and other user-entered paths) system

Problem/Motivation

Despite issue 1879120, link URL entry in the WYSIWYG is inconsistent with link URL entry in the link field. To link to a specific node: in the WYSIWYG, you have to write "/node/#", in the Link URL field, you would write "node/#".
This is a user experience issue.

Proposed resolution

Make link URL entry consistent between how URLs are added in the WYSIWYG, and how link URLs are added in the link field.

EditorLinkDialog should transform Drupal paths, like the Link field does (LinkWidget::massageFormValues

Contributor tasks needed
Task Novice task? Contributor instructions Complete?

(); ). EditorLinkDialog should not do this by pre-prending a slash because this would not work for sites installed in a subdirectory.

Remaining tasks

1. Complete patch - was blocked by #2347469 ?
2. Continue discussion of how it should work (output should be root relative path, drupal would store whatever user entered)
3. Add tests

User interface changes

In LinkDialog a user would enter "node/#" like they would do in a link field.

Original report by Charles Belov

Postponed on #2407505: [meta] Finalize the menu links (and other user-entered paths) system

Despite issue 1879120, link URL entry in the WYSIWYG is not consistent with link URL entry in the link field.

Steps:
1. Create a new Drupal 8.0-alpha12 website at simplytest.me
2. Enable the Link module
3. Add a link field to the Basic Page content type
4. Add content > Basic page > title "test page 1" (without the quotes) > Save and publish
5. Add content > Basic page
6. Title "test page 2"
7. Body - type "test page 1"
8. Select text "test page 1"
9. Click link icon
10. In the URL field, type "node/1"
11. Click OK
12. In the Basic Page Link URL field, type "node/1"
13. In the Basic Page Link Link text field, type "test page 1"
14. Click Save and publish
15. Click "test page 1" link corresponding to link field

Actual and expected results: Goes to [protocol]://[domain]/node/1 and displays test page 1

16. Click Back button
17. Click "test page 1" link corresponding to link in Body text

Actual result: Goes to [protocol]://[domain]/node/node/1 and displays not found error
Expected result: Goes to [protocol]://[domain]/node/1 and displays test page 1

Comments

charles belov’s picture

Issue tags: +Usability

Added Usability tag

wim leers’s picture

Title: Make ckeditor wysiwyg URL field entry consistent with Drupal URL field entry » Make EditorLinkDialog URL field entry consistent with Drupal URL field entry
Component: ckeditor.module » editor.module
wim leers’s picture

Issue tags: +Novice, +php-novice
tomogden’s picture

Even stranger, when I hover over the saved link in the body field while in Edit, the URL it sees is [protocol]://[domain]/node/2/node/1 . Does that offer any clues?

tomogden’s picture

So the rendered link in the body is being written without the leading slash, node/1 which the browser interprets as relative to the current page url.

By contrast the link field is adding a leading slash, /node/1 which the browser sees as relative to the domain's root.

This is the way Drupal has worked for years, so I am wont to say "It works as designed." But then you have to admit that there is a blatant inconsistency with the way the link field works.

So now that we know what the difference is, what is the prevailing community guideline or opinion on what behavior we should preserve and which to deprecate?

charles belov’s picture

The biggest argument in making the two link methods consistent is that the inconsistency is a barrier to newcomers and a training issue.

cs_shadow’s picture

Version: 8.0-alpha12 » 8.x-dev

Issue exists in the latest dev release also.

cs_shadow’s picture

Component: editor.module » ckeditor.module

Adding a slash at the beginning of node/1 will not work if your site is located in a subdirectory like: example.com/drupal. In that case the field the URL field adds the subdirectory path also which is not done in case of CKEditor.

Changing the component to CKEditor since the anchor element is created by CKEditor plugin and not by EditorLinkDialog (correct me if I'm wrong). Relevant code is in core/modules/ckeditor/js/plugins/drupallink/plugin.js:

 66               // Create the new link by applying a style to the new text.                                                                         
 67               var style = new CKEDITOR.style({ element: 'a', attributes: returnValues.attributes });                                              
wim leers’s picture

Title: Make EditorLinkDialog URL field entry consistent with Drupal URL field entry » Make EditorLinkDialog URL field validate URLs
Component: ckeditor.module » editor.module
Category: Feature request » Bug report
Priority: Normal » Major
Issue tags: +CKEditor in core

Everything in the issue summary can be summarized as follows: EditorLinkDialog simply accepts whatever string it is given; it doesn't perform any validation or transformation. Hence it also does not transform "Drupal paths" (which aren't actual URLs) to URLs, but e.g. the Link field does this, so EditorLinkDialog probably should do the same.

It's not CKEditor's responsibility to transform these, so this definitely belongs in the editor.module component.

wim leers’s picture

See LinkWidget::massageFormValues(); that's how the Link field does it, and how we should do it.

wim leers’s picture

Issue tags: +Needs tests

Oh, and as cs_shadow says in #8: merely prepending a slash will not work when Drupal is installed in a subdirectory.

emclaughlin’s picture

I tried to take a look at this but I'm having a problem as I'm not quite sure where to put any of the code. There's LinkWidget::massageFormValues(), yes, but LinkWidget and the other classes with this function extend WidgetBase whereas EditorLinkDialog extends FormBase. Would the validation and the transformation both occur in a validateForm function? That feels weird to me, to be editing a value while in a validate function, but I may be wrong about that.

Any insights, anyone?

wim leers’s picture

#12: Thanks for jumping in and giving it a shot! :)

I can understand your confusion. LinkWidget is a Field API widget. Therefore it is more abstract, and it indeed doesn't translate 1:1. A Field API widget is sufficiently abstract so that Drupal can automatically build forms that include one or more link widgets.
Compare that to EditorLinkDialog, which indeed just extends FormBase, which indicates what it is: it is nothing more than a simple form.
So rather than being an abstract building block of a form (LinkWidget), we're dealing with a simple form (EditorLinkDialog).

Basically, LinkWidget::massageFormValues() is more complex than what we need: it needs to handle multiple values (because a link field may be configured to accept multiple values, hence multiple URLs), but we only need to handle *one*!
So you can take the key parts of the logic:

$parsed_url = UrlHelper::parse($value['url']);

          // If internal links are supported, look up whether the given value is
          // a path alias and store the system path instead.
          if ($this->supportsInternalLinks() && !UrlHelper::isExternal($value['url'])) {
            $parsed_url['path'] = \Drupal::service('path.alias_manager')->getPathByAlias($parsed_url['path']);
          }

          // et cetera

and indeed put it in a new EditorLinkDialog::validateForm() method. I agree that it can feel bizarre to be altering/"cleaning up"/"massaging" a form value during the "validate" phase, but since Drupal's Form API doesn't have a separate "massage" stage, that's how it's done!

So: you were very close, your hunches were right, your sense of code smell was right, but… it's in fact how it needs to be done in today's Drupal — hopefully we can remove that code smell in the future — perhaps with your help! :)

emclaughlin’s picture

Assigned: Unassigned » emclaughlin
Status: Active » Needs review
StatusFileSize
new2.7 KB

Thanks @wim-leers!

I think I managed to take the key parts of the logic and apply them to validateForm(). I've attached a patch for review.

Status: Needs review » Needs work

The last submitted patch, 14: Drupal_core-validate_urls-2292159-13.patch, failed testing.

emclaughlin’s picture

Status: Needs work » Needs review
StatusFileSize
new2.7 KB

Okay, let's try this again with a patch that isn't corrupted.

wim leers’s picture

Status: Needs review » Needs work

Wonderful, this is looking great already! :)

There's one problem though: if I enter node/1, that gets converted correctly to /node/1, but if I then try to edit that link again, save immediately without changing it (so saving /node/1 instead of node/1), it tells me it's invalid. We'll need to fix that. I think the easiest solution would be to tell Url::createFromPath() to generate an absolute URL (http://example.com/node/1)for us instead of a relative one (/node/1). You can do that by calling ->setAbsolute(TRUE).

Code review:

  1. +++ b/core/modules/editor/src/Form/EditorLinkDialog.php
    @@ -79,6 +84,46 @@ public function buildForm(array $form, FormStateInterface $form_state, FilterFor
    +      $url->setOption('attributes', array('target' => $attr['target']));
    ...
    +      $form_state->setValue('attributes', array('href' => $path, 'target' => $attr['target']));
    

    Setting the target attribute is not necessary here, because Url will ignore it. After all the <a target="something"> attribute is something that lives at the link level, not the URL level.

  2. +++ b/core/modules/editor/src/Form/EditorLinkDialog.php
    @@ -79,6 +84,46 @@ public function buildForm(array $form, FormStateInterface $form_state, FilterFor
    +    catch (NotFoundHttpException $e) {
    +      $form_state->setErrorByName('attributes', $this->t('Failed to save URL. The server reports the following message: !message', array(
    +        '!message' => '<p class="error">' . $e->getMessage()  . '</p>',
    +      )));
    +    }
    +    catch (MatchingRouteNotFoundException $e) {
    +      $form_state->setErrorByName('attributes', $this->t('Failed to save URL. The server reports the following message: !message', array(
    +        '!message' => '<p class="error">' . $e->getMessage()  . '</p>',
    +      )));
    +    }
    +    catch (ParamNotConvertedException $e) {
    +      $form_state->setErrorByName('attributes', $this->t('Failed to save URL. The server reports the following message: !message', array(
    +        '!message' => '<p class="error">' . $e->getMessage()  . '</p>',
    +      )));
    +    }
    

    First: we don't want to expose the exception's error message to the end user — this is information for developers, not for end users. It would only be very confusing. :)
    I think think the message could just say something like The entered path does not exist. in all cases, without any further information.

    Second: there's a lot of repetition here! But we cannot combine the catching of all these exceptions in a single "catch" block. However… because Drupal 8 requires a fairly recent version of PHP, we can just create an anonymous function inside of this function to always do the same thing.

    So at the top of validateForm() you could do something like:

    $show_error = function(\Exception $e) {
      $form_state->setErrorByName(…);
    };
    

    And then call $show_error($e) in all three "catch" blocks!

emclaughlin’s picture

Using ->setAbsolute(TRUE) creates a problem, though, in cases where you don't want WYSIWYG links to be absolute URLs. Say you were having a user create content on a dev site and then merging the database up to the prod site pre-launch. The links would all still be pointing to the dev site, right?

charles belov’s picture

Same thing goes for if the staff user logged in using a www-free address and other staff users log in with the www version.

Same thing goes for http (anonymous) vs. https (staff). That said, we do expect to migrate to https for all because of Google's announced plan to rank https sites higher.

But it doesn't belong in the in-page links.

wim leers’s picture

Using ->setAbsolute(TRUE) creates a problem, though, in cases where you don't want WYSIWYG links to be absolute URLs. Say you were having a user create content on a dev site and then merging the database up to the prod site pre-launch. The links would all still be pointing to the dev site, right?

This is true, but:

  1. It's also the case for the relative URLs that the current patch creates! If your dev site is installed in a subdirectory and your production one isn't, then your links will look like /mysubdirectory/node/1 instead of /node/1.
  2. This is a general problem, the only way to solve this is by not actually generating a link, but storing data- attributes that a filter transforms to the correct links for the current site.

This issue is the first step: make EditorLinkDialog generate URLs at all from "Drupal paths" (like node/1 and user/5). The next step would be to create links in such a way that they're automatically adapted to the current site.

That being said, I'm fine with root-relative URLs, but then you will have to adjust the validation :) That's why I suggested absolute URLs as the solution: it's the easiest solution.


As you can tell, there is no solution that is both simple and solves everything!

charles belov’s picture

@Wim Leers #17

There's one problem though: if I enter node/1, that gets converted correctly to /node/1, but if I then try to edit that link again, save immediately without changing it (so saving /node/1 instead of node/1), it tells me it's invalid.

Actually, the field should always display node/1 to the user in the WYSIWYG dialog to be consistent with a link (module) URL field. If Drupal wants to store it internally as /node/1, that's fine, but the complexity needs to be hidden from the user.

The whole idea of this issue was to make data entry and presentation consistent for the user between the link module URL field an the WYSIWYG URL field.

The last thing we want is having the user see the WYSIWYG URL field "corrected" to /node/1, thinking they also need to do that in a link URL field, and getting the wrong results.

We often have both WYSIWYG and link URLs in the same content type, so it's important that they be presented consistently to the user.

wim leers’s picture

Good points regarding usability in #21. But that does mean this patch becomes more complex, because it will be necessary to store the path corresponding on the <a> element then, e.g.: <a data-drupal-internal-link="node/1">.

Are you still up for that, emclaughlin? :)

emclaughlin’s picture

Wouldn't the link needing to be whatever the user put in in the WYSIWYG and needing to be an absolute path when outputted suggest that the validation function should be used purely to validate the link, not change it, and then the link be updated to the absolute URL of the site it's on during rendering?

wim leers’s picture

#23: that's absolutely correct!

charles belov’s picture

For clarification, does #23 mean that internal code <a data-drupal-internal-link="node/1"> would be output for site http://www.example.com as <a href="http://www.example.com/node/1"?

wim leers’s picture

It could either be absolute (as in your example) or root-relative (/node/1). It could be configurable on the filter.

charles belov’s picture

I'd prefer /node/1 on output, but that's a community decision. As long as it's node/1 in the dialog box, either way on output is fine with me.

bneil’s picture

StatusFileSize
new177.86 KB

In testing #16, the patch applied, with the following message: "Hunk #2 succeeded at 85 (offset 1 line)."

I tried testing the current functionality of patch #16 before working on addressing some of the points in #17-27, but I am running into an ajax error and the dialog will not close or present the error messages:

Screenshot of AJAX error message

I'd be interested in working through addressing the points in the above comments if someone would be willing to help me figure out the dialog issues.

Tested with Drupal 8.0.x with the Standard profile.

emclaughlin’s picture

So, I'm having some trouble figuring out where the links are rendered so that I can make sure that links are being rendered as /node/1 and not node/1. Does anyone have any guidance on this?

emclaughlin’s picture

@bneil The patch no longer applies correctly because code has been changed since it was created. I've got an updated patch for validation but was waiting to attach it to the post until I could figure out the rendering issue.

emclaughlin’s picture

StatusFileSize
new3.25 KB

Okay, nope, new question, figured out the last one by rereading the thread and realizing I was doing everything wrong.

I've attached the patch I've created so far here. The problem is, when you input an invalid URL, and then input a valid one, the form redirects off to /editor/dialog/link/basic_html. I'm not sure how to track down where/why this is happening. Can anyone provide any guidance?

bneil’s picture

@mclaughlin -
I believe the issue you're running into is #2347469: Rendering forms in AjaxResponses does not attach assets automatically. Note the "referenced by" issues all relating to that problem.

wim leers’s picture

Status: Needs work » Postponed

Actually, the lack of clarity of how this should work points to a wider problem. And that wider problem is captured in #2407505: [meta] Finalize the menu links (and other user-entered paths) system, which is going to be the focus of the D8 criticals in New Jersey next week. Hopefully we'll come out of that week with all of those things solved, which should make this issue significantly simpler!

wim leers’s picture

yesct’s picture

Issue summary: View changes
wim leers’s picture

Title: Make EditorLinkDialog URL field validate URLs » EditorLinkDialog should validate URLs, and autocomplete like the Link widget
xjm’s picture

Status: Postponed » Active

I think this should be unpostponed now? The majority of the critical work for #2407505: [meta] Finalize the menu links (and other user-entered paths) system is done. If there's a specific issue that's still blocking this, let's postpone it on that rather than the meta. Thanks!

disasm’s picture

This could use an issue summary update with clear next tasks to be done in the issue. Also need tests written for this.

anon’s picture

So there is no need for me to port the Linkit module to D8 now?

adriancotter’s picture

Issue summary: View changes
emclaughlin’s picture

Assigned: emclaughlin » Unassigned
charles belov’s picture

Has it been decided that Drupal will be consistent as to whether a user will enter /node/1 or node/1 throughout the Drupal UI? I don't care what it is internally, but my whole point in filing #2292159: EditorLinkDialog should validate URLs, and autocomplete like the Link widget was that it's confusing to enter /node/1 in one place in the Drupal UI and node/1 in another place in the Drupal UI. Now I read an article (linked to from the latest Drupal Association newsletter) that says Drupal 8 is inconsistent on leading slashes.

I looked at the meta issue #2407505: [meta] Finalize the menu links (and other user-entered paths) system and the linked-to meeting notes appear to be at a more technical level than my Drupal experience level.

Is there a plain-english meta issue that says whether the goal is to resolve to leading slashes, resolve to no leading slashes, or not to resolve these inconsistencies? If there is some other meta, please relate this issue to it.

What is the impact of the closing of #2407505: [meta] Finalize the menu links (and other user-entered paths) system on #2292159: EditorLinkDialog should validate URLs, and autocomplete like the Link widget?

sidharthap’s picture

Issue summary: View changes
Issue tags: -Novice, -php-novice

It seems Novice tag was added 2 years back. As per #42 it required more discussion on it. It is no longer to be a Novice issue. I am removing the Novice tag.

wim leers’s picture

A similar feature request was posted at #2689661: Add CKEditor Link to core.

Benia’s picture

Indeed I suggested it for several reasons but the most important I think, is when a domain and or a TLD is changed, and there is no suitable page2page redirecting mechanism. Moreover, when someone moves from Drupal 7 to Drupal 8 and needs to work with another similar module and copy all links manually in each page (No one can promise, I guess, that a similar situation won't happen in Drupal 9).

From an SEO viewpoint, Non-boilerplate internal-links ("content region" internal-links in Drupalese), are important and can give better information-navigation and lower a chance for what can be defined as "fast remission", that is, when the client goes back to fast to Google and checking other results.

Making this tool most accessible and intuitive in Drupal could benefit all users in these contexts.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev
larowlan’s picture

Category: Bug report » Feature request
Issue tags: +Bug Smash Initiative

This sounds like adding link-it to core, which feels like a feature request rather than a bug

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

wim leers’s picture