added linkit contrib module

This commit is contained in:
Bachir Soussi Chiadmi
2017-11-27 00:05:29 +01:00
parent 6a9608f0c7
commit f7646c417a
87 changed files with 7018 additions and 0 deletions
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,33 @@
Linkit
===========
Linkit provides an **enriched linking experience for internal and external
linking** with editors by using an autocomplete field. Linkit has by default
support for nodes, users, taxonomy terms, files, comments and
**basic support for all types of entities** that defines a canonical link
template.
Installation
------------
* Normal module installation procedure. See
https://www.drupal.org/documentation/install/modules-themes/modules-8
Configuration
------------
After the installation, you have to create a Linkit profile. The profile will
contain information about which plugins to use.
Profiles can be created at `/admin/config/content/linkit`
When you have created a profile, you need to enable the Linkit plugin on the
text format you want to use. Formats are found at
`admin/config/content/formats`.
Plugins examples
------------
There are plugin implementation examples in the linkit_test module bundled with
Linkit core.
@@ -0,0 +1,19 @@
{
"name": "drupal/linkit",
"description": "Linkit - Enriched linking experience",
"type": "drupal-module",
"homepage": "http://drupal.org/project/linkit",
"authors": [
{
"name": "Emil Stjerneman",
"email": "emil@stjerneman.com",
"homepage": "https://stjerneman.com",
"role": "Maintainer"
}
],
"support": {
"issues": "http://drupal.org/project/linkit",
"source": "http://cgit.drupalcode.org/linkit"
},
"license": "GPL-2.0+"
}
@@ -0,0 +1,13 @@
langcode: en
status: true
dependencies: { }
name: linkit_result_thumbnail
label: 'Linkit result thumbnail'
effects:
2943df29-38ea-459c-ba1d-290489bb1807:
uuid: 2943df29-38ea-459c-ba1d-290489bb1807
id: image_scale_and_crop
weight: 1
data:
width: 50
height: 50
@@ -0,0 +1,131 @@
# Schema for the configuration files of the Linkit module.
linkit.linkit_profile.*:
type: config_entity
mapping:
label:
type: label
label: 'Name'
id:
type: string
description:
type: string
attributes:
type: sequence
sequence:
type: linkit.attribute
matchers:
type: sequence
sequence:
type: linkit.matcher
linkit.attribute:
type: mapping
mapping:
id:
type: string
settings:
type: linkit.attribute.[%parent.id]
weight:
type: integer
linkit.attribute.*:
type: mapping
label: 'Attribute settings'
# Plugin \Drupal\linkit\Plugin\Linkit\Attribute\Target
linkit.attribute.target:
type: linkit.attribute
mapping:
widget_type:
type: string
# Plugin \Drupal\linkit\Plugin\Linkit\Attribute\Title
linkit.attribute.title:
type: linkit.attribute
mapping:
automatic_title:
type: boolean
linkit.matcher:
type: mapping
mapping:
id:
type: string
uuid:
type: string
settings:
type: linkit.matcher.[%parent.id]
weight:
type: integer
linkit.matcher.*:
type: mapping
label: 'Matcher settings'
linkit.matcher.entity:
type: mapping
mapping:
result_description:
type: string
bundles:
type: sequence
sequence:
type: string
nullable: true
group_by_bundle:
type: boolean
linkit.matcher.entity:*:
type: linkit.matcher.entity
# Plugin \Drupal\linkit\Plugin\Linkit\Matcher\FileMatcher
linkit.matcher.entity:file:
type: linkit.matcher.entity
mapping:
images:
type: mapping
mapping:
show_dimensions:
type: boolean
show_thumbnail:
type: boolean
thumbnail_image_style:
type: string
nullable: true
# Plugin \Drupal\linkit\Plugin\Linkit\Matcher\NodeMatcher
linkit.matcher.entity:node:
type: linkit.matcher.entity
mapping:
include_unpublished:
type: boolean
# Plugin \Drupal\linkit\Plugin\Linkit\Matcher\UserMatcher
linkit.matcher.entity:user:
type: linkit.matcher.entity
mapping:
roles:
type: sequence
sequence:
type: string
nullable: true
include_blocked:
type: boolean
# Third party settings on behalf of the imce module.
linkit.linkit_profile.*.third_party.imce:
type: mapping
mapping:
use:
type: boolean
scheme:
type: string
ckeditor.plugin.linkit:
type: mapping
label: 'Profile dropdown'
mapping:
linkit_profile:
type: string
label: 'List of profiles'
@@ -0,0 +1,15 @@
/**
* @file
* Stylesheet for the Linkit module admin GUI.
*/
.linkit-plugin-summary {
float: left;
font-size: .9em;
line-height: 1.2em;
}
.linkit-matcher-add-form tbody td:first-child,
.linkit-attribute-add-form tbody td:first-child {
width: 1%;
}
@@ -0,0 +1,69 @@
/**
* @file
* Stylesheet for the Linkit module.
*/
.js input.form-linkit-autocomplete {
background-image: url(/core/misc/throbber-inactive.png);
background-position: 100% center; /* LTR */
background-repeat: no-repeat;
}
.js[dir="rtl"] input.form-linkit-autocomplete {
background-position: 0% center;
}
.js input.form-linkit-autocomplete.ui-autocomplete-loading {
background-image: url(/core/misc/throbber-active.gif);
background-position: 100% center; /* LTR */
}
.js[dir="rtl"] input.form-linkit-autocomplete.ui-autocomplete-loading {
background-position: 0% center;
}
/* Override the default jQuery UI theme */
.linkit-ui-autocomplete {
max-height: 330px;
overflow: auto;
}
.linkit-ui-autocomplete.ui-widget {
font-size: .9em;
}
.linkit-ui-autocomplete.ui-menu .ui-menu-item {
list-style: none;
padding: 5px 7px;
}
.linkit-ui-autocomplete .ui-menu-item.ui-state-focus {
margin: 0;
border: 0;
border-bottom: 1px solid #bfbfbf;
background: #0075ba;
color: #fff;
}
.linkit-result:not(:last-of-type) {
border-bottom: 1px solid #bfbfbf;
}
.linkit-result--group {
padding: 3px;
background-color: #e7e7e7;
border-bottom: 1px solid #bfbfbf;
text-align: center;
font-size: 0.9em;
font-weight: 600;
color: #555;
}
.linkit-result--title {
font-weight: 600;
}
.linkit-result--description {
display: block;
font-size: 0.9em;
line-height: 1.3;
}
.linkit-result--description img {
display: block;
}
@@ -0,0 +1,21 @@
/**
* @file
* Title attribute functions.
*/
(function ($, Drupal, document) {
'use strict';
var fieldName = '[name="attributes[title]"]';
/**
* Automatically populate the title attribute.
*/
$(document).bind('linkit.autocomplete.select', function (triggerEvent, event, ui) {
if (ui.item.hasOwnProperty('title')) {
$('form.linkit-editor-dialog-form').find(fieldName).val(ui.item.title);
}
});
})(jQuery, Drupal, document);
@@ -0,0 +1,167 @@
/**
* @file
* Linkit Autocomplete based on jQuery UI.
*/
(function ($, Drupal, _, document) {
'use strict';
var autocomplete;
/**
* JQuery UI autocomplete source callback.
*
* @param {object} request
* @param {function} response
*/
function sourceData(request, response) {
var elementId = this.element.attr('id');
if (!(elementId in autocomplete.cache)) {
autocomplete.cache[elementId] = {};
}
/**
* @param {object} suggestions
*/
function showSuggestions(suggestions) {
response(suggestions.matches);
}
/**
* Transforms the data object into an array and update autocomplete results.
*
* @param {object} data
*/
function sourceCallbackHandler(data) {
autocomplete.cache[elementId][term] = data;
showSuggestions(data);
}
// Get the desired term and construct the autocomplete URL for it.
var term = request.term;
// Check if the term is already cached.
if (autocomplete.cache[elementId].hasOwnProperty(term)) {
showSuggestions(autocomplete.cache[elementId][term]);
}
else {
var options = $.extend({success: sourceCallbackHandler, data: {q: term}}, autocomplete.ajax);
$.ajax(this.element.attr('data-autocomplete-path'), options);
}
}
/**
* Handles an autocomplete select event.
*
* @param {jQuery.Event} event
* @param {object} ui
*
* @return {boolean}
*/
function selectHandler(event, ui) {
if (ui.item.hasOwnProperty('path')) {
event.target.value = ui.item.path;
}
$(document).trigger('linkit.autocomplete.select', [event, ui]);
return false;
}
/**
* Override jQuery UI _renderItem function to output HTML by default.
*
* @param {object} ul
* The <ul> element that the newly created <li> element must be appended to.
* @param {object} item
*
* @return {object}
*/
function renderItem(ul, item) {
var $line = $('<li>').addClass('linkit-result');
$line.append($('<span>').html(item.title).addClass('linkit-result--title'));
if (item.description !== null) {
$line.append($('<span>').html(item.description).addClass('linkit-result--description'));
}
return $line.appendTo(ul);
}
/**
* Override jQuery UI _renderMenu function to handle groups.
*
* @param {object} ul
* An empty <ul> element to use as the widget's menu.
* @param {array} items
* An Array of items that match the user typed term.
*/
function renderMenu(ul, items) {
var self = this.element.autocomplete('instance');
var grouped_items = _.groupBy(items, function (item) {
return item.hasOwnProperty('group') ? item.group : '';
});
$.each(grouped_items, function (group, items) {
if (group.length) {
ul.append('<li class="linkit-result--group">' + group + '</li>');
}
$.each(items, function (index, item) {
self._renderItemData(ul, item);
});
});
}
/**
* Attaches the autocomplete behavior to all required fields.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.linkit_autocomplete = {
attach: function (context) {
// Act on textfields with the "form-autocomplete" class.
var $autocomplete = $(context).find('input.form-linkit-autocomplete').once('linkit-autocomplete');
if ($autocomplete.length) {
$.widget('custom.autocomplete', $.ui.autocomplete, {
_create: function () {
this._super();
this.widget().menu('option', 'items', '> :not(.linkit-result--group)');
},
_renderMenu: autocomplete.options.renderMenu,
_renderItem: autocomplete.options.renderItem
});
// Use jQuery UI Autocomplete on the textfield.
$autocomplete.autocomplete(autocomplete.options);
$autocomplete.autocomplete('widget').addClass('linkit-ui-autocomplete');
}
},
detach: function (context, settings, trigger) {
if (trigger === 'unload') {
$(context).find('input.form-linkit-autocomplete')
.removeOnce('linkit-autocomplete')
.autocomplete('destroy');
}
}
};
/**
* Autocomplete object implementation.
*/
autocomplete = {
cache: {},
options: {
source: sourceData,
renderItem: renderItem,
renderMenu: renderMenu,
select: selectHandler,
minLength: 1
},
ajax: {
dataType: 'json'
}
};
})(jQuery, Drupal, _, document);
@@ -0,0 +1,52 @@
/**
* @file
* IMCE integration for Linkit.
*/
(function ($, Drupal, drupalSettings) {
'use strict';
/**
* @namespace
*
* Need to be in the global namespace, otherwise the IMCE window will not show
* the 'select' button in the toolbar.
*/
var linkitImce = window.linkitImce = {};
/**
* Drupal behavior to handle imce linkit integration.
*/
Drupal.behaviors.linkitImce = {
attach: function (context, settings) {
var $link = $(context).find('.linkit-imce-open').once('linkit-imce-open');
if ($link.length) {
$link.bind('click', function (event) {
event.preventDefault();
window.open($(this).attr('href'), '', 'width=760,height=560,resizable=1');
});
}
}
};
/**
* Handler for imce sendto operation.
*/
linkitImce.sendto = function (file, win) {
var imce = win.imce;
var items = imce.getSelection();
if (imce.countSelection() > 1) {
imce.setMessage(Drupal.t('You can only select one file.'));
return;
}
var path = imce.getConf('root_url') + '/' + imce.getItemPath(items[0]);
$('[data-drupal-selector="edit-attributes-href"]').val(path);
win.close();
};
})(jQuery, Drupal, drupalSettings);
@@ -0,0 +1,15 @@
/**
* @file
* Some basic behaviors and utility functions for Linkit.
*/
(function ($, Drupal, drupalSettings) {
'use strict';
/**
* @namespace
*/
Drupal.linkit = {};
})(jQuery, Drupal, drupalSettings);
Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

@@ -0,0 +1,223 @@
/**
* @file
* Linkit plugin.
*
* @ignore
*/
(function ($, Drupal, drupalSettings, CKEDITOR) {
'use strict';
// Alter the dialog settings to make a bigger dialog.
// $(window).on('dialog:beforecreate', function (event, dialog, $element, settings) {
// settings.dialogClass = settings.dialogClass.replace('ui-dialog--narrow', '');
// settings.width = 700;
// });
CKEDITOR.plugins.add('linkit', {
init: function (editor) {
// Add the commands for link and unlink.
editor.addCommand('linkit', {
allowedContent: new CKEDITOR.style({
element: 'a',
attributes: {
'!href': '',
// @TODO: Read these dynamically from the profile.
'accesskey': '',
'id': '',
'rel': '',
'target': '',
'title': ''
}
}),
requiredContent: new CKEDITOR.style({
element: 'a',
attributes: {
href: ''
}
}),
modes: {wysiwyg: 1},
canUndo: true,
exec: function (editor) {
var linkElement = getSelectedLink(editor);
var linkDOMElement = null;
// Set existing values based on selected element.
var existingValues = {};
if (linkElement && linkElement.$) {
linkDOMElement = linkElement.$;
// Populate an array with the link's current attributes.
var attribute = null;
var attributeName;
for (var attrIndex = 0; attrIndex < linkDOMElement.attributes.length; attrIndex++) {
attribute = linkDOMElement.attributes.item(attrIndex);
attributeName = attribute.nodeName.toLowerCase();
// Don't consider data-cke-saved- attributes; they're just there
// to work around browser quirks.
if (attributeName.substring(0, 15) === 'data-cke-saved-') {
continue;
}
// Store the value for this attribute, unless there's a
// data-cke-saved- alternative for it, which will contain the
// quirk-free, original value.
existingValues[attributeName] = linkElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
}
}
// Prepare a save callback to be used upon saving the dialog.
var saveCallback = function (returnValues) {
editor.fire('saveSnapshot');
// Create a new link element if needed.
if (!linkElement && returnValues.attributes.href) {
var selection = editor.getSelection();
var range = selection.getRanges(1)[0];
// Use link URL as text with a collapsed cursor.
if (range.collapsed) {
// Shorten mailto URLs to just the email address.
var text = new CKEDITOR.dom.text(returnValues.attributes.href.replace(/^mailto:/, ''), editor.document);
range.insertNode(text);
range.selectNodeContents(text);
}
// Create the new link by applying a style to the new text.
var style = new CKEDITOR.style({element: 'a', attributes: returnValues.attributes});
style.type = CKEDITOR.STYLE_INLINE;
style.applyToRange(range);
range.select();
// Set the link so individual properties may be set below.
linkElement = getSelectedLink(editor);
}
// Update the link properties.
else if (linkElement) {
for (var attrName in returnValues.attributes) {
if (returnValues.attributes.hasOwnProperty(attrName)) {
// Update the property if a value is specified.
if (returnValues.attributes[attrName].length > 0) {
var value = returnValues.attributes[attrName];
linkElement.data('cke-saved-' + attrName, value);
linkElement.setAttribute(attrName, value);
}
// Delete the property if set to an empty string.
else {
linkElement.removeAttribute(attrName);
}
}
}
}
// Save snapshot for undo support.
editor.fire('saveSnapshot');
};
// Drupal.t() will not work inside CKEditor plugins because CKEditor
// loads the JavaScript file instead of Drupal. Pull translated
// strings from the plugin settings that are translated server-side.
var dialogSettings = {
title: linkElement ? editor.config.linkit_dialogTitleAdd : editor.config.linkit_dialogTitleEdit,
dialogClass: 'editor-linkit-dialog'
};
// Open the dialog for the edit form.
Drupal.ckeditor.openDialog(editor, Drupal.url('linkit/dialog/linkit/' + editor.config.drupal.format), existingValues, saveCallback, dialogSettings);
}
});
// CTRL + L.
editor.setKeystroke(CKEDITOR.CTRL + 76, 'linkit');
// Add buttons.
if (editor.ui.addButton) {
editor.ui.addButton('Linkit', {
label: Drupal.t('Link'),
command: 'linkit',
icon: this.path + '/linkit.png'
});
}
editor.on('doubleclick', function (evt) {
var element = getSelectedLink(editor) || evt.data.element;
if (!element.isReadOnly()) {
if (element.is('a')) {
editor.getSelection().selectElement(element);
editor.getCommand('linkit').exec();
}
}
});
// If the "menu" plugin is loaded, register the menu items.
if (editor.addMenuItems) {
editor.addMenuItems({
linkit: {
label: Drupal.t('Edit Link'),
command: 'linkit',
group: 'link',
order: 1
}
});
}
// If the "contextmenu" plugin is loaded, register the listeners.
if (editor.contextMenu) {
editor.contextMenu.addListener(function (element, selection) {
if (!element || element.isReadOnly()) {
return null;
}
var anchor = getSelectedLink(editor);
if (!anchor) {
return null;
}
var menu = {};
if (anchor.getAttribute('href') && anchor.getChildCount()) {
menu = {
linkit: CKEDITOR.TRISTATE_OFF
};
}
return menu;
});
}
}
});
/**
* Get the surrounding link element of current selection.
*
* The following selection will all return the link element.
*
* @example
* <a href="#">li^nk</a>
* <a href="#">[link]</a>
* text[<a href="#">link]</a>
* <a href="#">li[nk</a>]
* [<b><a href="#">li]nk</a></b>]
* [<a href="#"><b>li]nk</b></a>
*
* @param {CKEDITOR.editor} editor
* The CKEditor editor object
*
* @return {?HTMLElement}
* The selected link element, or null.
*
*/
function getSelectedLink(editor) {
var selection = editor.getSelection();
var selectedElement = selection.getSelectedElement();
if (selectedElement && selectedElement.is('a')) {
return selectedElement;
}
var range = selection.getRanges(true)[0];
if (range) {
range.shrink(CKEDITOR.SHRINK_TEXT);
return editor.elementPath(range.getCommonAncestor()).contains('a', 1);
}
return null;
}
})(jQuery, Drupal, drupalSettings, CKEDITOR);
@@ -0,0 +1,12 @@
name: Linkit
description: Linkit
package: Custom
type: module
# core: 8.x
configure: entity.linkit_profile.collection
# Information added by Drupal.org packaging script on 2017-03-21
version: '8.x-4.3'
core: '8.x'
project: 'linkit'
datestamp: 1490127367
@@ -0,0 +1,6 @@
<?php
/**
* @file
* Installation functions for Linkit module.
*/
@@ -0,0 +1,43 @@
linkit.admin:
version: VERSION
css:
theme:
css/linkit.admin.theme.css: {}
linkit.base:
version: VERSION
js:
js/linkit.js: {}
dependencies:
- core/jquery
- core/drupal
- core/drupalSettings
linkit.autocomplete:
version: VERSION
js:
js/autocomplete.js: {}
css:
component:
css/linkit.autocomplete.css: {}
dependencies:
- linkit/linkit.base
- core/drupal.ajax
- core/jquery.ui.autocomplete
- core/underscore
linkit.attribute.title:
version: VERSION
js:
js/attribute/title.js: {}
dependencies:
- linkit/linkit.base
- linkit/linkit.autocomplete
linkit.imce:
version: VERSION
js:
js/linkit.imce.js: {}
dependencies:
- core/jquery.once
- linkit/linkit.base
@@ -0,0 +1,18 @@
linkit_profile.add_form:
route_name: entity.linkit_profile.add_form
title: 'Add profile'
appears_on:
- entity.linkit_profile.collection
linkit.matcher.add:
route_name: linkit.matcher.add
title: 'Add matcher'
appears_on:
- linkit.matchers
linkit.attribute.add:
route_name: linkit.attribute.add
title: 'Add attribute'
appears_on:
- linkit.attributes
@@ -0,0 +1,5 @@
entity.linkit_profile.collection:
title: 'Linkit'
parent: system.admin_config_content
description: 'Manage Linkit profiles.'
route_name: entity.linkit_profile.collection
@@ -0,0 +1,15 @@
entity.linkit_profile.edit_form:
route_name: entity.linkit_profile.edit_form
title: 'Edit'
base_route: entity.linkit_profile.edit_form
weight: 0
linkit.matchers:
route_name: linkit.matchers
title: 'Manage matchers'
base_route: entity.linkit_profile.edit_form
weight: 5
linkit.attributes:
route_name: linkit.attributes
title: 'Manage attributes'
base_route: entity.linkit_profile.edit_form
weight: 10
@@ -0,0 +1,109 @@
<?php
/**
* @file
*
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\Url;
use Drupal\linkit\ProfileInterface;
/**
* Implements hook_help().
*/
function linkit_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'entity.linkit_profile.attributes':
return '<p>' . t('Attributes are HTML attributes that will be attached to the insert plugin.') . '</p>';
break;
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter() for linkit_profile_form on behalf
* of the 'imce' module.
*
* Adds IMCE settings to the form.
*
* @see imce_form_linkit_profile_form_builder()
*/
function imce_form_linkit_profile_form_alter(&$form, FormStateInterface $form_state) {
/** @var \Drupal\Linkit\ProfileInterface $linkit_profile */
$linkit_profile = $form_state->getFormObject()->getEntity();
$form['imce'] = array(
'#type' => 'details',
'#title' => t('IMCE integration'),
'#group' => 'additional_settings',
);
$form['imce']['imce_use'] = array(
'#type' => 'checkbox',
'#title' => t('Enable IMCE File Browser in the editor dialog.'),
'#default_value' => $linkit_profile->getThirdPartySetting('imce', 'use', FALSE),
);
$scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::READ_VISIBLE);
$form['imce']['imce_scheme'] = array(
'#type' => 'radios',
'#title' => t('Scheme'),
'#options' => $scheme_options,
'#default_value' => $linkit_profile->getThirdPartySetting('imce', 'scheme', 'public'),
'#states' => [
'visible' => [
':input[name="imce_use"]' => ['checked' => TRUE],
],
],
);
$form['#entity_builders'][] = 'imce_form_linkit_profile_form_builder';
}
/**
* Entity builder for the linkit profile form with imce options.
*
* @see imce_form_linkit_profile_form_alter().
*/
function imce_form_linkit_profile_form_builder($entity_type, ProfileInterface $linkit_profile, &$form, FormStateInterface $form_state) {
$linkit_profile->setThirdPartySetting('imce', 'use', $form_state->getValue('imce_use'));
$linkit_profile->setThirdPartySetting('imce', 'scheme', $form_state->getValue('imce_scheme'));
}
/**
* Implements hook_form_BASE_FORM_ID_alter() for linkit_editor_dialog_form on
* behalf of the 'imce' module.
*
* Adds a button to open the imce file browser if it is enabled.
*/
function imce_form_linkit_editor_dialog_form_alter(&$form, FormStateInterface $form_state) {
/** @var \Drupal\Linkit\ProfileInterface $linkit_profile */
$linkit_profile = $form_state->getFormObject()->getLinkitProfile();
if($linkit_profile->getThirdPartySetting('imce', 'use', FALSE)) {
$form['imce-link'] = [
'#type' => 'link',
'#title' => t('Open IMCE file browser'),
'#url' => Url::fromRoute('imce.page', [
'scheme' => $linkit_profile->getThirdPartySetting('imce', 'scheme', 'public'),
]),
'#options' => array(
'query' => array(
'sendto' => 'linkitImce.sendto',
),
),
'#attributes' => [
'class' => ['linkit-imce-open'],
],
'#attached' => [
'library' => [
'linkit/linkit.imce'
],
],
'#weight' => 1,
];
}
}
@@ -0,0 +1,2 @@
administer linkit profiles:
title: 'Administer linkit profiles'
@@ -0,0 +1,111 @@
entity.linkit_profile.collection:
path: '/admin/config/content/linkit'
defaults:
_entity_list: 'linkit_profile'
_title: 'Linkit profiles'
requirements:
_permission: 'administer linkit profiles'
entity.linkit_profile.add_form:
path: '/admin/config/content/linkit/add'
defaults:
_entity_form: linkit_profile.add
_title: 'Add profile'
requirements:
_permission: 'administer linkit profiles'
entity.linkit_profile.edit_form:
path: '/admin/config/content/linkit/manage/{linkit_profile}'
defaults:
_entity_form: linkit_profile.edit
_title_callback: 'Drupal\linkit\Controller\LinkitController::profileTitle'
requirements:
_permission: 'administer linkit profiles'
entity.linkit_profile.delete_form:
path: '/admin/config/content/linkit/manage/{linkit_profile}/delete'
defaults:
_entity_form: linkit_profile.delete
requirements:
_permission: 'administer linkit profiles'
linkit.attributes:
path: '/admin/config/content/linkit/manage/{linkit_profile}/attributes'
defaults:
_form: '\Drupal\linkit\Form\Attribute\OverviewForm'
_title: 'Manage attributes'
requirements:
_permission: 'administer linkit profiles'
linkit.attribute.add:
path: '/admin/config/content/linkit/manage/{linkit_profile}/attributes/add'
defaults:
_form: '\Drupal\linkit\Form\Attribute\AddForm'
_title: 'Add attribute'
requirements:
_permission: 'administer linkit profiles'
linkit.attribute.delete:
path: '/admin/config/content/linkit/manage/{linkit_profile}/attributes/{plugin_instance_id}/delete'
defaults:
_form: '\Drupal\linkit\Form\Attribute\DeleteForm'
requirements:
_permission: 'administer linkit profiles'
linkit.attribute.edit:
path: '/admin/config/content/linkit/manage/{linkit_profile}/attributes/{plugin_instance_id}'
defaults:
_form: '\Drupal\linkit\Form\Attribute\EditForm'
_title_callback: 'Drupal\linkit\Controller\LinkitController::attributeTitle'
requirements:
_permission: 'administer linkit profiles'
linkit.matchers:
path: '/admin/config/content/linkit/manage/{linkit_profile}/matchers'
defaults:
_form: '\Drupal\linkit\Form\Matcher\OverviewForm'
_title: 'Manage matchers'
requirements:
_permission: 'administer linkit profiles'
linkit.matcher.add:
path: '/admin/config/content/linkit/manage/{linkit_profile}/matchers/add'
defaults:
_form: '\Drupal\linkit\Form\Matcher\AddForm'
_title: 'Add matcher'
requirements:
_permission: 'administer linkit profiles'
linkit.matcher.delete:
path: '/admin/config/content/linkit/manage/{linkit_profile}/matchers/{plugin_instance_id}/delete'
defaults:
_form: '\Drupal\linkit\Form\Matcher\DeleteForm'
requirements:
_permission: 'administer linkit profiles'
linkit.matcher.edit:
path: '/admin/config/content/linkit/manage/{linkit_profile}/matchers/{plugin_instance_id}'
defaults:
_form: '\Drupal\linkit\Form\Matcher\EditForm'
_title_callback: 'Drupal\linkit\Controller\LinkitController::matcherTitle'
requirements:
_permission: 'administer linkit profiles'
linkit.linkit_dialog:
path: '/linkit/dialog/linkit/{filter_format}'
defaults:
_form: '\Drupal\linkit\Form\LinkitEditorDialog'
_title: 'Add link'
requirements:
_entity_access: 'filter_format.use'
options:
_theme: ajax_base_page
linkit.autocomplete:
path: '/linkit/autocomplete/{linkit_profile_id}'
defaults:
_controller: '\Drupal\linkit\Controller\AutocompleteController::autocomplete'
requirements:
_access: 'TRUE'
options:
_theme: ajax_base_page
@@ -0,0 +1,11 @@
services:
plugin.manager.linkit.attribute:
class: Drupal\linkit\AttributeManager
parent: default_plugin_manager
plugin.manager.linkit.matcher:
class: Drupal\linkit\MatcherManager
parent: default_plugin_manager
linkit.result_manager:
class: Drupal\linkit\ResultManager
@@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Annotation\Attribute.
*/
namespace Drupal\linkit\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines an attribute annotation object.
*
* Plugin Namespace: Plugin\Linkit\Attribute
*
* For a working example, see \Drupal\linkit\Plugin\Linkit\Attribute\Title
*
* @see \Drupal\linkit\AttributeInterface
* @see \Drupal\linkit\AttributeBase
* @see \Drupal\linkit\AttributeManager
* @see plugin_api
*
* @Annotation
*/
class Attribute extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the attribute.
*
* The string should be wrapped in a @Translation().
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;
/**
* The real HTML attribute name for this attribute.
*
* @var string
*/
public $html_name;
/**
* A brief description of the attribute.
*
* This will be shown when adding or configuring a profile.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation (optional)
*/
public $description = '';
/**
* A default weight for the attribute.
*
* @var int (optional)
*/
public $weight = 0;
}
@@ -0,0 +1,49 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Annotation\Matcher.
*/
namespace Drupal\linkit\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a matcher annotation object.
*
* Plugin Namespace: Plugin\Linkit\Matcher
*
* @see \Drupal\linkit\MatcherInterface
* @see \Drupal\linkit\MatcherBase
* @see \Drupal\linkit\MatcherManager
* @see plugin_api
*
* @Annotation
*/
class Matcher extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the matcher.
*
* The string should be wrapped in a @Translation().
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;
/**
* The entity type that is managed by this matcher.
*
* @var string
*/
public $entity_type;
}
@@ -0,0 +1,112 @@
<?php
/**
* @file
* Contains \Drupal\linkit\AttributeBase.
*/
namespace Drupal\linkit;
use Drupal\Core\Plugin\PluginBase;
/**
* Provides a base class for attribute plugins.
*
* @see \Drupal\linkit\Annotation\Attribute
* @see \Drupal\linkit\AttributeBase
* @see \Drupal\linkit\AttributeManager
* @see plugin_api
*/
abstract class AttributeBase extends PluginBase implements AttributeInterface {
/**
* The weight of the attribute compared to others in an attribute collection.
*
* @var int
*/
protected $weight = 0;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->setConfiguration($configuration);
}
/**
* {@inheritdoc}
*/
public function getConfiguration() {
return [
'id' => $this->getPluginId(),
'weight' => $this->weight,
'settings' => $this->configuration,
];
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration) {
$configuration += [
'weight' => '0',
'settings' => [],
];
$this->configuration = $configuration['settings'] + $this->defaultConfiguration();
$this->weight = $configuration['weight'];
return $this;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return [];
}
/**
* {@inheritdoc}
*/
public function getLabel() {
return $this->pluginDefinition['label'];
}
/**
* {@inheritdoc}
*/
public function getHtmlName() {
return $this->pluginDefinition['html_name'];
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->pluginDefinition['description'];
}
/**
* {@inheritdoc}
*/
public function getWeight() {
return $this->weight;
}
/**
* {@inheritdoc}
*/
public function setWeight($weight) {
$this->weight = $weight;
return $this;
}
}
@@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\linkit\AttributeCollection.
*/
namespace Drupal\linkit;
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
/**
* A collection of attribute plugins.
*/
class AttributeCollection extends DefaultLazyPluginCollection {
/**
* All possible attribute IDs.
*
* @var array
*/
protected $definitions;
/**
* {@inheritdoc}
*
* @return \Drupal\linkit\AttributeInterface
*/
public function &get($instance_id) {
return parent::get($instance_id);
}
/**
* {@inheritdoc}
*/
public function sortHelper($aID, $bID) {
$a_weight = $this->get($aID)->getWeight();
$b_weight = $this->get($bID)->getWeight();
if ($a_weight == $b_weight) {
return strnatcasecmp($this->get($aID)->getLabel(), $this->get($bID)->getLabel());
}
return ($a_weight < $b_weight) ? -1 : 1;
}
}
@@ -0,0 +1,78 @@
<?php
/**
* @file
* Contains \Drupal\linkit\AttributeInterface.
*/
namespace Drupal\linkit;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Component\Plugin\PluginInspectionInterface;
/**
* Defines the interface for attributes plugins.
*
* @see \Drupal\linkit\Annotation\Attribute
* @see \Drupal\linkit\AttributeBase
* @see \Drupal\linkit\AttributeManager
* @see plugin_api
*/
interface AttributeInterface extends PluginInspectionInterface, ConfigurablePluginInterface {
/**
* Returns the attribute label.
*
* @return string
* The attribute label.
*/
public function getLabel();
/**
* Returns the attribute description.
*
* @return string
* The attribute description.
*/
public function getDescription();
/**
* Returns the attribute html name. This is the name of the attribute
* that will be inserted in the <code>&lt;a&gt;</code> tag.
*
* @return string
* The attribute html name.
*/
public function getHtmlName();
/**
* Returns the weight of the attribute.
*
* @return int|string
* Either the integer weight of the attribute or an empty string.
*/
public function getWeight();
/**
* Sets the weight for this attribute.
*
* @param int $weight
* The weight for this attribute.
*
* @return $this
*/
public function setWeight($weight);
/**
* The form element structure for this attribute to be used in the dialog.
*
* @param mixed $default_value
* The default value for the element. Used when editing an attribute in the
* dialog.
*
* @return array
* The form element.
*/
public function buildFormElement($default_value);
}
@@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\linkit\AttributeManager.
*/
namespace Drupal\linkit;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
/**
* Manages attributes.
*/
class AttributeManager extends DefaultPluginManager {
/**
* {@inheritdoc}
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/Linkit/Attribute', $namespaces, $module_handler, 'Drupal\linkit\AttributeInterface', 'Drupal\linkit\Annotation\Attribute');
$this->alterInfo('linkit_attribute');
$this->setCacheBackend($cache_backend, 'linkit_attributes');
}
}
@@ -0,0 +1,16 @@
<?php
/**
* @file
* Contains \Drupal\linkit\ConfigurableAttributeBase.
*/
namespace Drupal\linkit;
/**
* Provides a base class for configurable attributes.
*
* @see plugin_api
*/
abstract class ConfigurableAttributeBase extends AttributeBase implements ConfigurableAttributeInterface {
}
@@ -0,0 +1,23 @@
<?php
/**
* @file
* Contains \Drupal\linkit\ConfigurableAttributeInterface.
*/
namespace Drupal\linkit;
use Drupal\Core\Plugin\PluginFormInterface;
/**
* Defines the interface for configurable attributes.
*
* @see \Drupal\linkit\Annotation\Attribute
* @see \Drupal\linkit\ConfigurableAttributeBase
* @see \Drupal\linkit\AttributeInterface
* @see \Drupal\linkit\AttributeBase
* @see \Drupal\linkit\AttributeManager
* @see plugin_api
*/
interface ConfigurableAttributeInterface extends AttributeInterface, PluginFormInterface {
}
@@ -0,0 +1,16 @@
<?php
/**
* @file
* Contains \Drupal\linkit\ConfigurableMatcherBase.
*/
namespace Drupal\linkit;
/**
* Provides a base class for configurable matchers.
*
* @see plugin_api
*/
abstract class ConfigurableMatcherBase extends MatcherBase implements ConfigurableMatcherInterface {
}
@@ -0,0 +1,24 @@
<?php
/**
* @file
* Contains \Drupal\linkit\ConfigurableMatcherInterface.
*/
namespace Drupal\linkit;
use Drupal\Core\Plugin\PluginFormInterface;
/**
* Defines the interface for configurable matchers.
*
* @see \Drupal\linkit\Annotation\Matcher
* @see \Drupal\linkit\ConfigurableMatcherBase
* @see \Drupal\linkit\MatcherInterface
* @see \Drupal\linkit\MatcherBase
* @see \Drupal\linkit\MatcherManager
* @see plugin_api
*/
interface ConfigurableMatcherInterface extends MatcherInterface, PluginFormInterface {
}
@@ -0,0 +1,89 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Controller\AutocompleteController.
*/
namespace Drupal\linkit\Controller;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\linkit\ResultManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class AutocompleteController implements ContainerInjectionInterface {
/**
* The linkit profile storage service.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $linkitProfileStorage;
/**
* The result manager.
*
* @var \Drupal\linkit\ResultManager
*/
protected $resultManager;
/**
* The linkit profile.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* Constructs a EntityAutocompleteController object.
*
* @param ResultManager $resultManager
* The result service.
* @param \Drupal\Core\Entity\EntityStorageInterface $linkit_profile_storage
* The linkit profile storage service.
*/
public function __construct(EntityStorageInterface $linkit_profile_storage, ResultManager $resultManager) {
$this->linkitProfileStorage = $linkit_profile_storage;
$this->resultManager = $resultManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')->getStorage('linkit_profile'),
$container->get('linkit.result_manager')
);
}
/**
* Menu callback for linkit search autocompletion.
*
* Like other autocomplete functions, this function inspects the 'q' query
* parameter for the string to use to search for suggestions.
*
* @param Request $request
* The request.
* @param $linkit_profile_id
* The linkit profile id.
* @return JsonResponse
* A JSON response containing the autocomplete suggestions.
*/
public function autocomplete(Request $request, $linkit_profile_id) {
$this->linkitProfile = $this->linkitProfileStorage->load($linkit_profile_id);
$string = Unicode::strtolower($request->query->get('q'));
$matches = $this->resultManager->getResults($this->linkitProfile, $string);
$json_object = new \stdClass();
$json_object->matches = $matches;
return new JsonResponse($json_object);
}
}
@@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Controller\LinkitController.
*/
namespace Drupal\linkit\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\linkit\ProfileInterface;
/**
* Provides route responses for linkit.module.
*/
class LinkitController extends ControllerBase {
/**
* Route title callback.
*
* @param \Drupal\linkit\ProfileInterface $linkit_profile
* The profile.
*
* @return string
* The profile label as a render array.
*/
public function profileTitle(ProfileInterface $linkit_profile) {
return $this->t('Edit %label profile', array('%label' => $linkit_profile->label()));
}
/**
* Route title callback.
*
* @param \Drupal\linkit\ProfileInterface $linkit_profile
* The profile.
* @param string $plugin_instance_id
* The plugin instance id.
*
* @return string
* The title for the matcher edit form.
*/
public function matcherTitle(ProfileInterface $linkit_profile, $plugin_instance_id) {
/** @var \Drupal\linkit\MatcherInterface $matcher */
$matcher = $linkit_profile->getMatcher($plugin_instance_id);
return $this->t('Edit %label matcher', array('%label' => $matcher->getLabel()));
}
/**
* Route title callback.
*
* @param \Drupal\linkit\ProfileInterface $linkit_profile
* The profile.
* @param string $plugin_instance_id
* The plugin instance id.
*
* @return string
* The title for the attribute edit form.
*/
public function attributeTitle(ProfileInterface $linkit_profile, $plugin_instance_id) {
/** @var \Drupal\linkit\AttributeInterface $attribute */
$attribute = $linkit_profile->getAttribute($plugin_instance_id);
return $this->t('Edit %label attribute', array('%label' => $attribute->getLabel()));
}
}
@@ -0,0 +1,108 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Element\Linkit.
*/
namespace Drupal\linkit\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\FormElement;
use Drupal\Core\Render\Element\Textfield;
use Drupal\Core\Url;
/**
* Provides a form element for linkit.
*
* @FormElement("linkit")
*/
class Linkit extends FormElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#input' => TRUE,
'#size' => 60,
'#process' => array(
array($class, 'processLinkitAutocomplete'),
array($class, 'processGroup'),
),
'#pre_render' => array(
array($class, 'preRenderLinkitElement'),
array($class, 'preRenderGroup'),
),
'#theme' => 'input__textfield',
'#theme_wrappers' => array('form_element'),
);
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
return Textfield::valueCallback($element, $input, $form_state);
}
/**
* Adds linkit custom autocomplete functionality to elements.
*
* Instead of using the core autocomplete, we use our own.
*
* {@inheritdoc}
*
* @see \Drupal\Core\Render\Element\FormElement::processAutocomplete
*/
public static function processLinkitAutocomplete(&$element, FormStateInterface $form_state, &$complete_form) {
$url = NULL;
$access = FALSE;
if (!empty($element['#autocomplete_route_name'])) {
$parameters = isset($element['#autocomplete_route_parameters']) ? $element['#autocomplete_route_parameters'] : array();
$url = Url::fromRoute($element['#autocomplete_route_name'], $parameters)->toString(TRUE);
/** @var \Drupal\Core\Access\AccessManagerInterface $access_manager */
$access_manager = \Drupal::service('access_manager');
$access = $access_manager->checkNamedRoute($element['#autocomplete_route_name'], $parameters, \Drupal::currentUser(), TRUE);
}
if ($access) {
$metadata = BubbleableMetadata::createFromRenderArray($element);
if ($access->isAllowed()) {
$element['#attributes']['class'][] = 'form-linkit-autocomplete';
$metadata->addAttachments(['library' => ['linkit/linkit.autocomplete']]);
// Provide a data attribute for the JavaScript behavior to bind to.
$element['#attributes']['data-autocomplete-path'] = $url->getGeneratedUrl();
$metadata = $metadata->merge($url);
}
$metadata
->merge(BubbleableMetadata::createFromObject($access))
->applyTo($element);
}
return $element;
}
/**
* Prepares a #type 'linkit' render element for input.html.twig.
*
* @param array $element
* An associative array containing the properties of the element.
* Properties used: #title, #value, #description, #size, #attributes.
*
* @return array
* The $element with prepared variables ready for input.html.twig.
*/
public static function preRenderLinkitElement($element) {
$element['#attributes']['type'] = 'text';
Element::setAttributes($element, array('id', 'name', 'value', 'size'));
static::setAttributes($element, array('form-text'));
return $element;
}
}
@@ -0,0 +1,252 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Entity\Profile.
*/
namespace Drupal\linkit\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
use Drupal\linkit\AttributeCollection;
use Drupal\linkit\MatcherCollection;
use Drupal\linkit\MatcherInterface;
use Drupal\linkit\ProfileInterface;
/**
* Defines the linkit profile entity.
*
* @ConfigEntityType(
* id = "linkit_profile",
* label = @Translation("Linkit profile"),
* handlers = {
* "list_builder" = "Drupal\linkit\ProfileListBuilder",
* "form" = {
* "add" = "Drupal\linkit\Form\Profile\AddForm",
* "edit" = "Drupal\linkit\Form\Profile\EditForm",
* "delete" = "Drupal\Core\Entity\EntityDeleteForm"
* }
* },
* admin_permission = "administer linkit profiles",
* config_prefix = "linkit_profile",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* links = {
* "collection" = "/admin/config/content/linkit",
* "edit-form" = "/admin/config/content/linkit/manage/{linkit_profile}",
* "delete-form" = "/admin/config/content/linkit/manage/{linkit_profile}/delete"
* },
* config_export = {
* "id",
* "label",
* "description",
* "attributes",
* "matchers"
* }
* )
*/
class Profile extends ConfigEntityBase implements ProfileInterface, EntityWithPluginCollectionInterface {
/**
* The ID of this profile.
*
* @var string
*/
protected $id;
/**
* The human-readable label of this profile.
*
* @var string
*/
protected $label;
/**
* Description of this profile.
*
* @var string
*/
protected $description;
/**
* Configured attribute for this profile.
*
* An associative array of attribute assigned to the profile, keyed by the
* attribute id of each attribute and using the properties:
* - id: The plugin ID of the attribute instance.
* - status: (optional) A Boolean indicating whether the attribute is enabled
* in the profile. Defaults to FALSE.
* - weight: (optional) The weight of the attribute in the profile.
* Defaults to 0.
*
* @var array
*/
protected $attributes = [];
/**
* Holds the collection of attributes that are attached to this profile.
*
* @var \Drupal\linkit\AttributeCollection
*/
protected $attributeCollection;
/**
* Configured matchers for this profile.
*
* An associative array of matchers assigned to the profile, keyed by the
* matcher ID of each matcher and using the properties:
* - id: The plugin ID of the matchers instance.
* - status: (optional) A Boolean indicating whether the matchers is enabled
* in the profile. Defaults to FALSE.
* - weight: (optional) The weight of the matchers in the profile.
* Defaults to 0.
*
* @var array
*/
protected $matchers = [];
/**
* Holds the collection of matchers that are attached to this profile.
*
* @var \Drupal\linkit\MatcherCollection
*/
protected $matcherCollection;
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->get('description');
}
/**
* {@inheritdoc}
*/
public function setDescription($description) {
$this->set('description', trim($description));
return $this;
}
/**
* {@inheritdoc}
*/
public function getAttribute($attribute_id) {
return $this->getAttributes()->get($attribute_id);
}
/**
* {@inheritdoc}
*/
public function getAttributes() {
if (!$this->attributeCollection) {
$this->attributeCollection = new AttributeCollection($this->getAttributeManager(), $this->attributes);
$this->attributeCollection->sort();
}
return $this->attributeCollection;
}
/**
* {@inheritdoc}
*/
public function addAttribute(array $configuration) {
$this->getAttributes()->addInstanceId($configuration['id'], $configuration);
return $configuration['id'];
}
/**
* {@inheritdoc}
*/
public function removeAttribute($attribute_id) {
unset($this->attributes[$attribute_id]);
$this->getAttributes()->removeInstanceId($attribute_id);
return $this;
}
/**
* {@inheritdoc}
*/
public function setAttributeConfig($attribute_id, array $configuration) {
$this->attributes[$attribute_id] = $configuration;
$this->getAttributes()->setInstanceConfiguration($attribute_id, $configuration);
return $this;
}
/**
* {@inheritdoc}
*/
public function getMatcher($instance_id) {
return $this->getMatchers()->get($instance_id);
}
/**
* {@inheritdoc}
*/
public function getMatchers() {
if (!$this->matcherCollection) {
$this->matcherCollection = new MatcherCollection($this->getMatcherManager(), $this->matchers);
$this->matcherCollection->sort();
}
return $this->matcherCollection;
}
/**
* {@inheritdoc}
*/
public function addMatcher(array $configuration) {
$configuration['uuid'] = $this->uuidGenerator()->generate();
$this->getMatchers()->addInstanceId($configuration['uuid'], $configuration);
return $configuration['uuid'];
}
/**
* {@inheritdoc}
*/
public function removeMatcher(MatcherInterface $matcher) {
$this->getMatchers()->removeInstanceId($matcher->getUuid());
$this->save();
return $this;
}
/**
* {@inheritdoc}
*/
public function setMatcherConfig($instance_id, array $configuration) {
$this->matchers[$instance_id] = $configuration;
$this->getMatchers()->setInstanceConfiguration($instance_id, $configuration);
return $this;
}
/**
* {@inheritdoc}
*/
public function getPluginCollections() {
return array(
'attributes' => $this->getAttributes(),
'matchers' => $this->getMatchers(),
);
}
/**
* Returns the attribute manager.
*
* @return \Drupal\Component\Plugin\PluginManagerInterface
* The attribute manager.
*/
protected function getAttributeManager() {
return \Drupal::service('plugin.manager.linkit.attribute');
}
/**
* Returns the matcher manager.
*
* @return \Drupal\Component\Plugin\PluginManagerInterface
* The matcher manager.
*/
protected function getMatcherManager() {
return \Drupal::service('plugin.manager.linkit.matcher');
}
}
@@ -0,0 +1,166 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Attribute\AddForm.
*/
namespace Drupal\linkit\Form\Attribute;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\linkit\AttributeManager;
use Drupal\linkit\ConfigurableAttributeInterface;
use Drupal\linkit\ProfileInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form to apply attributes to a profile.
*/
class AddForm extends FormBase {
/**
* The profiles to which the attributes will be applied.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* The attribute manager.
*
* @var \Drupal\linkit\AttributeManager
*/
protected $manager;
/**
* Constructs a new AddForm.
*
* @param \Drupal\linkit\AttributeManager $manager
* The attribute manager.
*/
public function __construct(AttributeManager $manager) {
$this->manager = $manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.linkit.attribute')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return "linkit_attribute_add_form";
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ProfileInterface $linkit_profile = NULL) {
$this->linkitProfile = $linkit_profile;
$form['#attached']['library'][] = 'linkit/linkit.admin';
$header = [
'label' => $this->t('Attributes'),
'description' => $this->t('Description'),
];
$form['plugin'] = [
'#type' => 'tableselect',
'#header' => $header,
'#options' => $this->buildRows(),
'#empty' => $this->t('No attributes available.'),
'#multiple' => FALSE,
];
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save and continue'),
'#submit' => ['::submitForm'],
'#tableselect' => TRUE,
'#button_type' => 'primary',
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if (empty($form_state->getValue('plugin'))) {
$form_state->setErrorByName('plugin', $this->t('No attribute selected.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_state->cleanValues();
/** @var \Drupal\linkit\AttributeInterface $plugin */
$plugin = $this->manager->createInstance($form_state->getValue('plugin'));
$plugin_id = $this->linkitProfile->addAttribute($plugin->getConfiguration());
$this->linkitProfile->save();
$this->logger('linkit')->notice('Added %label attribute to the @profile profile.', [
'%label' => $this->linkitProfile->getAttribute($plugin_id)->getLabel(),
'@profile' => $this->linkitProfile->label(),
]);
$is_configurable = $plugin instanceof ConfigurableAttributeInterface;
if ($is_configurable) {
$form_state->setRedirect('linkit.attribute.edit', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $plugin_id,
]);
}
else {
drupal_set_message($this->t('Added %label attribute.', ['%label' => $plugin->getLabel()]));
$form_state->setRedirect('linkit.attributes', [
'linkit_profile' => $this->linkitProfile->id(),
]);
}
}
/**
* Builds the table rows.
*
* Only attributes that is not already applied to the profile are shown.
*
* @return array
* An array of table rows.
*/
private function buildRows() {
$rows = [];
$applied_plugins = $this->linkitProfile->getAttributes()->getConfiguration();
$all_plugins = $this->manager->getDefinitions();
uasort($all_plugins, function ($a, $b) {
return strnatcasecmp($a['label'], $b['label']);
});
foreach (array_diff_key($all_plugins, $applied_plugins) as $definition) {
/** @var \Drupal\linkit\AttributeInterface $plugin */
$plugin = $this->manager->createInstance($definition['id']);
$row = [
'label' => (string) $plugin->getLabel(),
'description' => (string) $plugin->getDescription(),
];
$rows[$plugin->getPluginId()] = $row;
}
return $rows;
}
}
@@ -0,0 +1,92 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Attribute\DeleteForm.
*/
namespace Drupal\linkit\Form\Attribute;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\linkit\ProfileInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Provides a form to remove an attribute from a profile.
*/
class DeleteForm extends ConfirmFormBase {
/**
* The profiles that the attribute is applied to.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* The attribute to be removed from the profile.
*
* @var \Drupal\linkit\AttributeInterface
*/
protected $linkitAttribute;
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete the @plugin attribute from the %profile profile?', ['%profile' => $this->linkitProfile->label(), '@plugin' => $this->linkitAttribute->getLabel()]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return Url::fromRoute('linkit.attributes', [
'linkit_profile' => $this->linkitProfile->id(),
]);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'linkit_attribute_delete_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ProfileInterface $linkit_profile = NULL, $plugin_instance_id = NULL) {
$this->linkitProfile = $linkit_profile;
if (!$this->linkitProfile->getAttributes()->has($plugin_instance_id)) {
throw new NotFoundHttpException();
}
$this->linkitAttribute = $this->linkitProfile->getAttribute($plugin_instance_id);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($this->linkitProfile->getAttributes()->has($this->linkitAttribute->getPluginId())) {
$this->linkitProfile->removeAttribute($this->linkitAttribute->getPluginId());
$this->linkitProfile->save();
drupal_set_message($this->t('The attribute %label has been deleted.', ['%label' => $this->linkitAttribute->getLabel()]));
$this->logger('linkit')->notice('The attribute %label has been deleted in the @profile profile.', [
'%label' => $this->linkitAttribute->getLabel(),
'@profile' => $this->linkitProfile->label(),
]);
}
$form_state->setRedirect('linkit.attributes', [
'linkit_profile' => $this->linkitProfile->id(),
]);
}
}
@@ -0,0 +1,96 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Attribute\EditForm.
*/
namespace Drupal\linkit\Form\Attribute;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\linkit\ProfileInterface;
/**
* Provides an edit form for attributes.
*/
class EditForm extends FormBase {
/**
* The profiles to which the attributes will be applied.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* The attribute to edit.
*
* @var \Drupal\linkit\ConfigurableAttributeInterface
*/
protected $linkitAttribute;
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'linkit_attribute_edit_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ProfileInterface $linkit_profile = NULL, $plugin_instance_id = NULL) {
$this->linkitProfile = $linkit_profile;
$this->linkitAttribute = $this->linkitProfile->getAttribute($plugin_instance_id);
$form['data'] = [
'#tree' => true,
];
$form['data'] += $this->linkitAttribute->buildConfigurationForm($form, $form_state);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save changes'),
'#submit' => array('::submitForm'),
'#button_type' => 'primary',
);
$form['actions']['delete'] = array(
'#type' => 'link',
'#title' => $this->t('Delete'),
'#url' => Url::fromRoute('linkit.attribute.delete', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $this->linkitAttribute->getPluginId(),
]),
'#attributes' => [
'class' => ['button', 'button--danger'],
],
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_state->cleanValues();
$plugin_data = (new FormState())->setValues($form_state->getValue('data'));
$this->linkitAttribute->submitConfigurationForm($form, $plugin_data);
$this->linkitProfile->save();
drupal_set_message($this->t('Saved %label configuration.', array('%label' => $this->linkitAttribute->getLabel())));
$this->logger('linkit')->notice('The attribute %label has been updated in the @profile profile.', [
'%label' => $this->linkitAttribute->getLabel(),
'@profile' => $this->linkitProfile->label(),
]);
$form_state->setRedirect('linkit.attributes', [
'linkit_profile' => $this->linkitProfile->id(),
]);
}
}
@@ -0,0 +1,156 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Attribute\OverviewForm.
*/
namespace Drupal\linkit\Form\Attribute;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\linkit\AttributeManager;
use Drupal\linkit\ConfigurableAttributeInterface;
use Drupal\linkit\ProfileInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an overview form for attribute on a profile.
*/
class OverviewForm extends FormBase {
/**
* The profiles to which the attributes are applied to.
*
* @var \Drupal\linkit\ProfileInterface
*/
private $linkitProfile;
/**
* The attribute manager.
*
* @var \Drupal\linkit\AttributeManager
*/
protected $manager;
/**
* Constructs a new OverviewForm.
*
* @param \Drupal\linkit\AttributeManager $manager
* The attribute manager.
*/
public function __construct(AttributeManager $manager) {
$this->manager = $manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.linkit.attribute')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return "linkit_attribute_overview_form";
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ProfileInterface $linkit_profile = NULL) {
$this->linkitProfile = $linkit_profile;
$form['plugins'] = [
'#type' => 'table',
'#header' => [
$this->t('Attribute'),
$this->t('Description'),
$this->t('Weight'),
$this->t('Operations'),
],
'#empty' => $this->t('No attributes added.'),
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'plugin-order-weight',
],
],
];
foreach ($this->linkitProfile->getAttributes() as $plugin) {
$key = $plugin->getPluginId();
$form['plugins'][$key]['#attributes']['class'][] = 'draggable';
$form['plugins'][$key]['#weight'] = $plugin->getWeight();
$form['plugins'][$key]['label'] = [
'#plain_text' => (string) $plugin->getLabel(),
];
$form['plugins'][$key]['description'] = [
'#plain_text' => (string) $plugin->getDescription(),
];
$form['plugins'][$key]['weight'] = [
'#type' => 'weight',
'#title' => t('Weight for @title', ['@title' => (string) $plugin->getLabel()]),
'#title_display' => 'invisible',
'#default_value' => $plugin->getWeight(),
'#attributes' => ['class' => ['plugin-order-weight']],
];
$form['plugins'][$key]['operations'] = [
'#type' => 'operations',
'#links' => [],
];
$is_configurable = $plugin instanceof ConfigurableAttributeInterface;
if ($is_configurable) {
$form['plugins'][$key]['operations']['#links']['edit'] = [
'title' => t('Edit'),
'url' => Url::fromRoute('linkit.attribute.edit', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $key,
]),
];
}
$form['plugins'][$key]['operations']['#links']['delete'] = [
'title' => t('Delete'),
'url' => Url::fromRoute('linkit.attribute.delete', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $key,
]),
];
}
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
'#button_type' => 'primary',
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
foreach ($form_state->getValue('plugins') as $id => $plugin_data) {
if ($this->linkitProfile->getAttributes()->has($id)) {
$this->linkitProfile->getAttribute($id)->setWeight($plugin_data['weight']);
}
}
$this->linkitProfile->save();
}
}
@@ -0,0 +1,207 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\LinkitEditorDialog.
*/
namespace Drupal\linkit\Form;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\editor\Ajax\EditorDialogSave;
use Drupal\Core\Ajax\CloseModalDialogCommand;
use Drupal\linkit\AttributeCollection;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a linkit dialog for text editors.
*/
class LinkitEditorDialog extends FormBase {
/**
* The editor storage service.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $editorStorage;
/**
* The linkit profile storage service.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $linkitProfileStorage;
/**
* The linkit profile.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* Constructs a form object for linkit dialog.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $editor_storage
* The editor storage service.
* @param \Drupal\Core\Entity\EntityStorageInterface $linkit_profile_storage
* The linkit profile storage service.
*/
public function __construct(EntityStorageInterface $editor_storage, EntityStorageInterface $linkit_profile_storage) {
$this->editorStorage = $editor_storage;
$this->linkitProfileStorage = $linkit_profile_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')->getStorage('editor'),
$container->get('entity.manager')->getStorage('linkit_profile')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'linkit_editor_dialog_form';
}
/**
* {@inheritdoc}
*
* @param \Drupal\filter\Entity\FilterFormat $filter_format
* The filter format for which this dialog corresponds.
*/
public function buildForm(array $form, FormStateInterface $form_state, FilterFormat $filter_format = NULL) {
// The default values are set directly from \Drupal::request()->request,
// provided by the editor plugin opening the dialog.
$user_input = $form_state->getUserInput();
$input = isset($user_input['editor_object']) ? $user_input['editor_object'] : [];
/** @var \Drupal\editor\EditorInterface $editor */
$editor = $this->editorStorage->load($filter_format->id());
$linkit_profile_id = $editor->getSettings()['plugins']['linkit']['linkit_profile'];
$this->linkitProfile = $this->linkitProfileStorage->load($linkit_profile_id);
$form['#tree'] = TRUE;
$form['#attached']['library'][] = 'editor/drupal.editor.dialog';
$form['#prefix'] = '<div id="linkit-editor-dialog-form">';
$form['#suffix'] = '</div>';
// Everything under the "attributes" key is merged directly into the
// generated link tag's attributes.
$form['attributes']['href'] = [
'#title' => $this->t('Link'),
'#type' => 'linkit',
'#default_value' => isset($input['href']) ? $input['href'] : '',
'#description' => $this->t('Start typing to find content or paste a URL.'),
'#autocomplete_route_name' => 'linkit.autocomplete',
'#autocomplete_route_parameters' => [
'linkit_profile_id' => $linkit_profile_id
],
'#weight' => 0,
];
$this->addAttributes($form, $form_state, $this->linkitProfile->getAttributes(), $input);
$form['actions'] = [
'#type' => 'actions',
];
$form['actions']['save_modal'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
'#submit' => [],
'#ajax' => [
'callback' => '::submitForm',
'event' => 'click',
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$attributes = array_filter($form_state->getValue('attributes'));
$form_state->setValue('attributes', $attributes);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
if ($form_state->getErrors()) {
unset($form['#prefix'], $form['#suffix']);
$form['status_messages'] = [
'#type' => 'status_messages',
'#weight' => -10,
];
$response->addCommand(new HtmlCommand('#linkit-editor-dialog-form', $form));
}
else {
$response->addCommand(new EditorDialogSave($form_state->getValues()));
$response->addCommand(new CloseModalDialogCommand());
}
return $response;
}
/**
* Adds the attributes enabled on the current profile.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param AttributeCollection $attributes
* A collection of attributes for the current profile.
* @param array $input
* An array with the attribute values from the editor.
*/
private function addAttributes(array &$form, FormStateInterface &$form_state, AttributeCollection $attributes, array $input) {
if ($attributes->count()) {
$form['linkit_attributes'] = [
'#type' => 'container',
'#title' => $this->t('Attributes'),
'#weight' => '10',
];
/** @var \Drupal\linkit\AttributeInterface $plugin */
foreach ($attributes as $plugin) {
$plugin_name = $plugin->getHtmlName();
$default_value = isset($input[$plugin_name]) ? $input[$plugin_name] : '';
$form['linkit_attributes'][$plugin_name] = $plugin->buildFormElement($default_value);
$form['linkit_attributes'][$plugin_name] += [
'#parents' => [
'attributes', $plugin_name,
],
];
}
}
}
/**
* Gets the linkit profile entity.
*
* @return \Drupal\linkit\ProfileInterface
* The current linkit profile used by this form.
*/
public function getLinkitProfile() {
return $this->linkitProfile;
}
}
@@ -0,0 +1,165 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Matcher\AddForm.
*/
namespace Drupal\linkit\Form\Matcher;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\linkit\ConfigurableMatcherInterface;
use Drupal\linkit\MatcherManager;
use Drupal\linkit\ProfileInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form to apply matchers to a profile.
*/
class AddForm extends FormBase {
/**
* The profiles to which the matchers will be applied.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* The matcher manager.
*
* @var \Drupal\linkit\MatcherManager
*/
protected $manager;
/**
* Constructs a new AddForm.
*
* @param \Drupal\linkit\MatcherManager $manager
* The matcher manager.
*/
public function __construct(MatcherManager $manager) {
$this->manager = $manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.linkit.matcher')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return "linkit_matcher_add_form";
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ProfileInterface $linkit_profile = NULL) {
$this->linkitProfile = $linkit_profile;
$form['#attached']['library'][] = 'linkit/linkit.admin';
$header = [
'label' => $this->t('Matchers'),
];
$form['plugin'] = [
'#type' => 'tableselect',
'#header' => $header,
'#options' => $this->buildRows(),
'#empty' => $this->t('No matchers available.'),
'#multiple' => FALSE,
];
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save and continue'),
'#submit' => ['::submitForm'],
'#tableselect' => TRUE,
'#button_type' => 'primary',
];
$options = [];
foreach ($this->manager->getDefinitions() as $id => $plugin) {
$options[$id] = $plugin['label'];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if (empty($form_state->getValue('plugin'))) {
$form_state->setErrorByName('plugin', $this->t('No matcher selected.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_state->cleanValues();
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->manager->createInstance($form_state->getValue('plugin'));
$plugin_uuid = $this->linkitProfile->addMatcher($plugin->getConfiguration());
$this->linkitProfile->save();
$this->logger('linkit')->notice('Added %label matcher to the @profile profile.', [
'%label' => $this->linkitProfile->getMatcher($plugin_uuid)->getLabel(),
'@profile' => $this->linkitProfile->label(),
]);
$is_configurable = $plugin instanceof ConfigurableMatcherInterface;
if ($is_configurable) {
$form_state->setRedirect('linkit.matcher.edit', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $plugin_uuid,
]);
}
else {
drupal_set_message($this->t('Added %label matcher.', ['%label' => $plugin->getLabel()]));
$form_state->setRedirect('linkit.matchers', [
'linkit_profile' => $this->linkitProfile->id(),
]);
}
}
/**
* Builds the table rows.
*
* @return array
* An array of table rows.
*/
private function buildRows() {
$rows = [];
$all_plugins = $this->manager->getDefinitions();
uasort($all_plugins, function ($a, $b) {
return strnatcasecmp($a['label'], $b['label']);
});
foreach ($all_plugins as $definition) {
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->manager->createInstance($definition['id']);
$row = [
'label' => $plugin->getLabel(),
];
$rows[$plugin->getPluginId()] = $row;
}
return $rows;
}
}
@@ -0,0 +1,90 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Matcher\DeleteForm.
*/
namespace Drupal\linkit\Form\Matcher;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\linkit\ProfileInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Provides a form to remove a matcher from a profile.
*/
class DeleteForm extends ConfirmFormBase {
/**
* The profiles that the matcher is applied to.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* The matcher to be removed from the profile.
*
* @var \Drupal\linkit\MatcherInterface
*/
protected $linkitMatcher;
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to delete the @plugin matcher from the %profile profile?', ['%profile' => $this->linkitProfile->label(), '@plugin' => $this->linkitMatcher->getLabel()]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return Url::fromRoute('linkit.matchers', [
'linkit_profile' => $this->linkitProfile->id(),
]);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'linkit_matcher_delete_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ProfileInterface $linkit_profile = NULL, $plugin_instance_id = NULL) {
$this->linkitProfile = $linkit_profile;
if (!$this->linkitProfile->getMatchers()->has($plugin_instance_id)) {
throw new NotFoundHttpException();
}
$this->linkitMatcher = $this->linkitProfile->getMatcher($plugin_instance_id);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->linkitProfile->removeMatcher($this->linkitMatcher);
drupal_set_message($this->t('The matcher %label has been deleted.', ['%label' => $this->linkitMatcher->getLabel()]));
$this->logger('linkit')->notice('The matcher %label has been deleted in the @profile profile.', [
'%label' => $this->linkitMatcher->getLabel(),
'@profile' => $this->linkitProfile->label(),
]);
$form_state->setRedirect('linkit.matchers', [
'linkit_profile' => $this->linkitProfile->id(),
]);
}
}
@@ -0,0 +1,92 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Matcher\EditForm.
*/
namespace Drupal\linkit\Form\Matcher;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\linkit\ProfileInterface;
/**
* Provides an edit form for matchers.
*/
class EditForm extends FormBase {
/**
* The profiles to which the matchers will be applied.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* The matcher to edit.
*
* @var \Drupal\linkit\ConfigurableMatcherInterface
*/
protected $linkitMatcher;
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'linkit_matcher_edit_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ProfileInterface $linkit_profile = NULL, $plugin_instance_id = NULL) {
$this->linkitProfile = $linkit_profile;
$this->linkitMatcher = $this->linkitProfile->getMatcher($plugin_instance_id);
$form += $this->linkitMatcher->buildConfigurationForm($form, $form_state);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save changes'),
'#submit' => array('::submitForm'),
'#button_type' => 'primary',
);
$form['actions']['delete'] = array(
'#type' => 'link',
'#title' => $this->t('Delete'),
'#url' => Url::fromRoute('linkit.matcher.delete', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $this->linkitMatcher->getUuid(),
]),
'#attributes' => [
'class' => ['button', 'button--danger'],
],
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_state->cleanValues();
$plugin_data = (new FormState())->setValues($form_state->getValues());
$this->linkitMatcher->submitConfigurationForm($form, $plugin_data);
$this->linkitProfile->save();
drupal_set_message($this->t('Saved %label configuration.', array('%label' => $this->linkitMatcher->getLabel())));
$this->logger('linkit')->notice('The matcher %label has been updated in the @profile profile.', [
'%label' => $this->linkitMatcher->getLabel(),
'@profile' => $this->linkitProfile->label(),
]);
$form_state->setRedirect('linkit.matchers', [
'linkit_profile' => $this->linkitProfile->id(),
]);
}
}
@@ -0,0 +1,165 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Matcher\OverviewForm.
*/
namespace Drupal\linkit\Form\Matcher;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\linkit\ConfigurableMatcherInterface;
use Drupal\linkit\MatcherManager;
use Drupal\linkit\ProfileInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an overview form for matchers on a profile.
*/
class OverviewForm extends FormBase {
/**
* The profiles to which the matchers are applied to.
*
* @var \Drupal\linkit\ProfileInterface
*/
private $linkitProfile;
/**
* The matcher manager.
*
* @var \Drupal\linkit\MatcherManager
*/
protected $manager;
/**
* Constructs a new OverviewForm.
*
* @param \Drupal\linkit\MatcherManager $manager
* The matcher manager.
*/
public function __construct(MatcherManager $manager) {
$this->manager = $manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.linkit.matcher')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return "linkit_matcher_overview_form";
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ProfileInterface $linkit_profile = NULL) {
$this->linkitProfile = $linkit_profile;
$form['#attached']['library'][] = 'linkit/linkit.admin';
$form['plugins'] = [
'#type' => 'table',
'#header' => [
[
'data' => $this->t('Matcher'),
'colspan' => 2
],
$this->t('Weight'),
$this->t('Operations'),
],
'#empty' => $this->t('No matchers added.'),
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'plugin-order-weight',
],
],
];
foreach ($this->linkitProfile->getMatchers() as $plugin) {
$key = $plugin->getUuid();
$form['plugins'][$key]['#attributes']['class'][] = 'draggable';
$form['plugins'][$key]['#weight'] = $plugin->getWeight();
$form['plugins'][$key]['label'] = [
'#plain_text' => (string) $plugin->getLabel(),
];
$form['plugins'][$key]['summary'] = [];
$summary = $plugin->getSummary();
if (!empty($summary)) {
$form['plugins'][$key]['summary'] = [
'#type' => 'inline_template',
'#template' => '<div class="linkit-plugin-summary">{{ summary|safe_join("<br />") }}</div>',
'#context' => ['summary' => $summary],
];
}
$form['plugins'][$key]['weight'] = [
'#type' => 'weight',
'#title' => t('Weight for @title', ['@title' => (string) $plugin->getLabel()]),
'#title_display' => 'invisible',
'#default_value' => $plugin->getWeight(),
'#attributes' => ['class' => ['plugin-order-weight']],
];
$form['plugins'][$key]['operations'] = [
'#type' => 'operations',
'#links' => [],
];
$is_configurable = $plugin instanceof ConfigurableMatcherInterface;
if ($is_configurable) {
$form['plugins'][$key]['operations']['#links']['edit'] = [
'title' => t('Edit'),
'url' => Url::fromRoute('linkit.matcher.edit', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $key,
]),
];
}
$form['plugins'][$key]['operations']['#links']['delete'] = [
'title' => t('Delete'),
'url' => Url::fromRoute('linkit.matcher.delete', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $key,
]),
];
}
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
'#button_type' => 'primary',
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
foreach ($form_state->getValue('plugins') as $id => $plugin_data) {
if ($this->linkitProfile->getMatchers()->has($id)) {
$this->linkitProfile->getMatcher($id)->setWeight($plugin_data['weight']);
}
}
$this->linkitProfile->save();
}
}
@@ -0,0 +1,28 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Profile\AddForm.
*/
namespace Drupal\linkit\Form\Profile;
use Drupal\Core\Form\FormStateInterface;
/**
* Controller for profile addition forms.
*
* @see \Drupal\linkit\Profile\FormBase
*/
class AddForm extends FormBase {
/**
* {@inheritdoc}
*/
public function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
$actions['submit']['#value'] = $this->t('Save and manage matchers');
return $actions;
}
}
@@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Profile\EditForm.
*/
namespace Drupal\linkit\Form\Profile;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides an edit form for profile.
*
* @see \Drupal\linkit\Profile\FormBase
*/
class EditForm extends FormBase {
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
$actions['submit']['#value'] = $this->t('Update profile');
$actions['delete']['#value'] = $this->t('Delete profile');
return $actions;
}
}
@@ -0,0 +1,90 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Form\Profile\FormBase.
*/
namespace Drupal\linkit\Form\Profile;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
/**
* Base form for profile add and edit forms.
*/
abstract class FormBase extends EntityForm {
/**
* The entity being used by this form.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Profile Name'),
'#default_value' => $this->entity->label(),
'#description' => $this->t('The human-readable name of this profile. This name must be unique.'),
'#required' => TRUE,
'#size' => 30,
];
$form['id'] = [
'#type' => 'machine_name',
'#default_value' => $this->entity->id(),
'#machine_name' => [
'exists' => ['\Drupal\linkit\Entity\Profile', 'load']
],
'#disabled' => !$this->entity->isNew(),
];
$form['description'] = [
'#type' => 'textarea',
'#title' => $this->t('Description'),
'#default_value' => $this->entity->getDescription(),
'#description' => $this->t('The text will be displayed on the <em>profile collection</em> page.'),
];
$form['additional_settings'] = array(
'#type' => 'vertical_tabs',
'#weight' => 99,
);
return parent::form($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$linkit_profile = $this->entity;
// Prevent leading and trailing spaces in linkit profile labels.
$linkit_profile->set('label', trim($linkit_profile->label()));
$status = $linkit_profile->save();
$edit_link = $this->entity->link($this->t('Edit'));
switch ($status) {
case SAVED_NEW:
drupal_set_message($this->t('Created new profile %label.', ['%label' => $linkit_profile->label()]));
$this->logger('linkit')->notice('Created new profile %label.', ['%label' => $linkit_profile->label(), 'link' => $edit_link]);
$form_state->setRedirect('linkit.matchers', [
'linkit_profile' => $linkit_profile->id(),
]);
break;
case SAVED_UPDATED:
drupal_set_message($this->t('Updated profile %label.', ['%label' => $linkit_profile->label()]));
$this->logger('linkit')->notice('Updated profile %label.', ['%label' => $linkit_profile->label(), 'link' => $edit_link]);
$form_state->setRedirectUrl($linkit_profile->urlInfo('edit-form'));
break;
}
}
}
@@ -0,0 +1,119 @@
<?php
/**
* @file
* Contains \Drupal\linkit\MatcherBase.
*/
namespace Drupal\linkit;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
/**
* Provides a base class for matchers.
*
* @see plugin_api
*/
abstract class MatcherBase extends PluginBase implements MatcherInterface, ContainerFactoryPluginInterface {
/**
* The matcher ID.
*
* @var string
*/
protected $uuid;
/**
* The weight of the matcher compared to others in a matcher collection.
*
* @var int
*/
protected $weight = 0;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->setConfiguration($configuration);
}
/**
* {@inheritdoc}
*/
public function getUuid() {
return $this->uuid;
}
/**
* {@inheritdoc}
*/
public function getLabel() {
return $this->pluginDefinition['label'];
}
/**
* {@inheritdoc}
*/
public function getSummary() {
return array();
}
/**
* {@inheritdoc}
*/
public function getWeight() {
return $this->weight;
}
/**
* {@inheritdoc}
*/
public function setWeight($weight) {
return $this->weight = $weight;
}
/**
* {@inheritdoc}
*/
public function getConfiguration() {
return [
'uuid' => $this->getUuid(),
'id' => $this->getPluginId(),
'weight' => $this->getWeight(),
'settings' => $this->configuration,
];
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration) {
$configuration += [
'uuid' => '',
'weight' => '0',
'settings' => [],
];
$this->configuration = $configuration['settings'] + $this->defaultConfiguration();
$this->uuid = $configuration['uuid'];
$this->weight = $configuration['weight'];
return $this;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return [];
}
}
@@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\linkit\MatcherCollection.
*/
namespace Drupal\linkit;
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
/**
* A collection of matchers.
*/
class MatcherCollection extends DefaultLazyPluginCollection {
/**
* All possible matcher IDs.
*
* @var array
*/
protected $definitions;
/**
* {@inheritdoc}
*
* @return \Drupal\linkit\MatcherInterface
*/
public function &get($instance_id) {
return parent::get($instance_id);
}
/**
* {@inheritdoc}
*/
public function sortHelper($aID, $bID) {
$a_weight = $this->get($aID)->getWeight();
$b_weight = $this->get($bID)->getWeight();
if ($a_weight == $b_weight) {
return strnatcasecmp($this->get($aID)->getLabel(), $this->get($bID)->getLabel());
}
return ($a_weight < $b_weight) ? -1 : 1;
}
}
@@ -0,0 +1,84 @@
<?php
/**
* @file
* Contains \Drupal\linkit\MatcherInterface.
*/
namespace Drupal\linkit;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Plugin\PluginFormInterface;
/**
* Defines the interface for matchers.
*
* @see \Drupal\linkit\Annotation\Matcher
* @see \Drupal\linkit\MatcherBase
* @see \Drupal\linkit\MatcherManager
* @see plugin_api
*/
interface MatcherInterface extends PluginInspectionInterface, ConfigurablePluginInterface {
/**
* Returns the unique ID representing the matcher.
*
* @return string
* The matcher ID.
*/
public function getUuid();
/**
* Returns the matcher label.
*
* @return string
* The matcher label.
*/
public function getLabel();
/**
* Returns the summarized configuration of the matcher.
*
* @return array
* An array of summarized configuration of the matcher.
*/
public function getSummary();
/**
* Returns the weight of the matcher.
*
* @return int|string
* Either the integer weight of the matcher, or an empty string.
*/
public function getWeight();
/**
* Sets the weight for the matcher.
*
* @param int $weight
* The weight for this matcher.
*
* @return $this
*/
public function setWeight($weight);
/**
* Gets an array with search matches that will be presented in the autocomplete
* widget.
*
* @param $string
* The string that contains the text to search for.
*
* @return array
* An array whose values are an associative array containing:
* - title: A string to use as the search result label.
* - description: (optional) A string with additional information about the
* result item.
* - path: The URL to the item.
* - group: (optional) A string with the group name for the result item.
* Best practice is to use the plugin name as group name.
*/
public function getMatches($string);
}
@@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\linkit\MatcherManager.
*/
namespace Drupal\linkit;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
/**
* Manages matchers.
*/
class MatcherManager extends DefaultPluginManager {
/**
* {@inheritdoc}
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/Linkit/Matcher', $namespaces, $module_handler, 'Drupal\linkit\MatcherInterface', 'Drupal\linkit\Annotation\Matcher');
$this->alterInfo('linkit_matcher');
$this->setCacheBackend($cache_backend, 'linkit_matchers');
}
}
@@ -0,0 +1,71 @@
<?php
/**
* @file
* Contains \Drupal\linkit\MatcherTokensTrait.
*/
namespace Drupal\linkit;
/**
* Provides friendly methods for matchers using tokens.
*/
trait MatcherTokensTrait {
/**
* Inserts a form element with a list of available tokens.
*
* @param $form
* The form array to append the token list to.
* @param array $types
* An array of token types to use.
*/
public function insertTokenList(&$form, array $types = array()) {
if (\Drupal::moduleHandler()->moduleExists('token')) {
// Add the token tree UI.
$form['token_tree'] = array(
'#theme' => 'token_tree_link',
'#token_types' => $types,
'#dialog' => TRUE,
'#weight' => -90,
);
}
else {
$token_items = array();
foreach ($this->getAvailableTokens($types) as $type => $tokens) {
foreach ($tokens as $name => $info) {
$token_description = !empty($info['description']) ? $info['description'] : '';
$token_items[$type . ':' . $name] = "[$type:$name]" . ' - ' . $info['name'] . ': ' . $token_description;
}
}
if (count($token_items)) {
$form['tokens'] = array(
'#type' => 'details',
'#title' => t('Available tokens'),
'#weight' => -90,
);
$form['tokens']['list'] = array(
'#theme' => 'item_list',
'#items' => $token_items,
);
}
}
}
/**
* Gets all available tokens.
*
* @param array $types
* An array of token types to use.
* @return array
* An array with available tokens
*/
public function getAvailableTokens(array $types = array()) {
$info = \Drupal::token()->getInfo();
$available = array_intersect_key($info['tokens'], array_flip($types));
return $available;
}
}
@@ -0,0 +1,123 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\CKEditorPlugin\Linkit.
*/
namespace Drupal\linkit\Plugin\CKEditorPlugin;
use Drupal\ckeditor\CKEditorPluginBase;
use Drupal\ckeditor\CKEditorPluginConfigurableInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\editor\Entity\Editor;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the "linkit" plugin.
*
* @CKEditorPlugin(
* id = "linkit",
* label = @Translation("Linkit"),
* module = "linkit"
* )
*/
class Linkit extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface, ContainerFactoryPluginInterface {
/**
* The Linkit profile storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $linkitProfileStorage;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $linkit_profile_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->linkitProfileStorage = $linkit_profile_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')->getStorage('linkit_profile')
);
}
/**
* {@inheritdoc}
*/
public function getFile() {
return drupal_get_path('module', 'linkit') . '/js/plugins/linkit/plugin.js';
}
/**
* {@inheritdoc}
*/
public function getConfig(Editor $editor) {
return array(
'linkit_dialogTitleAdd' => t('Add link'),
'linkit_dialogTitleEdit' => t('Edit link'),
);
}
/**
* {@inheritdoc}
*/
public function getButtons() {
return array(
'Linkit' => array(
'label' => t('Linkit'),
'image' => drupal_get_path('module', 'linkit') . '/js/plugins/linkit/linkit.png',
),
);
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor) {
$settings = $editor->getSettings();
$all_profiles = $this->linkitProfileStorage->loadMultiple();
$options = array();
foreach ($all_profiles as $profile) {
$options[$profile->id()] = $profile->label();
}
$form['linkit_profile'] = array(
'#type' => 'select',
'#title' => t('Select a linkit profile'),
'#options' => $options,
'#default_value' => isset($settings['plugins']['linkit']) ? $settings['plugins']['linkit'] : '',
'#empty_option' => $this->t('- Select profile -'),
'#description' => $this->t('Select the linkit profile you wish to use with this text format.'),
'#element_validate' => array(
array($this, 'validateLinkitProfileSelection'),
),
);
return $form;
}
/**
* #element_validate handler for the "linkit_profile" element in settingsForm().
*/
public function validateLinkitProfileSelection(array $element, FormStateInterface $form_state) {
$toolbar_buttons = $form_state->getValue(array('editor', 'settings', 'toolbar', 'button_groups'));
if (strpos($toolbar_buttons, '"Linkit"') !== FALSE && empty($element['#value'])) {
$form_state->setError($element, t('Please select the linkit profile you wish to use.'));
}
}
}
@@ -0,0 +1,67 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Derivative\EntityMatcherDeriver.
*/
namespace Drupal\linkit\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
*
* @see plugin_api
*/
class EntityMatcherDeriver extends DeriverBase implements ContainerDeriverInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Creates an EntityMatcherDeriver object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
$has_canonical = $entity_type->hasLinkTemplate('canonical');
if ($has_canonical) {
$this->derivatives[$entity_type_id] = $base_plugin_definition;
$this->derivatives[$entity_type_id]['id'] = $base_plugin_definition['id'] . ':' . $entity_type_id;
$this->derivatives[$entity_type_id]['label'] = $entity_type->getLabel();
$this->derivatives[$entity_type_id]['target_entity'] = $entity_type_id;
$this->derivatives[$entity_type_id]['base_plugin_label'] = (string) $base_plugin_definition['label'];
}
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
}
@@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Attribute\Accesskey.
*/
namespace Drupal\linkit\Plugin\Linkit\Attribute;
use Drupal\linkit\AttributeBase;
/**
* Accesskey attribute.
*
* @Attribute(
* id = "accesskey",
* label = @Translation("Accesskey"),
* html_name = "accesskey",
* description = @Translation("Basic input field for the accesskey attribute.")
* )
*/
class Accesskey extends AttributeBase {
/**
* {@inheritdoc}
*/
public function buildFormElement($default_value) {
return [
'#type' => 'textfield',
'#title' => t('Accesskey'),
'#default_value' => $default_value,
'#maxlength' => 255,
'#size' => 40,
'#placeholder' => t('The "accesskey" attribute value'),
];
}
}
@@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Attribute\Clazz.
*/
namespace Drupal\linkit\Plugin\Linkit\Attribute;
use Drupal\linkit\AttributeBase;
/**
* Class attribute.
*
* @TODO: For now Drupal filter_html wont support class attributes with
* wildcards.
* See: \Drupal\filter\Plugin\Filter\FilterHtml::getHTMLRestrictions
* See: core/modules/filter/filter.filter_html.admin.js
*
* @Attribute(
* id = "class",
* label = @Translation("Class"),
* html_name = "class",
* description = @Translation("Basic input field for the class attribute."),
* )
*/
//class Clazz extends AttributeBase {
//
// /**
// * {@inheritdoc}
// */
// public function buildFormElement($default_value) {
// return [
// '#type' => 'textfield',
// '#title' => t('Class'),
// '#maxlength' => 255,
// '#size' => 40,
// ];
// }
//
//}
@@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Attribute\Id.
*/
namespace Drupal\linkit\Plugin\Linkit\Attribute;
use Drupal\linkit\AttributeBase;
/**
* Id attribute.
*
* @Attribute(
* id = "id",
* label = @Translation("Id"),
* html_name = "id",
* description = @Translation("Basic input field for the id attribute."),
* )
*/
class Id extends AttributeBase {
/**
* {@inheritdoc}
*/
public function buildFormElement($default_value) {
return [
'#type' => 'textfield',
'#title' => t('Id'),
'#default_value' => $default_value,
'#maxlength' => 255,
'#size' => 40,
'#placeholder' => t('The "id" attribute value'),
];
}
}
@@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Attribute\Relationship.
*/
namespace Drupal\linkit\Plugin\Linkit\Attribute;
use Drupal\linkit\AttributeBase;
/**
* Relationship attribute.
*
* @Attribute(
* id = "relationship",
* label = @Translation("Relationship"),
* html_name = "rel",
* description = @Translation("Basic input field for the relationship attribute."),
* )
*/
class Relationship extends AttributeBase {
/**
* {@inheritdoc}
*/
public function buildFormElement($default_value) {
return [
'#type' => 'textfield',
'#title' => t('Relationship'),
'#default_value' => $default_value,
'#maxlength' => 255,
'#size' => 40,
'#placeholder' => t('The "rel" attribute value'),
];
}
}
@@ -0,0 +1,96 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Attribute\Target.
*/
namespace Drupal\linkit\Plugin\Linkit\Attribute;
use Drupal\Core\Form\FormStateInterface;
use Drupal\linkit\ConfigurableAttributeBase;
/**
* Target attribute.
*
* @Attribute(
* id = "target",
* label = @Translation("Target"),
* html_name = "target"
* )
*/
class Target extends ConfigurableAttributeBase {
const SELECT_LIST = 'select_list';
const SIMPLE_CHECKBOX = 'simple_checkbox';
/**
* {@inheritdoc}
*/
public function buildFormElement($default_value) {
switch ($this->configuration['widget_type']) {
case self::SELECT_LIST:
return [
'#type' => 'select',
'#title' => t('Target'),
'#options' => [
'' => '',
'_blank' => t('New window (_blank)'),
'_top' => t('Top window (_top)'),
'_self' => t('Same window (_self)'),
'_parent' => t('Parent window (_parent)')
],
'#default_value' => $default_value,
];
case self::SIMPLE_CHECKBOX:
return [
'#type' => 'checkbox',
'#title' => t('Open in new window'),
'#default_value' => $default_value,
'#return_value' => '_blank',
];
}
return [];
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'widget_type' => self::SIMPLE_CHECKBOX,
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['widget_type'] = [
'#type' => 'radios',
'#title' => $this->t('Widget type'),
'#default_value' => $this->configuration['widget_type'],
'#options' => [
self::SELECT_LIST => $this->t('Selectlist with predefined targets.'),
self::SIMPLE_CHECKBOX => $this->t('Simple checkbox to allow links to be opened in a new browser window or tab.'),
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['widget_type'] = $form_state->getValue('widget_type');
}
}
@@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Attribute\Title.
*/
namespace Drupal\linkit\Plugin\Linkit\Attribute;
use Drupal\Core\Form\FormStateInterface;
use Drupal\linkit\ConfigurableAttributeBase;
/**
* Title attribute.
*
* @Attribute(
* id = "title",
* label = @Translation("Title"),
* html_name = "title",
* description = @Translation("Basic input field for the title attribute.")
* )
*/
class Title extends ConfigurableAttributeBase {
/**
* {@inheritdoc}
*/
public function buildFormElement($default_value) {
$element = [
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $default_value,
'#maxlength' => 255,
'#size' => 40,
'#placeholder' => t('The "title" attribute value'),
];
if ($this->configuration['automatic_title']) {
$element['#attached']['library'][] = 'linkit/linkit.attribute.title';
$element['#placeholder'] = t('The "title" attribute value (auto populated)');
}
return $element;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'automatic_title' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['automatic_title'] = [
'#type' => 'checkbox',
'#title' => $this->t('Automatically populate title'),
'#default_value' => $this->configuration['automatic_title'],
'#description' => $this->t('Automatically populate the title attribute with the title from the match selection.'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['automatic_title'] = $form_state->getValue('automatic_title');
}
}
@@ -0,0 +1,348 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Matcher\EntityMatcher.
*/
namespace Drupal\linkit\Plugin\Linkit\Matcher;
use Drupal\Component\Utility\Html;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\linkit\ConfigurableMatcherBase;
use Drupal\linkit\MatcherTokensTrait;
use Drupal\linkit\Utility\LinkitXss;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @Matcher(
* id = "entity",
* label = @Translation("Entity"),
* deriver = "\Drupal\linkit\Plugin\Derivative\EntityMatcherDeriver"
* )
*/
class EntityMatcher extends ConfigurableMatcherBase {
use MatcherTokensTrait;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The target entity type id
*
* @var string
*/
protected $target_type;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
if (empty($plugin_definition['target_entity'])) {
throw new \InvalidArgumentException("Missing required 'target_entity' property for a matcher.");
}
$this->database = $database;
$this->entityManager = $entity_manager;
$this->moduleHandler = $module_handler;
$this->currentUser = $current_user;
$this->target_type = $plugin_definition['target_entity'];
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('database'),
$container->get('entity.manager'),
$container->get('module_handler'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function getSummary() {
$summery = parent::getSummary();
$entity_type = $this->entityManager->getDefinition($this->target_type);
$result_description = $this->configuration['result_description'];
if (!empty($result_description)) {
$summery[] = $this->t('Result description: @result_description', [
'@result_description' => $result_description
]);
}
if ($entity_type->hasKey('bundle')) {
$has_bundle_filter = !empty($this->configuration['bundles']);
$bundles = [];
if ($has_bundle_filter) {
$bundles_info = $this->entityManager->getBundleInfo($this->target_type);
foreach ($this->configuration['bundles'] as $bundle) {
$bundles[] = $bundles_info[$bundle]['label'];
}
}
$summery[] = $this->t('Bundle filter: @bundle_filter', [
'@bundle_filter' => $has_bundle_filter ? implode(', ', $bundles) : t('None'),
]);
$summery[] = $this->t('Group by bundle: @bundle_grouping', [
'@bundle_grouping' => $this->configuration['group_by_bundle'] ? $this->t('Yes') : $this->t('No'),
]);
}
return $summery;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'result_description' => '',
'bundles' => [],
'group_by_bundle' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$entity_type = $this->entityManager->getDefinition($this->target_type);
$form['result_description'] = [
'#title' => $this->t('Result description'),
'#type' => 'textfield',
'#default_value' => $this->configuration['result_description'],
'#size' => 120,
'#maxlength' => 255,
'#weight' => -100,
];
$this->insertTokenList($form, [$this->target_type]);
// Filter the possible bundles to use if the entity has bundles.
if ($entity_type->hasKey('bundle')) {
$bundle_options = [];
foreach ($this->entityManager->getBundleInfo($this->target_type) as $bundle_name => $bundle_info) {
$bundle_options[$bundle_name] = $bundle_info['label'];
}
$form['bundles'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Restrict to the selected bundles'),
'#options' => $bundle_options,
'#default_value' => $this->configuration['bundles'],
'#description' => $this->t('If none of the checkboxes is checked, allow all bundles.'),
'#element_validate' => [[get_class($this), 'elementValidateFilter']],
'#weight' => -50,
];
// Group the results by bundle.
$form['group_by_bundle'] = [
'#type' => 'checkbox',
'#title' => $this->t('Group by bundle'),
'#default_value' => $this->configuration['group_by_bundle'],
'#weight' => -50,
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['result_description'] = $form_state->getValue('result_description');
$this->configuration['bundles'] = $form_state->getValue('bundles');
$this->configuration['group_by_bundle'] = $form_state->getValue('group_by_bundle');
}
/**
* Form element validation handler; Filters the #value property of an element.
*/
public static function elementValidateFilter(&$element, FormStateInterface $form_state) {
$element['#value'] = array_filter($element['#value']);
$form_state->setValueForElement($element, $element['#value']);
}
/**
* {@inheritdoc}
*/
public function getMatches($string) {
$query = $this->buildEntityQuery($string);
$result = $query->execute();
if (empty($result)) {
return [];
}
$matches = [];
$entities = $this->entityManager->getStorage($this->target_type)->loadMultiple($result);
foreach ($entities as $entity_id => $entity) {
// Check the access against the defined entity access handler.
/** @var \Drupal\Core\Access\AccessResultInterface $access */
$access = $entity->access('view', $this->currentUser, TRUE);
if (!$access->isAllowed()) {
continue;
}
$matches[] = [
'title' => $this->buildLabel($entity),
'description' => $this->buildDescription($entity),
'path' => $this->buildPath($entity),
'group' => $this->buildGroup($entity),
];
}
return $matches;
}
/**
* Builds an EntityQuery to get entities.
*
* @param $match
* Text to match the label against.
*
* @return \Drupal\Core\Entity\Query\QueryInterface
* The EntityQuery object with the basic conditions and sorting applied to
* it.
*/
protected function buildEntityQuery($match) {
$match = $this->database->escapeLike($match);
$entity_type = $this->entityManager->getDefinition($this->target_type);
$query = $this->entityManager->getStorage($this->target_type)->getQuery();
$label_key = $entity_type->getKey('label');
if ($label_key) {
$query->condition($label_key, '%' . $match . '%', 'LIKE');
$query->sort($label_key, 'ASC');
}
// Bundle check.
if (!empty($this->configuration['bundles']) && $bundle_key = $entity_type->getKey('bundle')) {
$query->condition($bundle_key, $this->configuration['bundles'], 'IN');
}
// Add tags to let other modules alter the query.
$query->addTag('linkit_entity_autocomplete');
$query->addTag('linkit_entity_' . $this->target_type . '_autocomplete');
// Add access tag for the query.
$query->addTag('entity_access');
$query->addTag($this->target_type . '_access');
return $query;
}
/**
* Builds the label string used in the match array.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The matched entity.
*
* @return string
* The label for this entity.
*/
protected function buildLabel($entity) {
return Html::escape($entity->label());
}
/**
* Builds the description string used in the match array.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The matched entity.
*
* @return string
* The description for this entity.
*/
protected function buildDescription($entity) {
$description = \Drupal::token()->replace($this->configuration['result_description'], [$this->target_type => $entity], []);
return LinkitXss::descriptionFilter($description);
}
/**
* Builds the path string used in the match array.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The matched entity.
*
* @return string
* The URL for this entity.
*/
protected function buildPath($entity) {
return $entity->toUrl()->toString();
}
/**
* Builds the group string used in the match array.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The matched entity.
*
* @return string
* The match group for this entity.
*/
protected function buildGroup($entity) {
$group = $entity->getEntityType()->getLabel();
// If the entities by this entity should be grouped by bundle, get the
// name and append it to the group.
if ($this->configuration['group_by_bundle']) {
$bundles = $this->entityManager->getBundleInfo($entity->getEntityTypeId());
$bundle_label = $bundles[$entity->bundle()]['label'];
$group .= ' - ' . $bundle_label;
}
return $group;
}
}
@@ -0,0 +1,189 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Matcher\FileMatcher.
*/
namespace Drupal\linkit\Plugin\Linkit\Matcher;
use Drupal\Core\Form\FormStateInterface;
use Drupal\image\Entity\ImageStyle;
use Drupal\linkit\Utility\LinkitXss;
/**
* @Matcher(
* id = "entity:file",
* target_entity = "file",
* label = @Translation("File"),
* provider = "file"
* )
*/
class FileMatcher extends EntityMatcher {
/**
* {@inheritdoc}
*/
public function getSummary() {
$summery = parent::getSummary();
$summery[] = $this->t('Show image dimensions: @show_image_dimensions', [
'@show_image_dimensions' => $this->configuration['images']['show_dimensions'] ? $this->t('Yes') : $this->t('No'),
]);
$summery[] = $this->t('Show image thumbnail: @show_image_thumbnail', [
'@show_image_thumbnail' => $this->configuration['images']['show_thumbnail'] ? $this->t('Yes') : $this->t('No'),
]);
if ($this->moduleHandler->moduleExists('image') && $this->configuration['images']['show_thumbnail']) {
$image_style = ImageStyle::load($this->configuration['images']['thumbnail_image_style']);
if (!is_null($image_style)) {
$summery[] = $this->t('Thumbnail style: @thumbnail_style', [
'@thumbnail_style' => $image_style->label(),
]);
}
}
return $summery;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'images' => [
'show_dimensions' => FALSE,
'show_thumbnail' => FALSE,
'thumbnail_image_style' => 'linkit_result_thumbnail',
],
];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies() + [
'module' => ['file'],
];
if ($this->configuration['images']['show_thumbnail']) {
$dependencies['module'][] = 'image';
$dependencies['config'][] = 'image.style.' . $this->configuration['images']['thumbnail_image_style'];
}
return $dependencies;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['images'] = array(
'#type' => 'details',
'#title' => t('Image file settings'),
'#description' => t('Extra settings for image files in the result.'),
'#tree' => TRUE,
);
$form['images']['show_dimensions'] = [
'#title' => t('Show pixel dimensions'),
'#type' => 'checkbox',
'#default_value' => $this->configuration['images']['show_dimensions'],
];
if ($this->moduleHandler->moduleExists('image')) {
$form['images']['show_thumbnail'] = [
'#title' => t('Show thumbnail'),
'#type' => 'checkbox',
'#default_value' => $this->configuration['images']['show_thumbnail'],
];
$form['images']['thumbnail_image_style'] = [
'#title' => t('Thumbnail image style'),
'#type' => 'select',
'#default_value' => $this->configuration['images']['thumbnail_image_style'],
'#options' => image_style_options(FALSE),
'#states' => [
'visible' => [
':input[name="images[show_thumbnail]"]' => ['checked' => TRUE],
],
],
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$values = $form_state->getValue('images');
if (!$values['show_thumbnail']) {
$values['thumbnail_image_style'] = NULL;
}
$this->configuration['images'] = $values;
}
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match) {
$query = parent::buildEntityQuery($match);
$query->condition('status', FILE_STATUS_PERMANENT);
return $query;
}
/**
* {@inheritdoc}
*/
protected function buildDescription($entity) {
$description_array = array();
$description_array[] = parent::buildDescription($entity);
/** @var \Drupal\file\FileInterface $entity */
$file = $entity->getFileUri();
/** @var \Drupal\Core\Image\ImageInterface $image */
$image = \Drupal::service('image.factory')->get($file);
if ($image->isValid()) {
if ($this->configuration['images']['show_dimensions']) {
$description_array[] = $image->getWidth() . 'x' . $image->getHeight() . 'px';
}
if ($this->configuration['images']['show_thumbnail'] && $this->moduleHandler->moduleExists('image')) {
$image_element = array(
'#weight' => -10,
'#theme' => 'image_style',
'#style_name' => $this->configuration['images']['thumbnail_image_style'],
'#uri' => $entity->getFileUri(),
);
$description_array[] = (string) \Drupal::service('renderer')->render($image_element);
}
}
$description = implode('<br />' , $description_array);
return LinkitXss::descriptionFilter($description);
}
/**
* {@inheritdoc}
*
* The file entity still uses url() even though it's deprecated in the
* entity interface.
*/
protected function buildPath($entity) {
/** @var \Drupal\file\FileInterface $entity */
return file_url_transform_relative(file_create_url($entity->getFileUri()));
}
}
@@ -0,0 +1,92 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Matcher\NodeMatcher.
*/
namespace Drupal\linkit\Plugin\Linkit\Matcher;
use Drupal\Core\Form\FormStateInterface;
/**
* @Matcher(
* id = "entity:node",
* target_entity = "node",
* label = @Translation("Content"),
* provider = "node"
* )
*/
class NodeMatcher extends EntityMatcher {
/**
* {@inheritdoc}
*/
public function getSummary() {
$summery = parent::getSummary();
$summery[] = $this->t('Include unpublished: @include_unpublished', [
'@include_unpublished' => $this->configuration['include_unpublished'] ? $this->t('Yes') : $this->t('No'),
]);
return $summery;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'include_unpublished' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return parent::calculateDependencies() + [
'module' => ['node'],
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['include_unpublished'] = [
'#title' => t('Include unpublished nodes'),
'#type' => 'checkbox',
'#default_value' => $this->configuration['include_unpublished'],
'#description' => t('In order to see unpublished nodes, the requesting user must also have permissions to do so.'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$this->configuration['include_unpublished'] = $form_state->getValue('include_unpublished');
}
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match) {
$query = parent::buildEntityQuery($match);
$no_access = !$this->currentUser->hasPermission('bypass node access') && !count($this->moduleHandler->getImplementations('node_grants'));
if ($this->configuration['include_unpublished'] !== TRUE || $no_access) {
$query->condition('status', NODE_PUBLISHED);
}
return $query;
}
}
@@ -0,0 +1,49 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Matcher\TermMatcher.
*/
namespace Drupal\linkit\Plugin\Linkit\Matcher;
use Drupal\Core\Form\FormStateInterface;
use Drupal\linkit\Utility\LinkitXss;
/**
* @Matcher(
* id = "entity:taxonomy_term",
* target_entity = "taxonomy_term",
* label = @Translation("Taxonomy term"),
* provider = "taxonomy"
* )
*/
class TermMatcher extends EntityMatcher {
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return parent::calculateDependencies() + [
'module' => ['taxonomy'],
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$this->insertTokenList($form, ['term']);
return $form;
}
/**
* {@inheritdoc}
*/
protected function buildDescription($entity) {
$description = \Drupal::token()->replace($this->configuration['result_description'], ['term' => $entity], []);
return LinkitXss::descriptionFilter($description);
}
}
@@ -0,0 +1,117 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Plugin\Linkit\Matcher\UserMatcher.
*/
namespace Drupal\linkit\Plugin\Linkit\Matcher;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\RoleInterface;
/**
* @Matcher(
* id = "entity:user",
* target_entity = "user",
* label = @Translation("User"),
* provider = "user"
* )
*/
class UserMatcher extends EntityMatcher {
/**
* {@inheritdoc}
*/
public function getSummary() {
$summery = parent::getSummary();
$roles = !empty($this->configuration['roles']) ? $this->configuration['roles'] : ['None'];
$summery[] = $this->t('Role filter: @role_filter', [
'@role_filter' => implode(', ', $roles),
]);
$summery[] = $this->t('Include blocked users: @include_blocked', [
'@include_blocked' => $this->configuration['include_blocked'] ? $this->t('Yes') : $this->t('No'),
]);
return $summery;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'roles' => [],
'include_blocked' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return parent::calculateDependencies() + [
'module' => ['user'],
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['roles'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Restrict to the selected roles'),
'#options' => array_diff_key(user_role_names(TRUE), array(RoleInterface::AUTHENTICATED_ID => RoleInterface::AUTHENTICATED_ID)),
'#default_value' => $this->configuration['roles'],
'#description' => $this->t('If none of the checkboxes is checked, allow all roles.'),
'#element_validate' => [[get_class($this), 'elementValidateFilter']],
);
$form['include_blocked'] = [
'#title' => t('Include blocked user'),
'#type' => 'checkbox',
'#default_value' => $this->configuration['include_blocked'],
'#description' => t('In order to see blocked users, the requesting user must also have permissions to do so.'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$this->configuration['roles'] = $form_state->getValue('roles');
$this->configuration['include_blocked'] = $form_state->getValue('include_blocked');
}
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match) {
$query = parent::buildEntityQuery($match);
$match = $this->database->escapeLike($match);
// The user entity don't specify a label key so we have to do it instead.
$query->condition('name', '%' . $match . '%', 'LIKE');
// Filter by role.
if (!empty($this->configuration['roles'])) {
$query->condition('roles', $this->configuration['roles'], 'IN');
}
if ($this->configuration['include_blocked'] !== TRUE || !$this->currentUser->hasPermission('administer users')) {
$query->condition('status', 1);
}
return $query;
}
}
@@ -0,0 +1,139 @@
<?php
/**
* @file
* Contains \Drupal\linkit\ProfileInterface.
*/
namespace Drupal\linkit;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface defining a profile entity.
*/
interface ProfileInterface extends ConfigEntityInterface {
/**
* Gets the profile description.
*
* @return string
* The profile description.
*/
public function getDescription();
/**
* Sets the profile description.
*
* @param string $description
* The profile description.
*
* @return $this
*/
public function setDescription($description);
/**
* Returns a specific attribute.
*
* @param string $attribute_id
* The attribute ID.
*
* @return \Drupal\linkit\AttributeInterface
* The attribute object.
*/
public function getAttribute($attribute_id);
/**
* Returns the attributes for this profile.
*
* @return \Drupal\linkit\AttributeCollection|\Drupal\linkit\AttributeInterface[]
* The attribute collection.
*/
public function getAttributes();
/**
* Adds an attribute to this profile.
*
* @param array $configuration
* An array of attribute configuration.
*
* @return String
* The ID of the attribute.
*/
public function addAttribute(array $configuration);
/**
* Removes an attribute from this profile.
*
* @param string $attribute_id
* The attribute ID.
*
* @return $this
*/
public function removeAttribute($attribute_id);
/**
* Sets the configuration for an attribute instance.
*
* @param string $attribute_id
* The ID of the attribute to set the configuration for.
* @param array $configuration
* The attribute configuration to set.
*
* @return $this
*/
public function setAttributeConfig($attribute_id, array $configuration);
/**
* Returns a specific matcher.
*
* @param string $instance_id
* The matcher instance ID.
*
* @return \Drupal\linkit\MatcherInterface
* The matcher object.
*/
public function getMatcher($instance_id);
/**
* Returns the matchers for this profile.
*
* @return \Drupal\linkit\MatcherCollection|\Drupal\linkit\MatcherInterface[]
* The matcher collection.
*/
public function getMatchers();
/**
* Adds a matcher to this profile.
*
* @param array $configuration
* An array of matcher configuration.
*
* @return string
* The instance ID of the matcher.
*/
public function addMatcher(array $configuration);
/**
* Removes a matcher from this profile.
*
* @param \Drupal\linkit\MatcherInterface $matcher
* The matcher object.
*
* @return $this
*/
public function removeMatcher(MatcherInterface $matcher);
/**
* Sets the configuration for a matcher instance.
*
* @param string $instance_id
* The instance ID of the matcher to set the configuration for.
* @param array $configuration
* The matcher configuration to set.
*
* @return $this
*/
public function setMatcherConfig($instance_id, array $configuration);
}
@@ -0,0 +1,73 @@
<?php
/**
* @file
* Contains \Drupal\linkit\ProfileListBuilder.
*/
namespace Drupal\linkit;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
/**
* Defines a class to build a listing of profile entities.
*
* @see \Drupal\linkit\Entity\Profile
*/
class ProfileListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['title'] = t('Profile');
$header['description'] = [
'data' => t('Description'),
'class' => [RESPONSIVE_PRIORITY_MEDIUM],
];
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
/** @var \Drupal\linkit\ProfileInterface $linkitProfile */
$linkitProfile = $entity;
$row['label'] = $linkitProfile->label();
$row['description']['data'] = ['#markup' => $linkitProfile->getDescription()];
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if (isset($operations['edit'])) {
$operations['edit']['title'] = t('Edit profile');
}
$operations['matchers'] = [
'title' => t('Manage matchers'),
'weight' => 10,
'url' => Url::fromRoute('linkit.matchers', [
'linkit_profile' => $entity->id()
]),
];
$operations['attributes'] = [
'title' => t('Manage attributes'),
'weight' => 20,
'url' => Url::fromRoute('linkit.attributes', [
'linkit_profile' => $entity->id()
]),
];
return $operations;
}
}
@@ -0,0 +1,74 @@
<?php
/**
* @file
* Contains \Drupal\linkit\ResultManager.
*/
namespace Drupal\linkit;
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
/**
* Result service to handle autocomplete matcher results.
*/
class ResultManager {
/**
* Gets the results.
*
* @param ProfileInterface $linkitProfile
* The linkit profile.
* @param $search_string
* The string ro use in the matchers.
*
* @return array
* An array of matches.
*/
public function getResults(ProfileInterface $linkitProfile, $search_string) {
$matches = array();
if (empty(trim($search_string))) {
return [[
'title' => t('No results'),
]];
}
// Special for link to front page.
if (strpos($search_string, 'front') !== FALSE) {
$matches[] = [
'title' => t('Front page'),
'description' => 'The front page for this site.',
'path' => Url::fromRoute('<front>')->toString(),
'group' => t('System'),
];
}
foreach ($linkitProfile->getMatchers() as $plugin) {
$matches = array_merge($matches, $plugin->getMatches($search_string));
}
// Check for an e-mail address then return an e-mail match and create a
// mail-to link if appropriate.
if (filter_var($search_string, FILTER_VALIDATE_EMAIL)) {
$matches[] = [
'title' => t('E-mail @email', ['@email' => $search_string]),
'description' => t('Opens your mail client ready to e-mail @email', ['@email' => $search_string]),
'path' => 'mailto:' . Html::escape($search_string),
'group' => t('E-mail'),
];
}
// If there is still no matches, return a "no results" array.
if (empty($matches)) {
return [[
'title' => t('No results'),
]];
}
return $matches;
}
}
@@ -0,0 +1,160 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Tests\AttributeCrudTest.
*/
namespace Drupal\linkit\Tests;
use Drupal\Core\Url;
use Drupal\linkit\Entity\Profile;
/**
* Tests adding, listing and deleting attributes on a profile.
*
* @group linkit
*/
class AttributeCrudTest extends LinkitTestBase {
/**
* The attribute manager.
*
* @var \Drupal\linkit\AttributeManager
*/
protected $manager;
/**
* The linkit profile.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->manager = $this->container->get('plugin.manager.linkit.attribute');
$this->linkitProfile = $this->createProfile();
$this->drupalLogin($this->adminUser);
}
/**
* Test the overview page.
*/
function testOverview() {
$this->drupalGet(Url::fromRoute('linkit.attributes', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$this->assertText(t('No attributes added.'));
$this->assertLinkByHref(Url::fromRoute('linkit.attribute.add', [
'linkit_profile' => $this->linkitProfile->id(),
])->toString());
}
/**
* Test adding an attribute to a profile.
*/
function testAdd() {
$this->drupalGet(Url::fromRoute('linkit.attribute.add', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$this->assertEqual(count($this->manager->getDefinitions()), count($this->xpath('//input[@type="radio"]')), 'All attributes are available.');
$edit = array();
$edit['plugin'] = 'dummy_attribute';
$this->drupalPostForm(NULL, $edit, t('Save and continue'));
$this->assertUrl(Url::fromRoute('linkit.attributes', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$this->assertEqual(1, count($this->xpath('//table/tbody/tr')), 'Attribute added.');
$this->assertNoText(t('No attributes added.'));
}
/**
* Test adding a configurable attribute to a profile.
*/
function testAddConfigurable() {
$this->drupalGet(Url::fromRoute('linkit.attribute.add', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$this->assertEqual(count($this->manager->getDefinitions()), count($this->xpath('//input[@type="radio"]')), 'All attributes are available.');
$edit = array();
$edit['plugin'] = 'configurable_dummy_attribute';
$this->drupalPostForm(NULL, $edit, t('Save and continue'));
$this->assertUrl(Url::fromRoute('linkit.attribute.edit', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => 'configurable_dummy_attribute',
]));
$this->drupalGet(Url::fromRoute('linkit.attributes', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$this->assertEqual(1, count($this->xpath('//table/tbody/tr')), 'Attribute added.');
$this->assertNoText(t('No attributes added.'));
$plugin_url = Url::fromRoute('linkit.attribute.edit', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => 'configurable_dummy_attribute',
]);
$this->assertLinkByHref($plugin_url->toString());
}
/**
* Test delete an attribute from a profile.
*/
function testDelete() {
/** @var \Drupal\linkit\AttributeInterface $plugin */
$plugin = $this->manager->createInstance('dummy_attribute');
$this->linkitProfile->addAttribute($plugin->getConfiguration());
$this->linkitProfile->save();
// Try delete an attribute that is not attached to the profile.
$this->drupalGet(Url::fromRoute('linkit.attribute.delete', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => 'doesntexists'
]));
$this->assertResponse('404');
// Go to the delete page, but press cancel.
$this->drupalGet(Url::fromRoute('linkit.attribute.delete', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $plugin->getPluginId(),
]));
$this->clickLink(t('Cancel'));
$this->assertUrl(Url::fromRoute('linkit.attributes', [
'linkit_profile' => $this->linkitProfile->id(),
]));
// Delete the attribute from the profile.
$this->drupalGet(Url::fromRoute('linkit.attribute.delete', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => 'dummy_attribute',
]));
$this->drupalPostForm(NULL, [], t('Confirm'));
$this->assertRaw(t('The attribute %plugin has been deleted.', ['%plugin' => $plugin->getLabel()]));
$this->assertUrl(Url::fromRoute('linkit.attributes', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$this->assertText(t('No attributes added.'));
/** @var \Drupal\linkit\Entity\Profile $updated_profile */
$updated_profile = Profile::load($this->linkitProfile->id());
$this->assertFalse($updated_profile->getAttributes()->has($plugin->getPluginId()), 'The attribute is deleted from the profile');
}
}
@@ -0,0 +1,83 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Tests\Controllers\LinkitControllerTest.
*/
namespace Drupal\linkit\Tests\Controllers;
use Drupal\Core\Url;
use Drupal\linkit\Tests\LinkitTestBase;
/**
* Tests Linkit controller.
*
* @group linkit
*/
class LinkitControllerTest extends LinkitTestBase {
/**
* The linkit profile.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->linkitProfile = $this->createProfile();
$this->drupalLogin($this->adminUser);
}
/**
* Tests the profile route title callback.
*/
function testProfileTitle() {
$this->drupalGet(Url::fromRoute('entity.linkit_profile.edit_form', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$this->assertText('Edit ' . $this->linkitProfile->label() . ' profile');
}
/**
* Tests the matcher route title callback.
*/
function testMatcherTitle() {
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->container->get('plugin.manager.linkit.matcher')->createInstance('configurable_dummy_matcher');
$matcher_uuid = $this->linkitProfile->addMatcher($plugin->getConfiguration());
$this->linkitProfile->save();
$this->drupalGet(Url::fromRoute('linkit.matcher.edit', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $matcher_uuid,
]));
$this->assertText('Edit ' . $plugin->getLabel() . ' matcher');
}
/**
* Tests the attribute route title callback.
*/
function testAttributeTitle() {
/** @var \Drupal\linkit\AttributeInterface $plugin */
$plugin = $this->container->get('plugin.manager.linkit.attribute')->createInstance('configurable_dummy_attribute');
$this->linkitProfile->addAttribute($plugin->getConfiguration());
$this->linkitProfile->save();
$this->drupalGet(Url::fromRoute('linkit.attribute.edit', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $plugin->getPluginId(),
]));
$this->assertText('Edit ' . $plugin->getLabel() . ' attribute');
}
}
@@ -0,0 +1,83 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Tests\LinkitTestBase.
*/
namespace Drupal\linkit\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\linkit\Entity\Profile;
use Drupal\simpletest\WebTestBase;
/**
* Sets up page and article content types.
*/
abstract class LinkitTestBase extends WebTestBase {
/**
* Modules to enable.
*
* Enable block module to get the local_actions_block to work.
*
* @var array
*/
public static $modules = ['linkit', 'linkit_test', 'block'];
/**
* A user with the 'administer linkit profiles' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* A user without the 'administer linkit profiles' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $baseUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(['administer linkit profiles']);
$this->baseUser = $this->drupalCreateUser();
$this->drupalPlaceBlock('page_title_block', ['region' => 'content']);
$this->drupalPlaceBlock('local_tasks_block', ['region' => 'content']);
$this->drupalPlaceBlock('local_actions_block', ['region' => 'content']);
$this->drupalPlaceBlock('system_messages_block', ['region' => 'highlighted']);
}
/**
* Creates a profile based on default settings.
*
* @param array $settings
* (optional) An associative array of settings for the profile, as used in
* entity_create(). Override the defaults by specifying the key and value
* in the array
*
* The following defaults are provided:
* - label: Random string.
*
* @return \Drupal\linkit\ProfileInterface
* The created profile entity.
*/
protected function createProfile(array $settings = []) {
// Populate defaults array.
$settings += [
'id' => Unicode::strtolower($this->randomMachineName()),
'label' => $this->randomMachineName(),
];
$profile = Profile::create($settings);
$profile->save();
return $profile;
}
}
@@ -0,0 +1,158 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Tests\MatcherCrudTest.
*/
namespace Drupal\linkit\Tests;
use Drupal\Core\Url;
use Drupal\linkit\Entity\Profile;
/**
* Tests adding, listing, updating and deleting matchers on a profile.
*
* @group linkit
*/
class MatcherCrudTest extends LinkitTestBase {
/**
* The attribute manager.
*
* @var \Drupal\linkit\MatcherManager
*/
protected $manager;
/**
* The linkit profile.
*
* @var \Drupal\linkit\ProfileInterface
*/
protected $linkitProfile;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->manager = $this->container->get('plugin.manager.linkit.matcher');
$this->linkitProfile = $this->createProfile();
$this->drupalLogin($this->adminUser);
}
/**
* Test the overview page.
*/
function testOverview() {
$this->drupalGet(Url::fromRoute('linkit.matchers', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$this->assertText(t('No matchers added.'));
$this->assertLinkByHref(Url::fromRoute('linkit.matcher.add', [
'linkit_profile' => $this->linkitProfile->id(),
])->toString());
}
/**
* Test adding a matcher to a profile.
*/
function testAdd() {
$this->drupalGet(Url::fromRoute('linkit.matcher.add', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$edit = array();
$edit['plugin'] = 'dummy_matcher';
$this->drupalPostForm(NULL, $edit, t('Save and continue'));
// Load the saved profile.
$this->linkitProfile = Profile::load($this->linkitProfile->id());
$matcher_ids = $this->linkitProfile->getMatchers()->getInstanceIds();
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->linkitProfile->getMatcher(current($matcher_ids));
$this->assertRaw(t('Added %label matcher.', ['%label' => $plugin->getLabel()]));
$this->assertNoText(t('No matchers added.'));
}
/**
* Test adding a configurable attribute to a profile.
*/
function testAddConfigurable() {
$this->drupalGet(Url::fromRoute('linkit.matcher.add', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$edit = array();
$edit['plugin'] = 'configurable_dummy_matcher';
$this->drupalPostForm(NULL, $edit, t('Save and continue'));
// Load the saved profile.
$this->linkitProfile = Profile::load($this->linkitProfile->id());
$matcher_ids = $this->linkitProfile->getMatchers()->getInstanceIds();
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->linkitProfile->getMatcher(current($matcher_ids));
$this->assertUrl(Url::fromRoute('linkit.matcher.edit', [
'linkit_profile' => $this->linkitProfile->id(),
'plugin_instance_id' => $plugin->getUuid(),
]));
$this->drupalGet(Url::fromRoute('linkit.matchers', [
'linkit_profile' => $this->linkitProfile->id(),
]));
$this->assertNoText(t('No matchers added.'));
}
/**
* Test delete a matcher from a profile.
*/
function testDelete() {
/** @var \Drupal\linkit\AttributeInterface $plugin */
$plugin = $this->manager->createInstance('dummy_matcher');
$profile = $this->createProfile();
$plugin_uuid = $profile->addMatcher($plugin->getConfiguration());
$profile->save();
// Try delete a matcher that is not attached to the profile.
$this->drupalGet(Url::fromRoute('linkit.matcher.delete', [
'linkit_profile' => $profile->id(),
'plugin_instance_id' => 'doesntexists'
]));
$this->assertResponse('404');
// Go to the delete page, but press cancel.
$this->drupalGet(Url::fromRoute('linkit.matcher.delete', [
'linkit_profile' => $profile->id(),
'plugin_instance_id' => $plugin_uuid,
]));
$this->clickLink(t('Cancel'));
$this->assertUrl(Url::fromRoute('linkit.matchers', [
'linkit_profile' => $profile->id(),
]));
// Delete the matcher from the profile.
$this->drupalGet(Url::fromRoute('linkit.matcher.delete', [
'linkit_profile' => $profile->id(),
'plugin_instance_id' => $plugin_uuid,
]));
$this->drupalPostForm(NULL, [], t('Confirm'));
$this->assertRaw(t('The matcher %plugin has been deleted.', ['%plugin' => $plugin->getLabel()]));
$this->assertUrl(Url::fromRoute('linkit.matchers', [
'linkit_profile' => $profile->id(),
]));
$this->assertText(t('No matchers added.'));
/** @var \Drupal\linkit\Entity\Profile $updated_profile */
$updated_profile = Profile::load($profile->id());
$this->assertFalse($updated_profile->getMatchers()->has($plugin_uuid), 'The user matcher is deleted from the profile');
}
}
@@ -0,0 +1,105 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Tests\Matchers\NodeMatcherTest.
*/
namespace Drupal\linkit\Tests\Matchers;
use Drupal\linkit\Tests\LinkitTestBase;
/**
* Tests node matcher.
*
* @group linkit
*/
class NodeMatcherTest extends LinkitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node'];
/**
* The matcher manager.
*
* @var \Drupal\linkit\MatcherManager
*/
protected $manager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->adminUser);
$this->manager = $this->container->get('plugin.manager.linkit.matcher');
$type1 = $this->drupalCreateContentType(['type' => 'test1', 'name' => 'Test1']);
$type2 = $this->drupalCreateContentType(['type' => 'test2', 'name' => 'Test2']);
// Nodes with type 1.
$this->drupalCreateNode(['title' => 'Lorem Ipsum 1', 'type' => $type1->id()]);
$this->drupalCreateNode(['title' => 'Lorem Ipsum 2', 'type' => $type1->id()]);
// Nodes with type 1.
$this->drupalCreateNode(['title' => 'Lorem Ipsum 3', 'type' => $type2->id()]);
// Unpublished node.
$this->drupalCreateNode(['title' => 'Lorem unpublishd', 'type' => $type1->id(), 'status' => FALSE]);
}
/**
* Tests node matcher.
*/
function testNodeMatcherWidthDefaultConfiguration() {
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->manager->createInstance('entity:node', []);
$matches = $plugin->getMatches('Lorem');
$this->assertEqual(3, count($matches), 'Correct number of matches');
}
/**
* Tests node matcher with bundle filer.
*/
function testNodeMatcherWidthBundleFiler() {
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->manager->createInstance('entity:node', [
'settings' => [
'bundles' => [
'test1' => 'test1'
],
],
]);
$matches = $plugin->getMatches('Lorem');
$this->assertEqual(2, count($matches), 'Correct number of matches');
}
/**
* Tests node matcher with include unpublished setting activated.
*/
function testNodeMatcherWidthIncludeUnpublished() {
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->manager->createInstance('entity:node', [
'settings' => [
'include_unpublished' => TRUE,
],
]);
// Test without permissions to see unpublished nodes.
$matches = $plugin->getMatches('Lorem');
$this->assertEqual(3, count($matches), 'Correct number of matches');
$account = $this->drupalCreateUser(['bypass node access']);
$this->drupalLogin($account);
// Test with permissions to see unpublished nodes.
$matches = $plugin->getMatches('Lorem');
$this->assertEqual(4, count($matches), 'Correct number of matches');
}
}
@@ -0,0 +1,134 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Tests\Matchers\TermMatcherTest.
*/
namespace Drupal\linkit\Tests\Matchers;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Language\LanguageInterface;
use Drupal\linkit\Tests\LinkitTestBase;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests term matcher.
*
* @group linkit
*/
class TermMatcherTest extends LinkitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['taxonomy'];
/**
* The matcher manager.
*
* @var \Drupal\linkit\MatcherManager
*/
protected $manager;
/**
* Creates and saves a vocabulary.
*
* @param string $name
* The vocabulary name.
*
* @return Vocabulary The new vocabulary object.
* The new vocabulary object.
*/
private function createVocabulary($name) {
$vocabularyStorage = \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary');
$vocabulary = $vocabularyStorage->create([
'name' => $name,
'description' => $name,
'vid' => Unicode::strtolower($name),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$vocabulary->save();
return $vocabulary;
}
/**
* Creates and saves a new term with in vocabulary $vid.
*
* @param \Drupal\taxonomy\Entity\Vocabulary $vocabulary
* The vocabulary object.
* @param array $values
* (optional) An array of values to set, keyed by property name. If the
* entity type has bundles, the bundle key has to be specified.
*
* @return \Drupal\taxonomy\Entity\Term
* The new taxonomy term object.
*/
private function createTerm(Vocabulary $vocabulary, $values = array()) {
$filter_formats = filter_formats();
$format = array_pop($filter_formats);
$termStorage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$term = $termStorage->create($values + array(
'name' => $this->randomMachineName(),
'description' => array(
'value' => $this->randomMachineName(),
// Use the first available text format.
'format' => $format->id(),
),
'vid' => $vocabulary->id(),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$term->save();
return $term;
}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->adminUser);
$this->manager = $this->container->get('plugin.manager.linkit.matcher');
$testing_vocabulary_1 = $this->createVocabulary('testing_vocabulary_1');
$testing_vocabulary_2 = $this->createVocabulary('testing_vocabulary_2');
$this->createTerm($testing_vocabulary_1, ['name' => 'foo_bar']);
$this->createTerm($testing_vocabulary_1, ['name' => 'foo_baz']);
$this->createTerm($testing_vocabulary_1, ['name' => 'foo_foo']);
$this->createTerm($testing_vocabulary_1, ['name' => 'bar']);
$this->createTerm($testing_vocabulary_2, ['name' => 'foo_bar']);
$this->createTerm($testing_vocabulary_2, ['name' => 'foo_baz']);
}
/**
* Tests term matcher with default configuration.
*/
function testTermMatcherWidthDefaultConfiguration() {
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->manager->createInstance('entity:taxonomy_term', []);
$matches = $plugin->getMatches('foo');
$this->assertEqual(5, count($matches), 'Correct number of matches');
}
/**
* Tests term matcher with bundle filer.
*/
function testTermMatcherWidthBundleFiler() {
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->manager->createInstance('entity:taxonomy_term', [
'settings' => [
'bundles' => [
'testing_vocabulary_1' => 'testing_vocabulary_1'
],
],
]);
$matches = $plugin->getMatches('foo');
$this->assertEqual(3, count($matches), 'Correct number of matches');
}
}
@@ -0,0 +1,114 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Tests\Matchers\UserMatcherTest.
*/
namespace Drupal\linkit\Tests\Matchers;
use Drupal\linkit\Tests\LinkitTestBase;
/**
* Tests user matcher.
*
* @group linkit
*/
class UserMatcherTest extends LinkitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['user'];
/**
* The matcher manager.
*
* @var \Drupal\linkit\MatcherManager
*/
protected $manager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['access user profiles']));
$this->manager = $this->container->get('plugin.manager.linkit.matcher');
$custom_role = $this->drupalCreateRole(array(), 'custom_role', 'custom_role');
$custom_role_admin = $this->drupalCreateRole(array(), 'custom_role_admin', 'custom_role_admin');
$this->drupalCreateUser([], 'lorem');
$this->drupalCreateUser([], 'foo');
$account = $this->drupalCreateUser([], 'ipsumlorem');
$account->addRole($custom_role);
$account->save();
$account = $this->drupalCreateUser([], 'lorem_custom_role');
$account->addRole($custom_role);
$account->save();
$account = $this->drupalCreateUser([], 'lorem_custom_role_admin');
$account->addRole($custom_role_admin);
$account->save();
$account = $this->drupalCreateUser([], 'blocked_lorem');
$account->block();
$account->save();
}
/**
* Tests user matcher.
*/
function testUserMatcherWidthDefaultConfiguration() {
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->manager->createInstance('entity:user', []);
$matches = $plugin->getMatches('Lorem');
$this->assertEqual(4, count($matches), 'Correct number of matches');
}
/**
* Tests user matcher with role filer.
*/
function testUserMatcherWidthRoleFiler() {
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->manager->createInstance('entity:user', [
'settings' => [
'roles' => [
'custom_role' => 'custom_role'
],
],
]);
$matches = $plugin->getMatches('Lorem');
$this->assertEqual(2, count($matches), 'Correct number of matches');
}
/**
* Tests user matcher with include blocked setting activated.
*/
function testUserMatcherWidthIncludeBlocked() {
/** @var \Drupal\linkit\MatcherInterface $plugin */
$plugin = $this->manager->createInstance('entity:user', [
'settings' => [
'include_blocked' => TRUE,
],
]);
// Test without permissions to see blocked users.
$matches = $plugin->getMatches('blocked');
$this->assertEqual(0, count($matches), 'Correct number of matches');
$account = $this->drupalCreateUser(['administer users']);
$this->drupalLogin($account);
// Test with permissions to see blocked users.
$matches = $plugin->getMatches('blocked');
$this->assertEqual(1, count($matches), 'Correct number of matches');
}
}
@@ -0,0 +1,125 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Tests\ProfileCreationTest.
*/
namespace Drupal\linkit\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Url;
/**
* Tests creating, loading and deleting profiles.
*
* @group linkit
*/
class ProfileCrudTest extends LinkitTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->adminUser);
}
/**
* Test the overview page.
*/
function testOverview() {
// Verify that the profile collection page is not accessible for regular
// users.
$this->drupalLogin($this->baseUser);
$this->drupalGet(Url::fromRoute('entity.linkit_profile.collection'));
$this->assertResponse(403);
$this->drupalLogout();
// Verify that the profile collection page is accessible for regular users.
$this->drupalLogin($this->adminUser);
$profiles = [];
$profiles[] = $this->createProfile();
$profiles[] = $this->createProfile();
$this->drupalGet(Url::fromRoute('entity.linkit_profile.collection'));
$this->assertResponse(200);
// Assert that the 'Add profile' action exists.
$this->assertLinkByHref(Url::fromRoute('entity.linkit_profile.add_form')->toString());
/** @var \Drupal\linkit\ProfileInterface $profile */
foreach ($profiles as $profile) {
$this->assertLinkByHref('admin/config/content/linkit/manage/' . $profile->id());
$this->assertLinkByHref('admin/config/content/linkit/manage/' . $profile->id() . '/delete');
}
}
/**
* Creates profile.
*/
function testProfileCreation() {
$this->drupalGet(Url::fromRoute('entity.linkit_profile.add_form'));
$this->drupalGet('admin/config/content/linkit/add');
$this->assertResponse(200);
// Create a profile.
$edit = [];
$edit['label'] = Unicode::strtolower($this->randomMachineName());
$edit['id'] = Unicode::strtolower($this->randomMachineName());
$edit['description'] = $this->randomMachineName(16);
$this->drupalPostForm(NULL, $edit, t('Save and manage matchers'));
$this->assertRaw(t('Created new profile %label.', ['%label' => $edit['label']]));
$this->drupalGet(Url::fromRoute('entity.linkit_profile.collection'));
$this->assertText($edit['label'], 'Profile exists in the profile collection.');
}
/**
* Updates a profile.
*/
function testProfileUpdate() {
$profile = $this->createProfile();
$this->drupalGet(Url::fromRoute('entity.linkit_profile.edit_form', [
'linkit_profile' => $profile->id(),
]));
$this->assertResponse(200);
$id_field = $this->xpath('.//input[not(@disabled) and @name="id"]');
$this->assertTrue(empty($id_field), 'Machine name field is disabled.');
$this->assertLinkByHref(Url::fromRoute('entity.linkit_profile.edit_form', [
'linkit_profile' => $profile->id(),
])->toString());
$this->assertLinkByHref('admin/config/content/linkit/manage/' . $profile->id() . '/delete');
$edit = [];
$edit['label'] = $this->randomMachineName();
$edit['description'] = $this->randomMachineName(16);
$this->drupalPostForm(NULL, $edit, t('Update profile'));
$this->assertRaw(t('Updated profile %label.', ['%label' => $edit['label']]));
$this->drupalGet(Url::fromRoute('entity.linkit_profile.collection'));
$this->assertText($edit['label'], 'Updated profile exists in the profile collection.');
}
/**
* Delete a profile.
*/
function testProfileDelete() {
/** @var \Drupal\linkit\ProfileInterface $profile */
$profile = $this->createProfile();
$this->drupalGet(Url::fromRoute('entity.linkit_profile.delete_form', [
'linkit_profile' => $profile->id(),
]));
$this->drupalPostForm(NULL, [], t('Delete'));
$this->assertRaw(t('The linkit profile %label has been deleted.', ['%label' => $profile->label()]));
$this->drupalGet(Url::fromRoute('entity.linkit_profile.collection'));
$this->assertNoText($profile->label(), 'Deleted profile does not exists in the profile collection.');
}
}
@@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\linkit\Utility\LinkitXss.
*/
namespace Drupal\linkit\Utility;
use Drupal\Component\Utility\Xss;
/**
* Extends the default XSS protection to simplify it for Linkits needs.
*/
class LinkitXss extends Xss {
/**
* Description filter helper.
*
* @param $string
* The string with raw HTML in it. It will be stripped of everything that
* can cause an XSS attack.
*
* @return string
* An XSS safe version of $string, or an empty string if $string is not
* valid UTF-8.
*
* @see \Drupal\Component\Utility\Xss::filter()
*/
public static function descriptionFilter($string) {
return parent::filter($string, ['img'] + Xss::getHtmlTagList());
}
}
@@ -0,0 +1,16 @@
# Schema for the configuration files of the Linkit test module.
# Plugin \Drupal\linkit_test\Plugin\Linkit\Attribute\ConfigurableDummyAttribute
linkit.attribute.configurable_dummy_attribute:
type: linkit.attribute
mapping:
dummy_setting:
type: boolean
# Plugin \Drupal\linkit_test\Plugin\Linkit\Matcher\ConfigurableDummyMatcher
linkit.matcher.configurable_dummy_matcher:
type: linkit.matcher
mapping:
dummy_setting:
type: boolean
@@ -0,0 +1,16 @@
name: 'Linkit test module'
description: 'Support module for Linkit testing.'
package: Testing
type: module
# version: VERSION
# core: 8.x
dependencies:
- linkit
- field
- text
# Information added by Drupal.org packaging script on 2017-03-21
version: '8.x-4.3'
core: '8.x'
project: 'linkit'
datestamp: 1490127367
@@ -0,0 +1,16 @@
<?php
/**
* @file
* Support module for Linkit testing.
*/
/**
* Implements hook_linkit_attribute_alter().
*/
function linkit_test_linkit_attribute_alter(&$linkit_attribute_info) {
if (isset($linkit_attribute_info['dummyattribute'])) {
$linkit_attribute_info['dummyattribute']['description'] = t('Altered dummy description');
}
}
@@ -0,0 +1,73 @@
<?php
/**
* @file
* Contains \Drupal\linkit_test\Plugin\Linkit\Attribute\ConfigurableDummyAttribute.
*/
namespace Drupal\linkit_test\Plugin\Linkit\Attribute;
use Drupal\Core\Form\FormStateInterface;
use Drupal\linkit\ConfigurableAttributeBase;
/**
* Accesskey attribute.
*
* @Attribute(
* id = "configurable_dummy_attribute",
* label = @Translation("Configurable Dummy Attribute"),
* html_name = "configurabledummyattribute",
* description = @Translation("Configurable Dummy Attribute")
* )
*/
class ConfigurableDummyAttribute extends ConfigurableAttributeBase {
/**
* {@inheritdoc}
*/
public function buildFormElement($default_value) {
return [
'#type' => 'textfield',
'#title' => t('DummyAttribute'),
'#default_value' => $default_value,
'#maxlength' => 255,
'#size' => 40,
];
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'dummy_setting' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['dummy_setting'] = [
'#type' => 'checkbox',
'#title' => $this->t('Dummy setting'),
'#default_value' => $this->configuration['dummy_setting'],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['dummy_setting'] = $form_state->getValue('dummy_setting');
}
}
@@ -0,0 +1,37 @@
<?php
/**
* @file
* Contains \Drupal\linkit_test\Plugin\Linkit\Attribute\DummyAttribute.
*/
namespace Drupal\linkit_test\Plugin\Linkit\Attribute;
use Drupal\linkit\AttributeBase;
/**
* Accesskey attribute.
*
* @Attribute(
* id = "dummy_attribute",
* label = @Translation("Dummy Attribute"),
* html_name = "dummyattribute",
* description = @Translation("Dummy Attribute")
* )
*/
class DummyAttribute extends AttributeBase {
/**
* {@inheritdoc}
*/
public function buildFormElement($default_value) {
return [
'#type' => 'textfield',
'#title' => t('DummyAttribute'),
'#default_value' => $default_value,
'#maxlength' => 255,
'#size' => 40,
];
}
}
@@ -0,0 +1,83 @@
<?php
/**
* @file
* Contains \Drupal\linkit_test\Plugin\Linkit\Matcher\ConfigurableDummyMatcher.
*/
namespace Drupal\linkit_test\Plugin\Linkit\Matcher;
use Drupal\Core\Form\FormStateInterface;
use Drupal\linkit\ConfigurableMatcherBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @Matcher(
* id = "configurable_dummy_matcher",
* label = @Translation("Configurable Dummy Matcher"),
* )
*/
class ConfigurableDummyMatcher extends ConfigurableMatcherBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'dummy_setting' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['dummy_setting'] = [
'#type' => 'checkbox',
'#title' => $this->t('Dummy setting'),
'#default_value' => $this->configuration['dummy_setting'],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['dummy_setting'] = $form_state->getValue('dummy_setting');
}
/**
* {@inheritdoc}
*/
public function getMatches($string) {
$matches[] = [
'title' => 'Configurable Dummy Matcher title',
'description' => 'Configurable Dummy Matcher description',
'path' => 'http://example.com',
'group' => 'Configurable Dummy Matcher',
];
return $matches;
}
}
@@ -0,0 +1,47 @@
<?php
/**
* @file
* Contains \Drupal\linkit_test\Plugin\Linkit\Matcher\DummyMatcher.
*/
namespace Drupal\linkit_test\Plugin\Linkit\Matcher;
use Drupal\linkit\MatcherBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @Matcher(
* id = "dummy_matcher",
* label = @Translation("Dummy Matcher"),
* )
*/
class DummyMatcher extends MatcherBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition
);
}
/**
* {@inheritdoc}
*/
public function getMatches($string) {
$matches[] = [
'title' => 'DummyMatcher title',
'description' => 'DummyMatcher description',
'path' => 'http://example.com',
'group' => 'DummyMatcher',
];
return $matches;
}
}