diff --git a/js/autocomplete.js b/js/autocomplete.js
index 3a98d78d5a1c3aeed11da8e7b961ce15a183a806..815b70ba1b5968effefe6518ff18a1fc5f4dadc9 100644
--- a/js/autocomplete.js
+++ b/js/autocomplete.js
@@ -26,7 +26,12 @@
* @param {object} suggestions
*/
function showSuggestions(suggestions) {
- response(suggestions.matches);
+ if (suggestions.matches.length === 0) {
+ response([{title: Drupal.t('No results')}]);
+ }
+ else {
+ response(suggestions.matches);
+ }
}
/**
@@ -63,11 +68,26 @@
function selectHandler(event, ui) {
if (ui.item.hasOwnProperty('path')) {
event.target.value = ui.item.path;
+
+ if (ui.item.hasOwnProperty('title')) {
+ $('.linkit-link-information > span').text(ui.item.title);
+ }
}
- $(document).trigger('linkit.autocomplete.select', [event, ui]);
return false;
}
+ /**
+ * Handles an autocomplete response event.
+ *
+ * @param {jQuery.Event} event
+ * @param {object} ui
+ */
+ function response(event, ui) {
+ if (ui.content.length !== 0) {
+ $('.linkit-link-information > span').text(event.target.value);
+ }
+ }
+
/**
* Override jQuery UI _renderItem function to output HTML by default.
*
@@ -157,6 +177,7 @@
renderItem: renderItem,
renderMenu: renderMenu,
select: selectHandler,
+ response: response,
minLength: 1
},
ajax: {
diff --git a/linkit.module b/linkit.module
index b4df80e13abb9f1a97b4121c7eab0c0f87af5632..fd5671e9ec1b02c824a084e4722aeca2dcd5593d 100644
--- a/linkit.module
+++ b/linkit.module
@@ -21,10 +21,8 @@ function linkit_ckeditor_plugin_info_alter(array &$plugins) {
* Implements hook_form_FORM_ID_alter().
*/
function linkit_form_editor_link_dialog_alter(&$form, FormStateInterface $form_state, $form_id) {
- $user_input = $form_state->getUserInput();
/** @var Drupal\filter\Entity\FilterFormat $filter_format */
$filter_format = $form_state->getBuildInfo()['args'][0];
- $input = isset($user_input['editor_object']) ? $user_input['editor_object'] : [];
/** @var \Drupal\Core\Entity\EntityStorageInterface $editorStorage */
$editorStorage = Drupal::service('entity.manager')->getStorage('editor');
@@ -42,12 +40,51 @@ function linkit_form_editor_link_dialog_alter(&$form, FormStateInterface $form_s
/** @var \Drupal\linkit\Entity\Profile $linkit_profile */
$linkit_profile = Drupal::entityTypeManager()->getStorage('linkit_profile')->load($linkit_profile_id);
+ $user_input = $form_state->getUserInput();
+ $input = isset($user_input['editor_object']) ? $user_input['editor_object'] : [];
+ $data_entity_type = isset($input['data-entity-type']) ? $input['data-entity-type'] : '';
+ $data_entity_uuid = isset($input['data-entity-uuid']) ? $input['data-entity-uuid'] : '';
+
+ // If the filter_html filter is activated, or any other filters using
+ // XSS:filter(), it will remove 'entity:' from the href as it thinks it's a
+ // bad protocol. We therefor have to restore the URI again when editing a
+ // link. It is possible given that data-entity-type and data-entity-uuid is
+ // set on the link element.
+ try {
+ if (!empty($data_entity_type) && !empty($data_entity_uuid)) {
+ /** @var \Drupal\Core\Entity\EntityInterface $entity */
+ $entity = \Drupal::service('entity.repository')
+ ->loadEntityByUuid($data_entity_type, $data_entity_uuid);
+ $href = 'entity:' . $entity->getEntityTypeId() . '/' . $entity->id();
+ $access = !$entity->access('view', NULL, TRUE)->isForbidden();
+ }
+ }
+ catch (Exception $exception) {
+ // Do nothing, this is handled in the finally block.
+ }
+ finally {
+ // If the href is not set, the data- attributes might not exists, or the
+ // href is external. In that case, use the given href.
+ if (!isset($href)) {
+ $href = isset($input['href']) ? $input['href'] : '';
+ }
+ }
+
+ $form['link-information'] = [
+ '#type' => 'inline_template',
+ '#template' => '
{% trans %}Links to:{% endtrans %} {{ link_target }}
',
+ '#context' => [
+ 'link_target' => !empty($entity) && !empty($access) && $access ? $entity->label() : $href,
+ ],
+ '#weight' => 2,
+ ];
+
// Everything under the "attributes" key is merged directly into the
// generated link tag's attributes.
$form['attributes']['href'] = [
'#title' => t('Link'),
'#type' => 'linkit',
- '#default_value' => isset($input['href']) ? $input['href'] : '',
+ '#default_value' => $href,
'#description' => t('Start typing to find content or paste a URL.'),
'#autocomplete_route_name' => 'linkit.autocomplete',
'#autocomplete_route_parameters' => [
@@ -77,7 +114,7 @@ function linkit_form_editor_link_dialog_alter(&$form, FormStateInterface $form_s
'linkit/linkit.imce',
],
],
- '#weight' => 1,
+ '#weight' => 10,
];
}
@@ -93,7 +130,8 @@ function linkit_form_editor_link_dialog_validate(array &$form, FormStateInterfac
// Check if the 'href' attribute contains a entity: URI.
$href = $form_state->getValue(['attributes', 'href']);
$uri_parts = parse_url($href);
- if ($uri_parts['scheme'] !== 'entity') {
+
+ if (!$uri_parts || !isset($uri_parts['scheme']) || $uri_parts['scheme'] !== 'entity') {
$form_state->setValue(['attributes', 'data-entity-type'], '');
$form_state->setValue(['attributes', 'data-entity-uuid'], '');
return;
@@ -102,20 +140,24 @@ function linkit_form_editor_link_dialog_validate(array &$form, FormStateInterfac
// Parse the entity: URI into an entity type ID and entity ID.
list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2);
+ // Check if the given entity type exists, to prevent the entity load method
+ // to throw exceptions.
+ $definition = \Drupal::entityTypeManager()->getDefinition($entity_type_id, FALSE);
+ if (is_null($definition)) {
+ $form_state->setError($form['attributes']['href'], t('Invalid URI'));
+ return;
+ }
+
// Load the entity and populate the data-entity-type and data-entity-uuid
// attributes as expected by filters.
// @see \Drupal\editor\Plugin\Filter\EditorFileReference
// @see \Drupal\linkit\Plugin\Filter\LinkitFilter
- try {
- if (!$entity = \Drupal::entityTypeManager()->getStorage($entity_type_id)->load($entity_id)) {
- $form_state->setError($form['attributes']['href'], t('Invalid URI'));
- }
- else {
- $form_state->setValue(['attributes', 'data-entity-type'], $entity_type_id);
- $form_state->setValue(['attributes', 'data-entity-uuid'], $entity->uuid());
- }
+ $entity = \Drupal::entityTypeManager()->getStorage($entity_type_id)->load($entity_id);
+ if (!empty($entity)) {
+ $form_state->setValue(['attributes', 'data-entity-type'], $entity->getEntityTypeId());
+ $form_state->setValue(['attributes', 'data-entity-uuid'], $entity->uuid());
}
- catch (Exception $exception) {
+ else {
$form_state->setError($form['attributes']['href'], t('Invalid URI'));
}
diff --git a/tests/src/FunctionalJavascript/LinkitDialogTest.php b/tests/src/FunctionalJavascript/LinkitDialogTest.php
index 91350e647336b08a934c5aa5d4df5ed03bd0052e..7786a1afdf319c38067f8f5acecf46cc3d1472a7 100644
--- a/tests/src/FunctionalJavascript/LinkitDialogTest.php
+++ b/tests/src/FunctionalJavascript/LinkitDialogTest.php
@@ -148,6 +148,11 @@ class LinkitDialogTest extends JavascriptTestBase {
$autocomplete_container = $page->find('css', 'ul.linkit-ui-autocomplete');
$this->assertFalse($autocomplete_container->isVisible());
+ // Make sure the link information is empty.
+ $javascript = "(function (){ return jQuery('.linkit-link-information > span').text(); })()";
+ $link_information = $session->evaluateScript($javascript);
+ $this->assertEquals('', $link_information, 'Link information is empty');
+
// Trigger a keydown event to active a autocomplete search.
$input_field->keyDown('f');
@@ -168,6 +173,11 @@ class LinkitDialogTest extends JavascriptTestBase {
// Make sure the href field is populated with the node uri.
$this->assertEquals('entity:' . $this->demoEntity->getEntityTypeId() . '/' . $this->demoEntity->id(), $input_field->getValue(), 'The href field is populated with the node uri');
+ // Make sure the link information is populated.
+ $javascript = "(function (){ return jQuery('.linkit-link-information > span').text(); })()";
+ $link_information = $session->evaluateScript($javascript);
+ $this->assertEquals($this->demoEntity->label(), $link_information, 'Link information is populated');
+
// Save the dialog input.
$button = $page->find('css', '.editor-link-dialog')->find('css', '.button.form-submit span');
$button->click();
@@ -179,14 +189,14 @@ class LinkitDialogTest extends JavascriptTestBase {
// have a name.
foreach (['data-entity-type' => $this->demoEntity->getEntityTypeId(), 'data-entity-uuid' => $this->demoEntity->uuid()] as $attr => $value) {
$javascript = <<evaluateScript($javascript);
$this->assertNotNull($link_attr);
diff --git a/tests/src/Kernel/LinkitEditorLinkDialogTest.php b/tests/src/Kernel/LinkitEditorLinkDialogTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..67bd7c1aca0a6a7265473b5cb2e1ef2b05ee6327
--- /dev/null
+++ b/tests/src/Kernel/LinkitEditorLinkDialogTest.php
@@ -0,0 +1,267 @@
+installEntitySchema('entity_test');
+
+ // Create a profile.
+ $this->linkitProfile = $this->createProfile();
+
+ /** @var \Drupal\linkit\MatcherManager $matcherManager */
+ $matcherManager = $this->container->get('plugin.manager.linkit.matcher');
+
+ // Add the entity_test matcher to the profile.
+ /** @var \Drupal\linkit\MatcherInterface $plugin */
+ $plugin = $matcherManager->createInstance('entity:entity_test');
+ $this->linkitProfile->addMatcher($plugin->getConfiguration());
+ $this->linkitProfile->save();
+
+ // Add a text format.
+ $this->format = FilterFormat::create([
+ 'format' => 'filtered_html',
+ 'name' => 'Filtered HTML',
+ 'weight' => 0,
+ 'filters' => [],
+ ]);
+ $this->format->save();
+
+ // Set up editor.
+ $editor = Editor::create([
+ 'format' => 'filtered_html',
+ 'editor' => 'ckeditor',
+ ]);
+ $editor->setSettings([
+ 'plugins' => [
+ 'drupallink' => [
+ 'linkit_enabled' => TRUE,
+ 'linkit_profile' => $this->linkitProfile->id(),
+ ],
+ ],
+ ]);
+ $editor->save();
+ }
+
+ /**
+ * Tests adding a link.
+ */
+ public function testAdd() {
+ $entity_label = $this->randomString();
+ $entity = EntityTest::create(['name' => $entity_label]);
+ $entity->save();
+
+ $form_object = new EditorLinkDialog();
+
+ $input = [
+ 'editor_object' => [],
+ 'dialogOptions' => [
+ 'title' => 'Add Link',
+ 'dialogClass' => 'editor-link-dialog',
+ 'autoResize' => 'true',
+ ],
+ '_drupal_ajax' => '1',
+ 'ajax_page_state' => [
+ 'theme' => 'bartik',
+ 'theme_token' => 'some-token',
+ 'libraries' => '',
+ ],
+ ];
+ $form_state = (new FormState())
+ ->setRequestMethod('POST')
+ ->setUserInput($input)
+ ->addBuildInfo('args', [$this->format]);
+
+ /** @var \Drupal\Core\Form\FormBuilderInterface $form_builder */
+ $form_builder = $this->container->get('form_builder');
+ $form_id = $form_builder->getFormId($form_object, $form_state);
+ $form = $form_builder->retrieveForm($form_id, $form_state);
+ $form_builder->prepareForm($form_id, $form, $form_state);
+ $form_builder->processForm($form_id, $form, $form_state);
+
+ $this->assertEquals('linkit.autocomplete', $form['attributes']['href']['#autocomplete_route_name'], 'Linkit is enabled on the href field.');
+ $this->assertEquals('', $form['attributes']['href']['#default_value'], 'The href attribute is empty.');
+ $this->assertEquals('', $form['link-information']['#context']['link_target'], 'Link information is empty.');
+
+ $form_state->setValue(['attributes', 'href'], 'entity:missing_entity/1');
+ $form_builder->submitForm($form_object, $form_state);
+ $this->assertNotEmpty($form_state->getErrors(), 'Got validation errors for none existing entity type.');
+
+ $form_state->setValue(['attributes', 'href'], 'url_without_schema');
+ $form_builder->submitForm($form_object, $form_state);
+ $this->assertEmpty($form_state->getErrors(), 'Got no validation errors for url without schema.');
+ $this->assertEquals('', $form_state->getValue(['attributes', 'data-entity-type']));
+ $this->assertEquals('', $form_state->getValue(['attributes', 'data-entity-uuid']));
+
+ $form_state->setValue(['attributes', 'href'], 'entity:entity_test/1');
+ $form_builder->submitForm($form_object, $form_state);
+ $this->assertEmpty($form_state->getErrors(), 'Got no validation errors for correct URI.');
+ $this->assertEquals($entity->getEntityTypeId(), $form_state->getValue(['attributes', 'data-entity-type']), 'Attribute "data-entity-type" exists and has the correct value.');
+ $this->assertEquals($entity->uuid(), $form_state->getValue(['attributes', 'data-entity-uuid']), 'Attribute "data-entity-uuid" exists and has the correct value.');
+ }
+
+ /**
+ * Tests editing a link with data attributes.
+ */
+ public function testEditWithDataAttributes() {
+ $entity_label = $this->randomString();
+ $entity = EntityTest::create(['name' => $entity_label]);
+ $entity->save();
+
+ $entity_no_access = EntityTest::create(['name' => 'forbid_access']);
+ $entity_no_access->save();
+
+ $form_object = new EditorLinkDialog();
+
+ $input = [
+ 'editor_object' => [
+ 'href' => 'entity:entity_test/1',
+ 'data-entity-type' => $entity->getEntityTypeId(),
+ 'data-entity-uuid' => $entity->uuid(),
+ ],
+ 'dialogOptions' => [
+ 'title' => 'Edit Link',
+ 'dialogClass' => 'editor-link-dialog',
+ 'autoResize' => 'true',
+ ],
+ '_drupal_ajax' => '1',
+ 'ajax_page_state' => [
+ 'theme' => 'bartik',
+ 'theme_token' => 'some-token',
+ 'libraries' => '',
+ ],
+ ];
+ $form_state = (new FormState())
+ ->setRequestMethod('POST')
+ ->setUserInput($input)
+ ->addBuildInfo('args', [$this->format]);
+
+ /** @var \Drupal\Core\Form\FormBuilderInterface $form_builder */
+ $form_builder = $this->container->get('form_builder');
+ $form_id = $form_builder->getFormId($form_object, $form_state);
+ $form = $form_builder->retrieveForm($form_id, $form_state);
+ $form_builder->prepareForm($form_id, $form, $form_state);
+ $form_builder->processForm($form_id, $form, $form_state);
+
+ $this->assertEquals('linkit.autocomplete', $form['attributes']['href']['#autocomplete_route_name'], 'Linkit is enabled on the href field.');
+ $this->assertEquals('entity:entity_test/1', $form['attributes']['href']['#default_value'], 'The href attribute is empty.');
+ $this->assertEquals($entity->label(), $form['link-information']['#context']['link_target'], 'Link information is empty.');
+
+ // Make sure the dialog don't display entity labels for inaccessible
+ // entities.
+ $input = [
+ 'editor_object' => [
+ 'href' => 'entity:entity_test/2',
+ 'data-entity-type' => $entity_no_access->getEntityTypeId(),
+ 'data-entity-uuid' => $entity_no_access->uuid(),
+ ],
+ 'dialogOptions' => [
+ 'title' => 'Edit Link',
+ 'dialogClass' => 'editor-link-dialog',
+ 'autoResize' => 'true',
+ ],
+ '_drupal_ajax' => '1',
+ 'ajax_page_state' => [
+ 'theme' => 'bartik',
+ 'theme_token' => 'some-token',
+ 'libraries' => '',
+ ],
+ ];
+ $form_state = (new FormState())
+ ->setRequestMethod('POST')
+ ->setUserInput($input)
+ ->addBuildInfo('args', [$this->format]);
+
+ /** @var \Drupal\Core\Form\FormBuilderInterface $form_builder */
+ $form_builder = $this->container->get('form_builder');
+ $form_id = $form_builder->getFormId($form_object, $form_state);
+ $form = $form_builder->retrieveForm($form_id, $form_state);
+ $form_builder->prepareForm($form_id, $form, $form_state);
+ $form_builder->processForm($form_id, $form, $form_state);
+
+ $this->assertEquals('linkit.autocomplete', $form['attributes']['href']['#autocomplete_route_name'], 'Linkit is enabled on the href field.');
+ $this->assertEquals('entity:entity_test/2', $form['attributes']['href']['#default_value'], 'The href attribute is empty.');
+ $this->assertEquals('entity:entity_test/2', $form['link-information']['#context']['link_target'], 'Link information is empty.');
+ }
+
+ /**
+ * Tests editing a link without data attributes.
+ */
+ public function testEditWithoutDataAttributes() {
+ $form_object = new EditorLinkDialog();
+
+ $input = [
+ 'editor_object' => [
+ 'href' => 'http://example.com/',
+ ],
+ 'dialogOptions' => [
+ 'title' => 'Edit Link',
+ 'dialogClass' => 'editor-link-dialog',
+ 'autoResize' => 'true',
+ ],
+ '_drupal_ajax' => '1',
+ 'ajax_page_state' => [
+ 'theme' => 'bartik',
+ 'theme_token' => 'some-token',
+ 'libraries' => '',
+ ],
+ ];
+ $form_state = (new FormState())
+ ->setRequestMethod('POST')
+ ->setUserInput($input)
+ ->addBuildInfo('args', [$this->format]);
+
+ /** @var \Drupal\Core\Form\FormBuilderInterface $form_builder */
+ $form_builder = $this->container->get('form_builder');
+ $form_id = $form_builder->getFormId($form_object, $form_state);
+ $form = $form_builder->retrieveForm($form_id, $form_state);
+ $form_builder->prepareForm($form_id, $form, $form_state);
+ $form_builder->processForm($form_id, $form, $form_state);
+
+ $this->assertEquals('linkit.autocomplete', $form['attributes']['href']['#autocomplete_route_name'], 'Linkit is enabled on the href field.');
+ $this->assertEquals('http://example.com/', $form['attributes']['href']['#default_value'], 'The href attribute is empty.');
+ $this->assertEquals('http://example.com/', $form['link-information']['#context']['link_target'], 'Link information is empty.');
+ }
+
+}