Переглянути джерело

added linkit contrib module

Bachir Soussi Chiadmi 7 роки тому
батько
коміт
f7646c417a
87 змінених файлів з 7018 додано та 0 видалено
  1. 339 0
      sites/all/modules/contrib/fields/linkit/LICENSE.txt
  2. 33 0
      sites/all/modules/contrib/fields/linkit/README.md
  3. 19 0
      sites/all/modules/contrib/fields/linkit/composer.json
  4. 13 0
      sites/all/modules/contrib/fields/linkit/config/optional/image.style.linkit_result_thumbnail.yml
  5. 131 0
      sites/all/modules/contrib/fields/linkit/config/schema/linkit.schema.yml
  6. 15 0
      sites/all/modules/contrib/fields/linkit/css/linkit.admin.theme.css
  7. 69 0
      sites/all/modules/contrib/fields/linkit/css/linkit.autocomplete.css
  8. 21 0
      sites/all/modules/contrib/fields/linkit/js/attribute/title.js
  9. 167 0
      sites/all/modules/contrib/fields/linkit/js/autocomplete.js
  10. 52 0
      sites/all/modules/contrib/fields/linkit/js/linkit.imce.js
  11. 15 0
      sites/all/modules/contrib/fields/linkit/js/linkit.js
  12. BIN
      sites/all/modules/contrib/fields/linkit/js/plugins/linkit/linkit.png
  13. 223 0
      sites/all/modules/contrib/fields/linkit/js/plugins/linkit/plugin.js
  14. 12 0
      sites/all/modules/contrib/fields/linkit/linkit.info.yml
  15. 6 0
      sites/all/modules/contrib/fields/linkit/linkit.install
  16. 43 0
      sites/all/modules/contrib/fields/linkit/linkit.libraries.yml
  17. 18 0
      sites/all/modules/contrib/fields/linkit/linkit.links.action.yml
  18. 5 0
      sites/all/modules/contrib/fields/linkit/linkit.links.menu.yml
  19. 15 0
      sites/all/modules/contrib/fields/linkit/linkit.links.task.yml
  20. 109 0
      sites/all/modules/contrib/fields/linkit/linkit.module
  21. 2 0
      sites/all/modules/contrib/fields/linkit/linkit.permissions.yml
  22. 111 0
      sites/all/modules/contrib/fields/linkit/linkit.routing.yml
  23. 11 0
      sites/all/modules/contrib/fields/linkit/linkit.services.yml
  24. 69 0
      sites/all/modules/contrib/fields/linkit/src/Annotation/Attribute.php
  25. 49 0
      sites/all/modules/contrib/fields/linkit/src/Annotation/Matcher.php
  26. 112 0
      sites/all/modules/contrib/fields/linkit/src/AttributeBase.php
  27. 46 0
      sites/all/modules/contrib/fields/linkit/src/AttributeCollection.php
  28. 78 0
      sites/all/modules/contrib/fields/linkit/src/AttributeInterface.php
  29. 29 0
      sites/all/modules/contrib/fields/linkit/src/AttributeManager.php
  30. 16 0
      sites/all/modules/contrib/fields/linkit/src/ConfigurableAttributeBase.php
  31. 23 0
      sites/all/modules/contrib/fields/linkit/src/ConfigurableAttributeInterface.php
  32. 16 0
      sites/all/modules/contrib/fields/linkit/src/ConfigurableMatcherBase.php
  33. 24 0
      sites/all/modules/contrib/fields/linkit/src/ConfigurableMatcherInterface.php
  34. 89 0
      sites/all/modules/contrib/fields/linkit/src/Controller/AutocompleteController.php
  35. 65 0
      sites/all/modules/contrib/fields/linkit/src/Controller/LinkitController.php
  36. 108 0
      sites/all/modules/contrib/fields/linkit/src/Element/Linkit.php
  37. 252 0
      sites/all/modules/contrib/fields/linkit/src/Entity/Profile.php
  38. 166 0
      sites/all/modules/contrib/fields/linkit/src/Form/Attribute/AddForm.php
  39. 92 0
      sites/all/modules/contrib/fields/linkit/src/Form/Attribute/DeleteForm.php
  40. 96 0
      sites/all/modules/contrib/fields/linkit/src/Form/Attribute/EditForm.php
  41. 156 0
      sites/all/modules/contrib/fields/linkit/src/Form/Attribute/OverviewForm.php
  42. 207 0
      sites/all/modules/contrib/fields/linkit/src/Form/LinkitEditorDialog.php
  43. 165 0
      sites/all/modules/contrib/fields/linkit/src/Form/Matcher/AddForm.php
  44. 90 0
      sites/all/modules/contrib/fields/linkit/src/Form/Matcher/DeleteForm.php
  45. 92 0
      sites/all/modules/contrib/fields/linkit/src/Form/Matcher/EditForm.php
  46. 165 0
      sites/all/modules/contrib/fields/linkit/src/Form/Matcher/OverviewForm.php
  47. 28 0
      sites/all/modules/contrib/fields/linkit/src/Form/Profile/AddForm.php
  48. 29 0
      sites/all/modules/contrib/fields/linkit/src/Form/Profile/EditForm.php
  49. 90 0
      sites/all/modules/contrib/fields/linkit/src/Form/Profile/FormBase.php
  50. 119 0
      sites/all/modules/contrib/fields/linkit/src/MatcherBase.php
  51. 46 0
      sites/all/modules/contrib/fields/linkit/src/MatcherCollection.php
  52. 84 0
      sites/all/modules/contrib/fields/linkit/src/MatcherInterface.php
  53. 29 0
      sites/all/modules/contrib/fields/linkit/src/MatcherManager.php
  54. 71 0
      sites/all/modules/contrib/fields/linkit/src/MatcherTokensTrait.php
  55. 123 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/CKEditorPlugin/Linkit.php
  56. 67 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Derivative/EntityMatcherDeriver.php
  57. 38 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Accesskey.php
  58. 41 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Clazz.php
  59. 38 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Id.php
  60. 38 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Relationship.php
  61. 96 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Target.php
  62. 82 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Title.php
  63. 348 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/EntityMatcher.php
  64. 189 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/FileMatcher.php
  65. 92 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/NodeMatcher.php
  66. 49 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/TermMatcher.php
  67. 117 0
      sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/UserMatcher.php
  68. 139 0
      sites/all/modules/contrib/fields/linkit/src/ProfileInterface.php
  69. 73 0
      sites/all/modules/contrib/fields/linkit/src/ProfileListBuilder.php
  70. 74 0
      sites/all/modules/contrib/fields/linkit/src/ResultManager.php
  71. 160 0
      sites/all/modules/contrib/fields/linkit/src/Tests/AttributeCrudTest.php
  72. 83 0
      sites/all/modules/contrib/fields/linkit/src/Tests/Controllers/LinkitControllerTest.php
  73. 83 0
      sites/all/modules/contrib/fields/linkit/src/Tests/LinkitTestBase.php
  74. 158 0
      sites/all/modules/contrib/fields/linkit/src/Tests/MatcherCrudTest.php
  75. 105 0
      sites/all/modules/contrib/fields/linkit/src/Tests/Matchers/NodeMatcherTest.php
  76. 134 0
      sites/all/modules/contrib/fields/linkit/src/Tests/Matchers/TermMatcherTest.php
  77. 114 0
      sites/all/modules/contrib/fields/linkit/src/Tests/Matchers/UserMatcherTest.php
  78. 125 0
      sites/all/modules/contrib/fields/linkit/src/Tests/ProfileCrudTest.php
  79. 34 0
      sites/all/modules/contrib/fields/linkit/src/Utility/LinkitXss.php
  80. 16 0
      sites/all/modules/contrib/fields/linkit/tests/linkit_test/config/schema/linkit_test.schema.yml
  81. 16 0
      sites/all/modules/contrib/fields/linkit/tests/linkit_test/linkit_test.info.yml
  82. 16 0
      sites/all/modules/contrib/fields/linkit/tests/linkit_test/linkit_test.module
  83. 0 0
      sites/all/modules/contrib/fields/linkit/tests/linkit_test/linkit_test.routing.yml
  84. 73 0
      sites/all/modules/contrib/fields/linkit/tests/linkit_test/src/Plugin/Linkit/Attribute/ConfigurableDummyAttribute.php
  85. 37 0
      sites/all/modules/contrib/fields/linkit/tests/linkit_test/src/Plugin/Linkit/Attribute/DummyAttribute.php
  86. 83 0
      sites/all/modules/contrib/fields/linkit/tests/linkit_test/src/Plugin/Linkit/Matcher/ConfigurableDummyMatcher.php
  87. 47 0
      sites/all/modules/contrib/fields/linkit/tests/linkit_test/src/Plugin/Linkit/Matcher/DummyMatcher.php

+ 339 - 0
sites/all/modules/contrib/fields/linkit/LICENSE.txt

@@ -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.

+ 33 - 0
sites/all/modules/contrib/fields/linkit/README.md

@@ -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.

+ 19 - 0
sites/all/modules/contrib/fields/linkit/composer.json

@@ -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+"
+}

+ 13 - 0
sites/all/modules/contrib/fields/linkit/config/optional/image.style.linkit_result_thumbnail.yml

@@ -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

+ 131 - 0
sites/all/modules/contrib/fields/linkit/config/schema/linkit.schema.yml

@@ -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'

+ 15 - 0
sites/all/modules/contrib/fields/linkit/css/linkit.admin.theme.css

@@ -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%;
+}

+ 69 - 0
sites/all/modules/contrib/fields/linkit/css/linkit.autocomplete.css

@@ -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;
+}

+ 21 - 0
sites/all/modules/contrib/fields/linkit/js/attribute/title.js

@@ -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);

+ 167 - 0
sites/all/modules/contrib/fields/linkit/js/autocomplete.js

@@ -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);

+ 52 - 0
sites/all/modules/contrib/fields/linkit/js/linkit.imce.js

@@ -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);
+
+

+ 15 - 0
sites/all/modules/contrib/fields/linkit/js/linkit.js

@@ -0,0 +1,15 @@
+/**
+ * @file
+ * Some basic behaviors and utility functions for Linkit.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  /**
+   * @namespace
+   */
+  Drupal.linkit = {};
+
+})(jQuery, Drupal, drupalSettings);

BIN
sites/all/modules/contrib/fields/linkit/js/plugins/linkit/linkit.png


+ 223 - 0
sites/all/modules/contrib/fields/linkit/js/plugins/linkit/plugin.js

@@ -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);

+ 12 - 0
sites/all/modules/contrib/fields/linkit/linkit.info.yml

@@ -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

+ 6 - 0
sites/all/modules/contrib/fields/linkit/linkit.install

@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * Installation functions for Linkit module.
+ */

+ 43 - 0
sites/all/modules/contrib/fields/linkit/linkit.libraries.yml

@@ -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

+ 18 - 0
sites/all/modules/contrib/fields/linkit/linkit.links.action.yml

@@ -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
+

+ 5 - 0
sites/all/modules/contrib/fields/linkit/linkit.links.menu.yml

@@ -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

+ 15 - 0
sites/all/modules/contrib/fields/linkit/linkit.links.task.yml

@@ -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

+ 109 - 0
sites/all/modules/contrib/fields/linkit/linkit.module

@@ -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,
+    ];
+  }
+}

+ 2 - 0
sites/all/modules/contrib/fields/linkit/linkit.permissions.yml

@@ -0,0 +1,2 @@
+administer linkit profiles:
+  title: 'Administer linkit profiles'

+ 111 - 0
sites/all/modules/contrib/fields/linkit/linkit.routing.yml

@@ -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

+ 11 - 0
sites/all/modules/contrib/fields/linkit/linkit.services.yml

@@ -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

+ 69 - 0
sites/all/modules/contrib/fields/linkit/src/Annotation/Attribute.php

@@ -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;
+
+}

+ 49 - 0
sites/all/modules/contrib/fields/linkit/src/Annotation/Matcher.php

@@ -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;
+
+}

+ 112 - 0
sites/all/modules/contrib/fields/linkit/src/AttributeBase.php

@@ -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;
+  }
+
+}

+ 46 - 0
sites/all/modules/contrib/fields/linkit/src/AttributeCollection.php

@@ -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;
+  }
+
+}

+ 78 - 0
sites/all/modules/contrib/fields/linkit/src/AttributeInterface.php

@@ -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);
+
+}

+ 29 - 0
sites/all/modules/contrib/fields/linkit/src/AttributeManager.php

@@ -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');
+  }
+
+}

+ 16 - 0
sites/all/modules/contrib/fields/linkit/src/ConfigurableAttributeBase.php

@@ -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 {
+}

+ 23 - 0
sites/all/modules/contrib/fields/linkit/src/ConfigurableAttributeInterface.php

@@ -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 {
+}

+ 16 - 0
sites/all/modules/contrib/fields/linkit/src/ConfigurableMatcherBase.php

@@ -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 {
+}

+ 24 - 0
sites/all/modules/contrib/fields/linkit/src/ConfigurableMatcherInterface.php

@@ -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 {
+}

+ 89 - 0
sites/all/modules/contrib/fields/linkit/src/Controller/AutocompleteController.php

@@ -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);
+  }
+
+}

+ 65 - 0
sites/all/modules/contrib/fields/linkit/src/Controller/LinkitController.php

@@ -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()));
+  }
+
+}

+ 108 - 0
sites/all/modules/contrib/fields/linkit/src/Element/Linkit.php

@@ -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;
+  }
+
+}

+ 252 - 0
sites/all/modules/contrib/fields/linkit/src/Entity/Profile.php

@@ -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');
+  }
+
+}

+ 166 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Attribute/AddForm.php

@@ -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;
+  }
+
+}

+ 92 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Attribute/DeleteForm.php

@@ -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(),
+    ]);
+  }
+
+}

+ 96 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Attribute/EditForm.php

@@ -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(),
+    ]);
+  }
+
+}

+ 156 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Attribute/OverviewForm.php

@@ -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();
+  }
+
+}

+ 207 - 0
sites/all/modules/contrib/fields/linkit/src/Form/LinkitEditorDialog.php

@@ -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;
+  }
+
+}

+ 165 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Matcher/AddForm.php

@@ -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;
+  }
+
+}

+ 90 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Matcher/DeleteForm.php

@@ -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(),
+    ]);
+
+  }
+
+}

+ 92 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Matcher/EditForm.php

@@ -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(),
+    ]);
+  }
+}

+ 165 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Matcher/OverviewForm.php

@@ -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();
+  }
+
+}

+ 28 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Profile/AddForm.php

@@ -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;
+  }
+
+}

+ 29 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Profile/EditForm.php

@@ -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;
+  }
+
+}

+ 90 - 0
sites/all/modules/contrib/fields/linkit/src/Form/Profile/FormBase.php

@@ -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;
+    }
+  }
+
+}

+ 119 - 0
sites/all/modules/contrib/fields/linkit/src/MatcherBase.php

@@ -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 [];
+  }
+
+}

+ 46 - 0
sites/all/modules/contrib/fields/linkit/src/MatcherCollection.php

@@ -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;
+  }
+
+}

+ 84 - 0
sites/all/modules/contrib/fields/linkit/src/MatcherInterface.php

@@ -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);
+
+}

+ 29 - 0
sites/all/modules/contrib/fields/linkit/src/MatcherManager.php

@@ -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');
+  }
+
+}

+ 71 - 0
sites/all/modules/contrib/fields/linkit/src/MatcherTokensTrait.php

@@ -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;
+  }
+
+}

+ 123 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/CKEditorPlugin/Linkit.php

@@ -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.'));
+    }
+  }
+
+}

+ 67 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Derivative/EntityMatcherDeriver.php

@@ -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);
+  }
+
+}

+ 38 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Accesskey.php

@@ -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'),
+    ];
+  }
+
+}

+ 41 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Clazz.php

@@ -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,
+//    ];
+//  }
+//
+//}

+ 38 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Id.php

@@ -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'),
+    ];
+  }
+
+}

+ 38 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Relationship.php

@@ -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'),
+    ];
+  }
+
+}

+ 96 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Target.php

@@ -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');
+  }
+
+}

+ 82 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Attribute/Title.php

@@ -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');
+  }
+
+}

+ 348 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/EntityMatcher.php

@@ -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;
+  }
+
+}

+ 189 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/FileMatcher.php

@@ -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()));
+  }
+}

+ 92 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/NodeMatcher.php

@@ -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;
+  }
+
+}

+ 49 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/TermMatcher.php

@@ -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);
+  }
+
+}

+ 117 - 0
sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/UserMatcher.php

@@ -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;
+  }
+
+}

+ 139 - 0
sites/all/modules/contrib/fields/linkit/src/ProfileInterface.php

@@ -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);
+
+}

+ 73 - 0
sites/all/modules/contrib/fields/linkit/src/ProfileListBuilder.php

@@ -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;
+  }
+
+}

+ 74 - 0
sites/all/modules/contrib/fields/linkit/src/ResultManager.php

@@ -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;
+  }
+
+}

+ 160 - 0
sites/all/modules/contrib/fields/linkit/src/Tests/AttributeCrudTest.php

@@ -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');
+  }
+
+}

+ 83 - 0
sites/all/modules/contrib/fields/linkit/src/Tests/Controllers/LinkitControllerTest.php

@@ -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');
+  }
+
+}

+ 83 - 0
sites/all/modules/contrib/fields/linkit/src/Tests/LinkitTestBase.php

@@ -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;
+  }
+
+}

+ 158 - 0
sites/all/modules/contrib/fields/linkit/src/Tests/MatcherCrudTest.php

@@ -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');
+  }
+
+}

+ 105 - 0
sites/all/modules/contrib/fields/linkit/src/Tests/Matchers/NodeMatcherTest.php

@@ -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');
+  }
+
+}

+ 134 - 0
sites/all/modules/contrib/fields/linkit/src/Tests/Matchers/TermMatcherTest.php

@@ -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');
+  }
+
+}

+ 114 - 0
sites/all/modules/contrib/fields/linkit/src/Tests/Matchers/UserMatcherTest.php

@@ -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');
+  }
+
+}

+ 125 - 0
sites/all/modules/contrib/fields/linkit/src/Tests/ProfileCrudTest.php

@@ -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.');
+  }
+
+}

+ 34 - 0
sites/all/modules/contrib/fields/linkit/src/Utility/LinkitXss.php

@@ -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());
+  }
+
+}

+ 16 - 0
sites/all/modules/contrib/fields/linkit/tests/linkit_test/config/schema/linkit_test.schema.yml

@@ -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

+ 16 - 0
sites/all/modules/contrib/fields/linkit/tests/linkit_test/linkit_test.info.yml

@@ -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

+ 16 - 0
sites/all/modules/contrib/fields/linkit/tests/linkit_test/linkit_test.module

@@ -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
sites/all/modules/contrib/fields/linkit/tests/linkit_test/linkit_test.routing.yml


+ 73 - 0
sites/all/modules/contrib/fields/linkit/tests/linkit_test/src/Plugin/Linkit/Attribute/ConfigurableDummyAttribute.php

@@ -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');
+  }
+
+}

+ 37 - 0
sites/all/modules/contrib/fields/linkit/tests/linkit_test/src/Plugin/Linkit/Attribute/DummyAttribute.php

@@ -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,
+    ];
+  }
+
+}

+ 83 - 0
sites/all/modules/contrib/fields/linkit/tests/linkit_test/src/Plugin/Linkit/Matcher/ConfigurableDummyMatcher.php

@@ -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;
+  }
+
+}

+ 47 - 0
sites/all/modules/contrib/fields/linkit/tests/linkit_test/src/Plugin/Linkit/Matcher/DummyMatcher.php

@@ -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;
+  }
+
+}