Commit 9632e2f5 authored by anon's avatar anon Committed by anon

Issue #2844466 by anon, ckaotik: Insert external link on form submit

parent f6a24536
......@@ -43,7 +43,10 @@
response(autocomplete.cache[elementId][term]);
}
else {
var options = $.extend({success: sourceCallbackHandler, data: {q: term}}, autocomplete.ajax);
var options = $.extend({
success: sourceCallbackHandler,
data: {q: term}
}, autocomplete.ajax);
$.ajax(this.element.attr('data-autocomplete-path'), options);
}
}
......@@ -62,28 +65,23 @@
function selectHandler(event, ui) {
var $form = $(event.target).closest('form');
if (ui.item.path != null) {
$('input[name="attributes[href]"]', $form).val(ui.item.path);
$('input[name="attributes[data-entity-type]"]', $form).val('');
$('input[name="attributes[data-entity-uuid]"]', $form).val('');
$('input[name="attributes[data-entity-substitution]"]', $form).val('');
event.target.value = ui.item.path;
if (!ui.item.path) {
throw 'Missing path param.' + JSON.stringify(ui.item);
}
else {
$('input[name="attributes[href]"]', $form).val(ui.item.path);
if (ui.item.entity_type_id || ui.item.entity_uuid || ui.item.substitution_id) {
if (!ui.item.entity_type_id || !ui.item.entity_uuid || !ui.item.substitution_id) {
throw 'Missing data params.' + JSON.stringify(ui.item);
throw 'Missing path param.' + JSON.stringify(ui.item);
}
// The href needs to be set in order for the drupallink saveCallback to
// insert new anchor elements.
$('input[name="attributes[href]"]', $form).val('#');
$('input[name="attributes[data-entity-type]"]', $form).val(ui.item.entity_type_id);
$('input[name="attributes[data-entity-uuid]"]', $form).val(ui.item.entity_uuid);
$('input[name="attributes[data-entity-substitution]"]', $form).val(ui.item.substitution_id);
event.target.value = ui.item.label;
}
$('.linkit-link-information > span', $form).text(ui.item.label);
event.target.value = ui.item.path;
return false;
}
......@@ -163,6 +161,10 @@
// Use jQuery UI Autocomplete on the textfield.
$autocomplete.autocomplete(autocomplete.options);
$autocomplete.autocomplete('widget').addClass('linkit-ui-autocomplete');
$autocomplete.click(function () {
$autocomplete.autocomplete('search', $autocomplete.val());
});
}
},
detach: function (context, settings, trigger) {
......
......@@ -84,48 +84,16 @@ function linkit_form_editor_link_dialog_alter(&$form, FormStateInterface $form_s
}
$form['linkit'] = [
'#title' => t('Link'),
'#title' => t('URL'),
'#type' => 'linkit',
'#description' => t('Start typing to find content or paste a URL.'),
'#description' => t('Start typing to find content.'),
'#maxlength' => 2048,
'#autocomplete_route_name' => 'linkit.autocomplete',
'#autocomplete_route_parameters' => [
'linkit_profile_id' => $linkit_profile_id,
],
"#weight" => -10,
];
// Determine what the default value of the linkit autocomplete field.
try {
if (!empty($input['data-entity-type']) && !empty($input['data-entity-uuid'])) {
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = \Drupal::service('entity.repository')
->loadEntityByUuid($input['data-entity-type'], $input['data-entity-uuid']);
$entity = \Drupal::service('entity.repository')
->getTranslationFromContext($entity);
$access = !$entity->access('view', NULL, TRUE)->isForbidden();
$autocomplete_default_value = !empty($access) && $access ? $entity->label() : '';
}
}
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($autocomplete_default_value)) {
$autocomplete_default_value = isset($input['href']) ? $input['href'] : '';
}
}
$form['linkit']['#default_value'] = $autocomplete_default_value;
$form['link-information'] = [
'#type' => 'inline_template',
'#template' => '<div class="form-item linkit-link-information"><strong>{% trans %}Selected link:{% endtrans %}</strong> <span>{{ link_target }}</span></div>',
'#context' => [
'link_target' => !empty($entity) && !empty($access) && $access ? $entity->label() : $autocomplete_default_value,
],
"#weight" => -10,
'#default_value' => isset($input['href']) ? $input['href'] : '',
];
$fields = [
......@@ -139,6 +107,7 @@ function linkit_form_editor_link_dialog_alter(&$form, FormStateInterface $form_s
foreach ($fields as $field_name) {
$form['attributes'][$field_name] = [
'#title' => $field_name,
'#type' => 'hidden',
'#default_value' => isset($input[$field_name]) ? $input[$field_name] : '',
];
......@@ -178,19 +147,32 @@ function linkit_form_editor_link_dialog_alter(&$form, FormStateInterface $form_s
function linkit_form_editor_link_dialog_submit(array &$form, FormStateInterface $form_state) {
$link_element = $form_state->get('link_element');
$data_entity_type = $form_state->getValue(['attributes', 'data-entity-type']);
$data_entity_uuid = $form_state->getValue(['attributes', 'data-entity-uuid']);
$href = $form_state->getValue(['attributes', 'href']);
$linkit_field = $form_state->getValue(['linkit']);
if (empty($data_entity_type) || empty($data_entity_uuid)) {
if (!empty($link_element)) {
$form_state->setValue(['attributes', 'data-entity-type'], '');
$form_state->setValue(['attributes', 'data-entity-uuid'], '');
$form_state->setValue(['attributes', 'data-entity-substitution'], '');
}
else {
$form_state->unsetValue(['attributes', 'data-entity-type']);
$form_state->unsetValue(['attributes', 'data-entity-uuid']);
$form_state->unsetValue(['attributes', 'data-entity-substitution']);
if ($href !== $linkit_field) {
$form_state->setValue(['attributes', 'href'], $linkit_field);
$form_state->unsetValue(['attributes', 'data-entity-type']);
$form_state->unsetValue(['attributes', 'data-entity-uuid']);
$form_state->unsetValue(['attributes', 'data-entity-substitution']);
}
$fields = [
'href',
'data-entity-type',
'data-entity-uuid',
'data-entity-substitution',
];
foreach ($fields as $field_name) {
$value = $form_state->getValue(['attributes', $field_name]);
if (empty($value)) {
if (!empty($link_element)) {
$form_state->setValue(['attributes', $field_name], '');
}
else {
$form_state->unsetValue(['attributes', $field_name]);
}
}
}
}
......@@ -12,12 +12,14 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\linkit\ConfigurableMatcherBase;
use Drupal\linkit\MatcherTokensTrait;
use Drupal\linkit\SubstitutionManagerInterface;
use Drupal\linkit\Suggestion\EntitySuggestion;
use Drupal\linkit\Suggestion\SuggestionCollection;
use Drupal\linkit\Utility\LinkitXss;
use Exception;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -287,7 +289,9 @@ class EntityMatcher extends ConfigurableMatcherBase {
public function execute($string) {
$suggestions = new SuggestionCollection();
$query = $this->buildEntityQuery($string);
$result = $query->execute();
$query_result = $query->execute();
$url_results = $this->findEntityIdByUrl($string);
$result = array_merge($query_result, $url_results);
if (empty($result)) {
return $suggestions;
......@@ -299,7 +303,7 @@ class EntityMatcher extends ConfigurableMatcherBase {
// Check the access against the defined entity access handler.
/** @var \Drupal\Core\Access\AccessResultInterface $access */
$access = $entity->access('view', $this->currentUser, TRUE);
if ($access->isForbidden()) {
if (!$access->isAllowed()) {
continue;
}
......@@ -311,7 +315,8 @@ class EntityMatcher extends ConfigurableMatcherBase {
->setDescription($this->buildDescription($entity))
->setEntityUuid($entity->uuid())
->setEntityTypeId($entity->getEntityTypeId())
->setSubstitutionId($this->configuration['substitution_type']);
->setSubstitutionId($this->configuration['substitution_type'])
->setPath($this->buildPath($entity));
$suggestions->addSuggestion($suggestion);
}
......@@ -415,4 +420,43 @@ class EntityMatcher extends ConfigurableMatcherBase {
return $group;
}
/**
* Builds the path used in the match array.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The matched entity.
*
* @return string
* The path for this entity.
*/
protected function buildPath(EntityInterface $entity) {
return $entity->toUrl('canonical', ['path_processing' => FALSE])->toString();
}
/**
* Finds entity id from the given input.
*
* @param string $user_input
* The string to url parse.
*
* @return array
* An array with an entity id if the input can be parsed as an internal url
* and a match is found, otherwise an empty array.
*/
protected function findEntityIdByUrl($user_input) {
$result = [];
try {
$params = Url::fromUserInput($user_input)->getRouteParameters();
if (key($params) === $this->targetType) {
$result = [end($params)];
}
}
catch (Exception $e) {
// Do nothing.
}
return $result;
}
}
......@@ -213,4 +213,12 @@ class FileMatcher extends EntityMatcher {
return LinkitXss::descriptionFilter($description);
}
/**
* {@inheritdoc}
*/
protected function buildPath(EntityInterface $entity) {
/** @var \Drupal\file\FileInterface $entity */
return file_url_transform_relative(file_create_url($entity->getFileUri()));
}
}
......@@ -43,13 +43,6 @@ class LinkitDialogTest extends JavascriptTestBase {
*/
protected $ckeditor;
/**
* A demo entity.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $demoEntity;
/**
* A linkit profile.
*
......@@ -124,6 +117,7 @@ class LinkitDialogTest extends JavascriptTestBase {
'create page content',
'edit own page content',
'use text format llama',
'view test entity',
]);
$this->drupalLogin($account);
......@@ -144,12 +138,10 @@ class LinkitDialogTest extends JavascriptTestBase {
}
// Create a test entity.
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = EntityTestMul::create(['name' => 'Foo']);
$entity->save();
// Create test nodes.
$this->demoEntity = $entity;
// Go to node creation page.
$this->drupalGet('node/add/page');
......@@ -178,9 +170,6 @@ class LinkitDialogTest extends JavascriptTestBase {
$this->assertEmptyWithJs('attributes[data-entity-substitution]');
$this->assertEmptyWithJs('attributes[href]');
// Make sure the link information is empty.
$this->assertEmpty($this->getLinkInfoText(), 'Link information is empty');
// Make sure the autocomplete result container is hidden.
$autocomplete_container = $page->find('css', 'ul.linkit-ui-autocomplete');
$this->assertFalse($autocomplete_container->isVisible());
......@@ -201,17 +190,14 @@ class LinkitDialogTest extends JavascriptTestBase {
// Find the first result and click it.
$page->find('xpath', '(//li[contains(@class, "linkit-result") and contains(@class, "ui-menu-item")])[1]')->click();
// Make sure the linkit field field is populated with the node label.
$this->assertEquals($this->demoEntity->label(), $linkit_field->getValue(), 'The linkit field is populated with the node label.');
// Make sure the link information is populated.
$this->assertEquals($this->demoEntity->label(), $this->getLinkInfoText(), 'Link information is populated');
// Make sure the linkit field field is populated with the node url.
$this->assertEquals($entity->toUrl()->toString(), $linkit_field->getValue(), 'The linkit field is populated with the node url.');
// Make sure all other fields are populated.
$this->assertEqualsWithJs('attributes[data-entity-type]', $this->demoEntity->getEntityTypeId());
$this->assertEqualsWithJs('attributes[data-entity-uuid]', $this->demoEntity->uuid());
$this->assertEqualsWithJs('attributes[data-entity-type]', $entity->getEntityTypeId());
$this->assertEqualsWithJs('attributes[data-entity-uuid]', $entity->uuid());
$this->assertEqualsWithJs('attributes[data-entity-substitution]', 'canonical');
$this->assertEqualsWithJs('attributes[href]', '#');
$this->assertEqualsWithJs('attributes[href]', $entity->toUrl()->toString());
// Save the dialog input.
$page->find('css', '.editor-link-dialog')->find('css', '.button.form-submit span')->click();
......@@ -220,10 +206,10 @@ class LinkitDialogTest extends JavascriptTestBase {
$web_assert->assertWaitOnAjaxRequest();
$fields = [
'data-entity-type' => $this->demoEntity->getEntityTypeId(),
'data-entity-uuid' => $this->demoEntity->uuid(),
'data-entity-type' => $entity->getEntityTypeId(),
'data-entity-uuid' => $entity->uuid(),
'data-entity-substitution' => 'canonical',
'href' => '#',
'href' => $entity->toUrl()->toString(),
];
foreach ($fields as $attribute => $value) {
$link_attribute = $this->getLinkAttributeFromEditor($attribute);
......@@ -249,44 +235,16 @@ JS;
// Find the linkit field.
$linkit_field = $page->findField('linkit');
$this->assertEquals($this->demoEntity->label(), $linkit_field->getValue(), 'Linkit field has the correct value.');
$this->assertEquals($entity->toUrl()->toString(), $linkit_field->getValue(), 'Linkit field contains the node url when edit.');
// Make sure all other fields are populated when editing a link.
$this->assertEqualsWithJs('attributes[data-entity-type]', $this->demoEntity->getEntityTypeId());
$this->assertEqualsWithJs('attributes[data-entity-uuid]', $this->demoEntity->uuid());
$this->assertEqualsWithJs('attributes[data-entity-type]', $entity->getEntityTypeId());
$this->assertEqualsWithJs('attributes[data-entity-uuid]', $entity->uuid());
$this->assertEqualsWithJs('attributes[data-entity-substitution]', 'canonical');
$this->assertEqualsWithJs('attributes[href]', '#');
// Make sure the link information is populated with the old label.
$this->assertEquals($this->demoEntity->label(), $this->getLinkInfoText(), 'Link information is populated');
// Trigger a keydown event to active a autocomplete search.
$linkit_field->setValue('http://example.co');
$linkit_field->keyDown('m');
// Wait for the autocomplete to be done.
$this->getSession()->wait(5000, "jQuery('.linkit-result.ui-menu-item').length > 0");
// Make sure the autocomplete result container is visible.
$this->assertTrue($autocomplete_container->isVisible());
// Find all the autocomplete results.
$results = $page->findAll('css', '.linkit-result.ui-menu-item');
$this->assertEquals(1, count($results), 'Found autocomplete result');
// Find the first result and click it.
$page->find('xpath', '(//li[contains(@class, "linkit-result") and contains(@class, "ui-menu-item")])[1]')->click();
// Make sure the href field is populated with the external uri.
$this->assertEquals('http://example.com', $linkit_field->getValue(), 'The linkit field is the same as the URI');
$this->assertEqualsWithJs('attributes[href]', $entity->toUrl()->toString());
// Make sure the link information is populated.
$this->assertEquals('http://example.com', $this->getLinkInfoText(), 'Link information is populated');
$this->assertEmptyWithJs('attributes[data-entity-type]');
$this->assertEmptyWithJs('attributes[data-entity-uuid]');
$this->assertEmptyWithJs('attributes[data-entity-substitution]');
$this->assertEqualsWithJs('attributes[href]', 'http://example.com');
// Edit the linkit field and set an external url.
$linkit_field->setValue('http://example.com');
// Save the dialog input.
$page->find('css', '.editor-link-dialog')->find('css', '.button.form-submit span')->click();
......@@ -308,94 +266,6 @@ JS;
$this->assertEquals('http://example.com', $href_attribute, 'The link href is correct.');
}
/**
* Test the link dialog with translated entities.
*/
public function testLinkDialogTranslations() {
$session = $this->getSession();
$web_assert = $this->assertSession();
$page = $session->getPage();
// Adds additional languages.
$langcodes = ['sv', 'da', 'fi'];
foreach ($langcodes as $langcode) {
ConfigurableLanguage::createFromLangcode($langcode)->save();
}
// Create a test entity.
$entity = EntityTestMul::create(['name' => 'Foo']);
foreach ($langcodes as $langcode) {
$entity->addTranslation($langcode, ['name' => 'Foo ' . $langcode]);
}
$entity->save();
// Create test nodes.
$this->demoEntity = $entity;
$this->config('system.site')->set('default_langcode', 'sv')->save();
// Go to node creation page.
$this->drupalGet('node/add/page');
// Wait until the editor has been loaded.
$ckeditor_loaded = $this->getSession()->wait(5000, "jQuery('.cke_contents').length > 0");
$this->assertTrue($ckeditor_loaded, 'The editor has been loaded.');
// Click on the drupallink plugin.
$page->find('css', 'a.cke_button__drupallink')->click();
// Wait for the form to load.
$web_assert->assertWaitOnAjaxRequest();
// Find the linkit field.
$linkit_field = $page->findField('linkit');
// Trigger a keydown event to active a autocomplete search.
$linkit_field->keyDown('f');
// Wait for the results to load.
$this->getSession()->wait(5000, "jQuery('.linkit-result.ui-menu-item').length > 0");
// Find all the autocomplete results.
$results = $page->findAll('css', '.linkit-result.ui-menu-item');
$this->assertEquals(1, count($results), 'Found autocomplete result');
// Find the first result and click it.
$page->find('xpath', '(//li[contains(@class, "linkit-result") and contains(@class, "ui-menu-item")])[1]')->click();
// Save the dialog input.
$page->find('css', '.editor-link-dialog')->find('css', '.button.form-submit span')->click();
// Wait for the dialog to close.
$web_assert->assertWaitOnAjaxRequest();
// Select the link in the editor.
$javascript = <<<JS
(function(){
var editor = window.CKEDITOR.instances['edit-body-0-value'];
console.log(editor);
var element = editor.document.findOne( 'a' );
editor.getSelection().selectElement( element );
})()
JS;
$session->executeScript($javascript);
// Click on the drupallink plugin.
$page->find('css', 'a.cke_button__drupallink')->click();
// Wait for the form to load.
$web_assert->assertWaitOnAjaxRequest();
// Find the linkit field.
$linkit_field = $page->findField('linkit');
$this->assertEquals($this->demoEntity->getTranslation('sv')->label(), $linkit_field->getValue(), 'Linkit field has the correct value.');
// Make sure the link information is populated with the old label.
$this->assertEquals($this->demoEntity->getTranslation('sv')->label(), $this->getLinkInfoText(), 'Link information is populated');
}
/**
* Asserts that a variable is empty.
*
......@@ -447,15 +317,4 @@ JS;
return $this->getSession()->evaluateScript($javascript);
}
/**
* Gets the text in the link information element.
*
* @return string
* The text in the link information element.
*/
private function getLinkInfoText() {
$javascript = "(function (){ return jQuery('.linkit-link-information > span').text(); })()";
return $this->getSession()->evaluateScript($javascript);
}
}
......@@ -54,6 +54,11 @@ class LinkitAutocompleteTest extends LinkitKernelTestBase {
protected function setUp() {
parent::setUp();
// Create user 1 who has special permissions.
$this->createUser();
\Drupal::currentUser()->setAccount($this->createUser([], ['view test entity']));
\Drupal::service('router.builder')->rebuild();
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test');
......
......@@ -148,9 +148,11 @@ class LinkitEditorLinkDialogTest extends LinkitKernelTestBase {
*/
public function testEditWithDataAttributes() {
$entity_label = $this->randomString();
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = EntityTest::create(['name' => $entity_label]);
$entity->save();
/** @var \Drupal\Core\Entity\EntityInterface $entity_no_access */
$entity_no_access = EntityTest::create(['name' => 'forbid_access']);
$entity_no_access->save();
......@@ -158,7 +160,7 @@ class LinkitEditorLinkDialogTest extends LinkitKernelTestBase {
$input = [
'editor_object' => [
'href' => '#',
'href' => $entity->toUrl()->toString(),
'data-entity-type' => $entity->getEntityTypeId(),
'data-entity-uuid' => $entity->uuid(),
'data-entity-substitution' => SubstitutionManagerInterface::DEFAULT_SUBSTITUTION,
......@@ -188,46 +190,10 @@ class LinkitEditorLinkDialogTest extends LinkitKernelTestBase {
$form_builder->processForm($form_id, $form, $form_state);
$this->assertEquals('linkit.autocomplete', $form['linkit']['#autocomplete_route_name'], 'Linkit is enabled on the linkit field.');
$this->assertEquals($entity->label(), $form['linkit']['#default_value'], 'The linkit field has the label as default value.');
$this->assertEquals($entity->toUrl()->toString(), $form['linkit']['#default_value'], 'The linkit field has the url as default value.');
$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.');
$this->assertEquals(SubstitutionManagerInterface::DEFAULT_SUBSTITUTION, $form_state->getValue(['attributes', 'data-entity-substitution']), 'Attribute "data-entity-substitution" exists and has the correct value.');
// Make sure the dialog don't display entity labels for inaccessible
// entities.
$input = [
'editor_object' => [
'href' => '#',
'data-entity-type' => $entity_no_access->getEntityTypeId(),
'data-entity-uuid' => $entity_no_access->uuid(),
'data-entity-substitution' => SubstitutionManagerInterface::DEFAULT_SUBSTITUTION,
],
'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->editor]);
/** @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['linkit']['#autocomplete_route_name'], 'Linkit is enabled on the linkit field.');
$this->assertEmpty($form['linkit']['#default_value']);
}
/**
......
......@@ -32,6 +32,11 @@ class ContactFormMatcherTest extends LinkitKernelTestBase {
protected function setUp() {
parent::setUp();
// Create user 1 who has special permissions.
$this->createUser();
\Drupal::currentUser()->setAccount($this->createUser([], ['access site-wide contact form', 'view test entity translations']));
$this->manager = $this->container->get('plugin.manager.linkit.matcher');
ContactForm::create([
......
......@@ -36,6 +36,11 @@ class TermMatcherTest extends LinkitKernelTestBase {
protected function setUp() {
parent::setUp();
// Create user 1 who has special permissions.
$this->createUser();
\Drupal::currentUser()->setAccount($this->createUser([], ['access content']));
$this->installEntitySchema('taxonomy_term');
$this->manager = $this->container->get('plugin.manager.linkit.matcher');
......
......@@ -25,6 +25,11 @@ class UserMatcherTest extends LinkitKernelTestBase {
protected function setUp() {
parent::setUp();
// Create user 1 who has special permissions.
$this->createUser();
\Drupal::currentUser()->setAccount($this->createUser([], ['access user profiles']));
$this->manager = $this->container->get('plugin.manager.linkit.matcher');
$custom_role = Role::create([
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment