Added the Search API Synonym module to deal specifically with licence and license...
authorJeff Veit <jeff.veit@gmail.com>
Mon, 19 Nov 2018 22:05:11 +0000 (22:05 +0000)
committerJeff Veit <jeff.veit@gmail.com>
Mon, 19 Nov 2018 22:05:11 +0000 (22:05 +0000)
43 files changed:
web/modules/contrib/search_api_synonym/LICENSE.txt [new file with mode: 0644]
web/modules/contrib/search_api_synonym/README.txt [new file with mode: 0644]
web/modules/contrib/search_api_synonym/composer.json [new file with mode: 0644]
web/modules/contrib/search_api_synonym/config/install/search_api_synonym.settings.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/config/install/views.view.search_api_synonym.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/config/schema/search_api_synonym.schema.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/console/translations/en/searchapi.synonym.export.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/examples/example.csv [new file with mode: 0644]
web/modules/contrib/search_api_synonym/examples/example.json [new file with mode: 0644]
web/modules/contrib/search_api_synonym/examples/solr_synonyms.txt [new file with mode: 0644]
web/modules/contrib/search_api_synonym/search_api_synonym.drush.inc [new file with mode: 0644]
web/modules/contrib/search_api_synonym/search_api_synonym.info.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/search_api_synonym.install [new file with mode: 0644]
web/modules/contrib/search_api_synonym/search_api_synonym.links.action.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/search_api_synonym.links.menu.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/search_api_synonym.links.task.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/search_api_synonym.module [new file with mode: 0644]
web/modules/contrib/search_api_synonym/search_api_synonym.permissions.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/search_api_synonym.routing.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/search_api_synonym.services.yml [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Annotation/SearchApiSynonymExport.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Annotation/SearchApiSynonymImport.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Command/ExportCommand.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Entity/Synonym.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Export/ExportPluginBase.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Export/ExportPluginInterface.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Export/ExportPluginManager.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Form/SynonymDeleteForm.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Form/SynonymForm.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Form/SynonymImportForm.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Form/SynonymSettingsForm.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Import/ImportException.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Import/ImportPluginBase.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Import/ImportPluginInterface.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Import/ImportPluginManager.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Import/Importer.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/export/Solr.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/import/CSV.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/import/JSON.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/import/Solr.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/SynonymInterface.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/SynonymListBuilder.php [new file with mode: 0644]
web/modules/contrib/search_api_synonym/src/SynonymViewsData.php [new file with mode: 0644]

diff --git a/web/modules/contrib/search_api_synonym/LICENSE.txt b/web/modules/contrib/search_api_synonym/LICENSE.txt
new file mode 100644 (file)
index 0000000..d159169
--- /dev/null
@@ -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.
diff --git a/web/modules/contrib/search_api_synonym/README.txt b/web/modules/contrib/search_api_synonym/README.txt
new file mode 100644 (file)
index 0000000..ff7c7f4
--- /dev/null
@@ -0,0 +1,89 @@
+CONTENTS OF THIS FILE
+---------------------
+ * Introduction
+ * Requirements
+ * Installation
+ * Manage synonyms
+ * Export synonyms
+ * Developers
+ * Troubleshooting
+ * Sponsors
+ * Maintainers
+
+INTRODUCTION
+------------
+This module let editors or administrators manage synonyms for Search API
+directly in Drupal.
+
+Synonyms can be export using the build in Drupal Console command.
+Drush command and automatic export using Drupal cron job is in development.
+
+The module support the synonyms.txt format used in Apache Solr.
+Other formats can be added using the Export plugin annotation.
+
+REQUIREMENTS
+------------
+* No requirements.
+
+INSTALLATION
+------------
+ * Install as you would normally install a contributed drupal module. See:
+  https://www.drupal.org/documentation/install/modules-themes/modules-8
+  for further information.
+
+MANAGE SYNONYMS
+---------------
+After installation can you start managing your synonyms and spelling errors
+at admin/config/search/search-api-synonyms.
+
+EXPORT SYNONYMS
+---------------
+
+Drupal Console
+--------------
+
+Export the added synonyms using the Drupal Console command:
+
+- drupal searchapi:synonym:export
+
+Execute the command with --help to see the different options.
+
+Drush
+-----
+
+Export synonyms using the Drush command:
+
+- drush search-api-synonym-export
+
+- drush sapi-syn-ex
+
+Execute the command with --help to see the different options.
+
+Cron
+----
+
+Export using Drupal cron is supported. See the settings in /admin/config/search/search-api-synonyms/settings.
+
+DEVELOPERS
+----------
+
+The Search API Synonym module provides the following ways for developers to
+extend the functionality:
+
+- Plugins
+  Export plugin - see the annotation and the Solr plugin:
+  - Drupal\search_api_synonym\Annotation\SearchApiSynonymExport
+  - Drupal\search_api_synonym\Plugin\search_api_synonym\export\Solr
+
+TROUBLESHOOTING
+---------------
+-
+
+SPONSORS
+--------
+ * FFW - https://ffwagency.com
+
+MAINTAINERS
+-----------
+Current maintainers:
+ * Jens Beltofte (beltofte) - https://drupal.org/u/beltofte
diff --git a/web/modules/contrib/search_api_synonym/composer.json b/web/modules/contrib/search_api_synonym/composer.json
new file mode 100644 (file)
index 0000000..af1c05b
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "name": "drupal/search_api_synonym",
+  "type": "drupal-module",
+  "description": "Managing of search synonyms in Drupal.",
+  "keywords": ["Drupal"],
+  "license": "GPL-2.0+",
+  "homepage": "https://www.drupal.org/project/search_api_synonym",
+  "minimum-stability": "dev",
+  "support": {
+    "issues": "http://drupal.org/project/issues/search_api_synonym",
+    "source": "http://cgit.drupalcode.org/search_api_synonym"
+  },
+  "require": { }
+}
diff --git a/web/modules/contrib/search_api_synonym/config/install/search_api_synonym.settings.yml b/web/modules/contrib/search_api_synonym/config/install/search_api_synonym.settings.yml
new file mode 100644 (file)
index 0000000..a704d6e
--- /dev/null
@@ -0,0 +1,7 @@
+cron:
+  plugin: solr
+  interval: 86400
+  type: all
+  filter: none
+  separate_files: 1
+  export_if_changed: 1
diff --git a/web/modules/contrib/search_api_synonym/config/install/views.view.search_api_synonym.yml b/web/modules/contrib/search_api_synonym/config/install/views.view.search_api_synonym.yml
new file mode 100644 (file)
index 0000000..a4b4de7
--- /dev/null
@@ -0,0 +1,917 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - options
+    - search_api_synonym
+    - user
+id: search_api_synonym
+label: 'Search API Synonym'
+module: views
+description: 'Editorial views used by Search API Synonym.'
+tag: ''
+base_table: search_api_synonym
+base_field: sid
+core: 8.x
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: 0
+    display_options:
+      access:
+        type: perm
+        options:
+          perm: 'administer search api synonyms'
+      cache:
+        type: tag
+        options: {  }
+      query:
+        type: views_query
+        options:
+          disable_sql_rewrite: false
+          distinct: false
+          replica: false
+          query_comment: ''
+          query_tags: {  }
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Apply
+          reset_button: true
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: true
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: mini
+        options:
+          items_per_page: 50
+          offset: 0
+          id: 0
+          total_pages: null
+          expose:
+            items_per_page: false
+            items_per_page_label: 'Items per page'
+            items_per_page_options: '5, 10, 25, 50'
+            items_per_page_options_all: false
+            items_per_page_options_all_label: '- All -'
+            offset: false
+            offset_label: Offset
+          tags:
+            previous: â€¹â€¹
+            next: â€ºâ€º
+      style:
+        type: table
+        options:
+          grouping: {  }
+          row_class: ''
+          default_row_class: true
+          override: true
+          sticky: false
+          caption: ''
+          summary: ''
+          description: ''
+          columns:
+            word: word
+            synonyms: synonyms
+            type: type
+            langcode: langcode
+            name: name
+            status: status
+            changed: changed
+            operations: operations
+          info:
+            word:
+              sortable: true
+              default_sort_order: asc
+              align: ''
+              separator: ''
+              empty_column: false
+              responsive: ''
+            synonyms:
+              sortable: true
+              default_sort_order: asc
+              align: ''
+              separator: ''
+              empty_column: false
+              responsive: priority-medium
+            type:
+              sortable: true
+              default_sort_order: asc
+              align: ''
+              separator: ''
+              empty_column: false
+              responsive: priority-medium
+            langcode:
+              sortable: true
+              default_sort_order: asc
+              align: ''
+              separator: ''
+              empty_column: false
+              responsive: priority-low
+            name:
+              sortable: true
+              default_sort_order: asc
+              align: ''
+              separator: ''
+              empty_column: false
+              responsive: priority-low
+            status:
+              sortable: true
+              default_sort_order: asc
+              align: ''
+              separator: ''
+              empty_column: false
+              responsive: priority-low
+            changed:
+              sortable: true
+              default_sort_order: asc
+              align: ''
+              separator: ''
+              empty_column: false
+              responsive: priority-low
+            operations:
+              sortable: false
+              default_sort_order: asc
+              align: ''
+              separator: ''
+              empty_column: false
+              responsive: priority-medium
+          default: word
+          empty_table: false
+      row:
+        type: fields
+      fields:
+        word:
+          id: word
+          table: search_api_synonym
+          field: word
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Word
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: string
+          settings:
+            link_to_entity: true
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: null
+          entity_field: word
+          plugin_id: field
+        synonyms:
+          id: synonyms
+          table: search_api_synonym
+          field: synonyms
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Synonyms
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 125
+            word_boundary: false
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: true
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: string
+          settings:
+            link_to_entity: true
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: search_api_synonym
+          entity_field: synonyms
+          plugin_id: field
+        type:
+          id: type
+          table: search_api_synonym
+          field: type
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Type
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: list_default
+          settings: {  }
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: search_api_synonym
+          entity_field: type
+          plugin_id: field
+        langcode:
+          id: langcode
+          table: search_api_synonym
+          field: langcode
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Language
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: language
+          settings:
+            link_to_entity: false
+            native_language: false
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: search_api_synonym
+          entity_field: langcode
+          plugin_id: field
+        name:
+          id: name
+          table: users_field_data
+          field: name
+          relationship: uid
+          group_type: group
+          admin_label: ''
+          label: Author
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: user_name
+          settings:
+            link_to_entity: true
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: user
+          entity_field: name
+          plugin_id: field
+        status:
+          id: status
+          table: search_api_synonym
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Status
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: boolean
+          settings:
+            format: custom
+            format_custom_true: active
+            format_custom_false: inactive
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: search_api_synonym
+          entity_field: status
+          plugin_id: field
+        changed:
+          id: changed
+          table: search_api_synonym
+          field: changed
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Updated
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: timestamp
+          settings:
+            date_format: custom
+            custom_date_format: 'd/m/Y H:i'
+            timezone: ''
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: search_api_synonym
+          entity_field: changed
+          plugin_id: field
+        operations:
+          id: operations
+          table: search_api_synonym
+          field: operations
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Operations
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          destination: true
+          entity_type: search_api_synonym
+          plugin_id: entity_operations
+      filters:
+        word:
+          id: word
+          table: search_api_synonym
+          field: word
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: word_op
+            label: Word
+            description: ''
+            use_operator: false
+            operator: word_op
+            identifier: word
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: search_api_synonym
+          entity_field: word
+          plugin_id: string
+        synonyms:
+          id: synonyms
+          table: search_api_synonym
+          field: synonyms
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: synonyms_op
+            label: Synonyms
+            description: ''
+            use_operator: false
+            operator: synonyms_op
+            identifier: synonyms
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: search_api_synonym
+          entity_field: synonyms
+          plugin_id: string
+        type:
+          id: type
+          table: search_api_synonym
+          field: type
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: type_op
+            label: Type
+            description: ''
+            use_operator: false
+            operator: type_op
+            identifier: type
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: true
+          group_info:
+            label: Type
+            description: ''
+            identifier: type
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items:
+              1:
+                title: Synonym
+                operator: '='
+                value: synonym
+              2:
+                title: 'Spelling error'
+                operator: '='
+                value: spelling_error
+          entity_type: search_api_synonym
+          entity_field: type
+          plugin_id: string
+        status:
+          id: status
+          table: search_api_synonym
+          field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: All
+          group: 1
+          exposed: true
+          expose:
+            operator_id: ''
+            label: Status
+            description: ''
+            use_operator: false
+            operator: status_op
+            identifier: status
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: search_api_synonym
+          entity_field: status
+          plugin_id: boolean
+        langcode:
+          id: langcode
+          table: search_api_synonym
+          field: langcode
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: in
+          value: {  }
+          group: 1
+          exposed: true
+          expose:
+            operator_id: langcode_op
+            label: Language
+            description: ''
+            use_operator: false
+            operator: langcode_op
+            identifier: langcode
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+              anonymous: '0'
+              administrator: '0'
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          entity_type: search_api_synonym
+          entity_field: langcode
+          plugin_id: language
+      sorts: {  }
+      title: 'Search API Synonym'
+      header: {  }
+      footer: {  }
+      empty: {  }
+      relationships:
+        uid:
+          id: uid
+          table: search_api_synonym
+          field: uid
+          relationship: none
+          group_type: group
+          admin_label: User
+          required: true
+          entity_type: search_api_synonym
+          entity_field: uid
+          plugin_id: standard
+      arguments: {  }
+      display_extenders: {  }
+      filter_groups:
+        operator: AND
+        groups:
+          1: AND
+      use_ajax: true
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - user.permissions
+      tags: {  }
+  list:
+    display_plugin: page
+    id: list
+    display_title: Page
+    position: 1
+    display_options:
+      display_extenders: {  }
+      path: admin/config/search/search-api-synonyms
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url
+        - url.query_args
+        - user.permissions
+      tags: {  }
diff --git a/web/modules/contrib/search_api_synonym/config/schema/search_api_synonym.schema.yml b/web/modules/contrib/search_api_synonym/config/schema/search_api_synonym.schema.yml
new file mode 100644 (file)
index 0000000..8ccb126
--- /dev/null
@@ -0,0 +1,28 @@
+# Schema for the configuration files of the Search API Synonym module.
+
+search_api_synonym.settings:
+  type: config_object
+  label: 'Search API Synonym settings'
+  mapping:
+    cron:
+      type: mapping
+      label: 'Cron'
+      mapping:
+        plugin:
+          type: string
+          label: 'Plugin'
+        interval:
+          type: integer
+          label: 'Interval'
+        type:
+          type: string
+          label: 'Type'
+        filter:
+          type: string
+          label: 'Filter'
+        separate_files:
+          type: bool
+          label: 'Separate files'
+        export_if_changed:
+          type: bool
+          label: 'Export if new added or changed since last export.'
diff --git a/web/modules/contrib/search_api_synonym/console/translations/en/searchapi.synonym.export.yml b/web/modules/contrib/search_api_synonym/console/translations/en/searchapi.synonym.export.yml
new file mode 100644 (file)
index 0000000..ad1369d
--- /dev/null
@@ -0,0 +1,23 @@
+description: 'Export search synonyms to a specific format.'
+options:
+  plugin:
+    description: 'Machine name of the export plugin. E.g. solr.'
+  langcode:
+    description: 'Language being exported. Use the language code. E.g. en or da.'
+  type:
+    description: 'Synonym type. Allowed values: synonym = Synomyms, spelling_error = Spelling errors, all = All types (synonyms and spelling errors). If option not defined will all types be exported.'
+  filter:
+    description: 'Export filter. Allowed values: nospace = Skip all words containing a space, onlyspace = Skip all words without a space.'
+  incremental:
+    description: 'Incremental export - use Unix timestamp. Only export synonyms changed after the provided timestamp.'
+  file:
+    description: 'File name used when saving the exported file. Include extension and not folder name!'
+arguments: {}
+messages:
+  start: 'Starting synonym export....'
+  success: 'Synonyms export and saved in the following file:'
+  invalidplugin: '--plugin is not valid. Please, use an existing plugin machine name.'
+  invalidlangcode: '--langcode is not valid. Please, use an existing language code.'
+  invalidtype: '--type option is not valid. The only allowed values are "synonym", "spelling_error", "all".'
+  invalidfilter: '--filter option is not valid. The only allowed values are "nospace", "onlyspace", "all".'
+
diff --git a/web/modules/contrib/search_api_synonym/examples/example.csv b/web/modules/contrib/search_api_synonym/examples/example.csv
new file mode 100644 (file)
index 0000000..891bd29
--- /dev/null
@@ -0,0 +1,4 @@
+"word";"synonym";"type"
+"cms";"content management system";"synonym"
+"dropal";"drupal";"spelling_error"
+"cq5,sitecore,sharepoint";"drupal";"spelling_error"
diff --git a/web/modules/contrib/search_api_synonym/examples/example.json b/web/modules/contrib/search_api_synonym/examples/example.json
new file mode 100644 (file)
index 0000000..9b1cae0
--- /dev/null
@@ -0,0 +1,17 @@
+[
+  {
+    "word": "cms",
+    "synonym": "content management system",
+    "type": "synonym"
+  },
+  {
+    "word": "dropal",
+    "synonym": "drupal",
+    "type": "spelling_error"
+  },
+  {
+    "word": "cq5,sitecore,sharepoint",
+    "synonym": "drupal",
+    "type": "spelling_error"
+  }
+]
\ No newline at end of file
diff --git a/web/modules/contrib/search_api_synonym/examples/solr_synonyms.txt b/web/modules/contrib/search_api_synonym/examples/solr_synonyms.txt
new file mode 100644 (file)
index 0000000..cede97b
--- /dev/null
@@ -0,0 +1,10 @@
+#
+# Apache Solr - Synonyms.txt example file.
+#
+
+# Synonym
+cms, content management system
+
+# Spelling errors
+dropal => drupal
+cq5, sitecore, sharepoint => drupal
diff --git a/web/modules/contrib/search_api_synonym/search_api_synonym.drush.inc b/web/modules/contrib/search_api_synonym/search_api_synonym.drush.inc
new file mode 100644 (file)
index 0000000..2fb3e8f
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Drush commands for Search API Synonym.
+ */
+
+/**
+ * Implements hook_drush_command().
+ */
+function search_api_synonym_drush_command() {
+  $items = [];
+
+  $items['search-api-synonym-export'] = [
+    'description' => 'Export search synonyms to a specific format.',
+    'examples' => [
+      'drush search-api-synonym-export --plugin=solr langcode=da type=spelling_error filter=all' => dt('Export all Danish spelling errors in the Solr format.'),
+      'drush sapi-syn-ex --plugin=solr langcode=da type=spelling_error filter=all' => dt('Export all Danish spelling errors in the Solr format.'),
+    ],
+    'options' => [
+      'plugin' => dt('Machine name of the export plugin. E.g. solr.'),
+      'langcode' => dt('Language being exported. Use the language code. E.g. en or da.'),
+      'type' => dt('Synonym type. Allowed values: synonym = Synomyms, spelling_error = Spelling errors, all = All types (synonyms and spelling errors). Defaults to "alL".'),
+      'filter' => dt('Export filter. Allowed values: nospace = Skip all words containing a space, onlyspace = Skip all words without a space. Defaults to "all".'),
+      'incremental' => dt('Incremental export - use Unix timestamp. Only export synonyms changed after the provided timestamp.'),
+      'file' => dt('File name used when saving the exported file. Include extension but not folder name!.'),
+    ],
+    'aliases' => ['sapi-syn-ex'],
+  ];
+
+  return $items;
+}
+
+/**
+ * Export synonyms to a flat file.
+ */
+function drush_search_api_synonym_export() {
+  // Plugin manager
+  $pluginManager = \Drupal::service('plugin.manager.search_api_synonym.export');
+
+  // Options
+  $plugin = drush_get_option('plugin');
+  $langcode = drush_get_option('langcode');
+  $type = drush_get_option('type', 'all');
+  $filter = drush_get_option('filter', 'all');
+  $file = drush_get_option('file');
+  $incremental = drush_get_option('incremental');
+
+  // Validate option: plugin
+  if (!$pluginManager->validatePlugin($plugin)) {
+    $error = TRUE;
+    drush_set_error(dt('--plugin is not valid. Please, use an existing plugin machine name.'));
+  }
+
+  // Validate option: langcode
+  if (empty($langcode)) {
+    $error = TRUE;
+    drush_set_error(dt('--langcode is not valid. Please, use an existing language code.'));
+  }
+
+  // Validate option: type
+  if (!empty($type) && !search_api_synonym_drush_validate_option_type($type)) {
+    $error = TRUE;
+    drush_set_error(dt('--type option is not valid. The only allowed values are "synonym", "spelling_error", "all".'));
+  }
+
+  // Validate option: filter
+  if (!empty($filter) && !search_api_synonym_drush_validate_option_filter($filter)) {
+    $error = TRUE;
+    drush_set_error(dt('--filter option is not valid. The only allowed values are "nospace", "onlyspace", "all".'));
+  }
+
+  // Prepare export
+  if (!isset($error)) {
+    drush_log(dt('Starting synonym export....'), 'ok');
+
+    $options = [
+      'langcode' => $langcode,
+      'type' => $type,
+      'filter' => $filter,
+      'file' => $file,
+      'incremental' => (int) $incremental,
+    ];
+    $pluginManager->setPluginId($plugin);
+    $pluginManager->setExportOptions($options);
+
+    // Execute export
+    if ($result = $pluginManager->executeExport()) {
+
+      // Output result
+      drush_log(dt('Synonyms export and saved in the file @file', ['@file' => $result]), 'ok');
+    }
+  }
+}
+
+/**
+ * Validate that the type option is valid.
+ *
+ * @param string $type
+ *   Type value from --type command option.
+ *
+ * @return boolean
+ *   TRUE if valid, FALSE if invalid.
+ */
+function search_api_synonym_drush_validate_option_type($type) {
+  $types = ['synonym', 'spelling_error', 'all'];
+  return in_array($type, $types);
+}
+
+/**
+ * Validate that the filter option is valid.
+ *
+ * @param string $filter
+ *   Type value from --filter command option.
+ *
+ * @return boolean
+ *   TRUE if valid, FALSE if invalid.
+ */
+function search_api_synonym_drush_validate_option_filter($filter) {
+  $filters = ['nospace', 'onlyspace', 'all'];
+  return in_array($filter, $filters);
+}
diff --git a/web/modules/contrib/search_api_synonym/search_api_synonym.info.yml b/web/modules/contrib/search_api_synonym/search_api_synonym.info.yml
new file mode 100644 (file)
index 0000000..a9afd88
--- /dev/null
@@ -0,0 +1,14 @@
+name: Search API Synonym
+type: module
+description: Managing of search synonyms in Drupal.
+# core: 8.x
+package: Search
+configure: entity.search_api_synonym.collection
+dependencies:
+  - system (>=8.2.0)
+
+# Information added by Drupal.org packaging script on 2017-07-24
+version: '8.x-1.2'
+core: '8.x'
+project: 'search_api_synonym'
+datestamp: 1500888244
diff --git a/web/modules/contrib/search_api_synonym/search_api_synonym.install b/web/modules/contrib/search_api_synonym/search_api_synonym.install
new file mode 100644 (file)
index 0000000..695b500
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+use Drupal\Core\Database\Database;
+use Drupal\search_api_synonym\Entity\Synonym;
+
+/**
+ * @file
+ * Contains search_api_synonym.install.
+ */
+
+/**
+ * Change length of the field 'word'.
+ */
+function search_api_synonym_update_8001() {
+  $spec = [
+    'type' => 'varchar',
+    'length' => 128,
+    'not null' => FALSE,
+  ];
+  $schema = Database::getConnection()->schema();
+  $schema->changeField('search_api_synonym', 'word', 'word', $spec);
+}
+
+/**
+ * Remove extra white spaces from synonyms.
+ */
+function search_api_synonym_update_8002() {
+  $sids = \Drupal::entityQuery('search_api_synonym')
+    ->condition('synonyms', '% %', 'LIKE')
+    ->execute();
+
+  foreach ($sids as $sid) {
+    $synonym = Synonym::load($sid);
+    $synonyms = explode(',', $synonym->getSynonyms());
+    array_walk($synonyms, 'trim');
+    $synonyms = implode(',', $synonyms);
+    $synonym->setSynonyms($synonyms);
+    $synonym->save();
+  }
+}
diff --git a/web/modules/contrib/search_api_synonym/search_api_synonym.links.action.yml b/web/modules/contrib/search_api_synonym/search_api_synonym.links.action.yml
new file mode 100644 (file)
index 0000000..c92bcb0
--- /dev/null
@@ -0,0 +1,6 @@
+entity.search_api_synonym.add_form:
+  route_name: 'entity.search_api_synonym.add_form'
+  title: 'Add synonym'
+  appears_on:
+    - entity.search_api_synonym.collection
+
diff --git a/web/modules/contrib/search_api_synonym/search_api_synonym.links.menu.yml b/web/modules/contrib/search_api_synonym/search_api_synonym.links.menu.yml
new file mode 100644 (file)
index 0000000..7f2b91f
--- /dev/null
@@ -0,0 +1,5 @@
+entity.search_api_synonym.collection:
+  title: 'Search API Synonyms'
+  route_name: entity.search_api_synonym.collection
+  description: 'Create and configure synonyms.'
+  parent: system.admin_config_search
diff --git a/web/modules/contrib/search_api_synonym/search_api_synonym.links.task.yml b/web/modules/contrib/search_api_synonym/search_api_synonym.links.task.yml
new file mode 100644 (file)
index 0000000..b1a8a6f
--- /dev/null
@@ -0,0 +1,14 @@
+entity.search_api_synonym.collection:
+  title: List
+  route_name: entity.search_api_synonym.collection
+  base_route: entity.search_api_synonym.collection
+
+entity.search_api_synonym.settings_form:
+  title: Settings
+  route_name: entity.search_api_synonym.settings
+  base_route: entity.search_api_synonym.collection
+
+entity.search_api_synonym.import:
+  title: Import
+  route_name: entity.search_api_synonym.import
+  base_route: entity.search_api_synonym.collection
diff --git a/web/modules/contrib/search_api_synonym/search_api_synonym.module b/web/modules/contrib/search_api_synonym/search_api_synonym.module
new file mode 100644 (file)
index 0000000..e8ab743
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Contains search_api_synonym.module.
+ */
+
+use Drupal\Core\Routing\RouteMatchInterface;
+
+/**
+ * Denotes that the synonym is not active.
+ */
+const SYNONYM_NOT_ACTIVE = 0;
+
+/**
+ * Denotes that the synonym is active.
+ */
+const SYNONYM_ACTIVE = 1;
+
+/**
+ * Implements hook_help().
+ */
+function search_api_synonym_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    // Main module help for the search_api_synonym module.
+    case 'help.page.search_api_synonym':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('Managing of search synonyms in Drupal.') . '</p>';
+      return $output;
+
+    default:
+      return '';
+  }
+}
+
+/**
+ * Implements hook_cron().
+ */
+function search_api_synonym_cron() {
+  $request_time = \Drupal::time()->getRequestTime();
+
+  // Export synonyms to files.
+  $conf = \Drupal::configFactory()->getEditable('search_api_synonym.settings')->get('cron');
+  $interval = !empty($conf['interval']) ? $conf['interval'] : 86400;
+  $next_execution = \Drupal::state()->get('search_api_synonym.export.next_execution', 0);
+
+  if ($request_time >= $next_execution) {
+    $logger = \Drupal::logger('search_api_synonym');
+    // Execute export
+    $logger->notice('Executing export');
+
+    // Plugin manager
+    $pluginManager = \Drupal::service('plugin.manager.search_api_synonym.export');
+
+    // Validate option: plugin
+    $plugin = $conf['plugin'];
+    if (!$pluginManager->validatePlugin($plugin)) {
+      $logger->warning('Export plugin not found');
+      return;
+    }
+
+    // Setting non language specific export options
+    $options = [
+      'type' => $conf['type'],
+      'filter' => $conf['filter'],
+      'file' => '',
+      'incremental' => $conf['export_if_changed'] ? $next_execution : 0
+    ];
+
+    // Get all languages in the system
+    $languages = \Drupal::languageManager()->getLanguages();
+
+    foreach ($languages as $language) {
+      $options['langcode'] = $language->getId();
+
+      // Export synonyms with and without spaces into separate files
+      if ($conf['separate_files'] && ($conf['filter'] == 'none' || !$conf['filter'])) {
+        // Without spaces
+        $options['filter'] = 'nospace';
+        search_api_synonym_execute_single_import($plugin, $options);
+
+        // With spaces
+        $options['filter'] = 'onlyspace';
+        search_api_synonym_execute_single_import($plugin, $options);
+
+      }
+      else {
+        search_api_synonym_execute_single_import($plugin, $options);
+      }
+    }
+
+    $logger->info('Export done');
+
+    \Drupal::state()->set('search_api_synonym.export.next_execution', $request_time + $interval);
+  }
+
+}
+
+/**
+ * Execute single export.
+ *
+ * @param string $plugin
+ *   Plugin name
+ *
+ * @param array $options
+ *   Array of export options
+ */
+function search_api_synonym_execute_single_import($plugin, $options) {
+  // Plugin manager
+  $pluginManager = \Drupal::service('plugin.manager.search_api_synonym.export');
+
+  // Logger
+  $logger = \Drupal::logger('search_api_synonym');
+
+  $pluginManager->setPluginId($plugin);
+  $pluginManager->setExportOptions($options);
+  if ($result = $pluginManager->executeExport()) {
+    $logger->info('Synonyms export to {filename}', ['filename' => $result]);
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/search_api_synonym.permissions.yml b/web/modules/contrib/search_api_synonym/search_api_synonym.permissions.yml
new file mode 100644 (file)
index 0000000..b786d91
--- /dev/null
@@ -0,0 +1,9 @@
+administer search api synonyms:
+  title: 'Administer Search API Synonym synonyms'
+  description: 'Allow to access the administration of search API synonyms entities.'
+administer search api synonym configuration:
+  title: 'Administer Search API Synonym configuration'
+  description: 'Allow to access the configuration of Search API Synonym.'
+import search api synonyms:
+  title: 'Import synonyms to Search API Synonym'
+  description: 'Allow to access to import synonyms into Search API Synonym.'
diff --git a/web/modules/contrib/search_api_synonym/search_api_synonym.routing.yml b/web/modules/contrib/search_api_synonym/search_api_synonym.routing.yml
new file mode 100644 (file)
index 0000000..99b9183
--- /dev/null
@@ -0,0 +1,55 @@
+entity.search_api_synonym.collection:
+  path: '/admin/config/search/search-api-synonyms'
+  defaults:
+    _entity_list: 'search_api_synonym'
+    _title: 'Search API Synonyms'
+  requirements:
+    _permission: 'administer search api synonyms'
+
+entity.search_api_synonym.add_form:
+  path: '/admin/config/search/search-api-synonyms/add'
+  defaults:
+    _entity_form: 'search_api_synonym.add'
+    _title: 'Add synonym'
+  requirements:
+    _permission: 'administer search api synonyms'
+
+entity.search_api_synonym.canonical:
+  path: '/admin/config/search/search-api-synonyms/{search_api_synonym}'
+  defaults:
+    _entity_form: 'search_api_synonym.edit'
+    _title: 'Edit synonym'
+  requirements:
+    _permission: 'administer search api synonyms'
+
+entity.search_api_synonym.edit_form:
+  path: '/admin/config/search/search-api-synonyms/{search_api_synonym}/edit'
+  defaults:
+    _entity_form: 'search_api_synonym.edit'
+    _title: 'Edit synonym'
+  requirements:
+    _permission: 'administer search api synonyms'
+
+entity.search_api_synonym.delete_form:
+  path: '/admin/config/search/search-api-synonyms/{search_api_synonym}/delete'
+  defaults:
+    _entity_form: 'search_api_synonym.delete'
+    _title: 'Delete synonym'
+  requirements:
+    _permission: 'administer search api synonyms'
+
+entity.search_api_synonym.settings:
+  path: '/admin/config/search/search-api-synonyms/settings'
+  defaults:
+    _form: 'Drupal\search_api_synonym\Form\SynonymSettingsForm'
+    _title: 'Settings'
+  requirements:
+    _permission: 'administer search api synonym configuration'
+
+entity.search_api_synonym.import:
+  path: '/admin/config/search/search-api-synonyms/import'
+  defaults:
+    _form: 'Drupal\search_api_synonym\Form\SynonymImportForm'
+    _title: 'Import'
+  requirements:
+    _permission: 'import search api synonyms'
diff --git a/web/modules/contrib/search_api_synonym/search_api_synonym.services.yml b/web/modules/contrib/search_api_synonym/search_api_synonym.services.yml
new file mode 100644 (file)
index 0000000..5d80d80
--- /dev/null
@@ -0,0 +1,12 @@
+services:
+  plugin.manager.search_api_synonym.import:
+    class: Drupal\search_api_synonym\Import\ImportPluginManager
+    parent: default_plugin_manager
+  plugin.manager.search_api_synonym.export:
+    class: Drupal\search_api_synonym\Export\ExportPluginManager
+    parent: default_plugin_manager
+  search_api_synonym.command.export:
+    class: Drupal\search_api_synonym\Command\ExportCommand
+    arguments: []
+    tags:
+      - { name: drupal.command }
diff --git a/web/modules/contrib/search_api_synonym/src/Annotation/SearchApiSynonymExport.php b/web/modules/contrib/search_api_synonym/src/Annotation/SearchApiSynonymExport.php
new file mode 100644 (file)
index 0000000..b157980
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\search_api_synonym\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Plugin annotation object.
+ *
+ * @ingroup plugin_api
+ *
+ * @Annotation
+ */
+class SearchApiSynonymExport extends Plugin {
+
+  /**
+   * The plugin id.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The plugins label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The plugin description.
+   *
+   * @var string
+   */
+  public $description;
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Annotation/SearchApiSynonymImport.php b/web/modules/contrib/search_api_synonym/src/Annotation/SearchApiSynonymImport.php
new file mode 100644 (file)
index 0000000..4c95297
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\search_api_synonym\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Plugin annotation object.
+ *
+ * @ingroup plugin_api
+ *
+ * @Annotation
+ */
+class SearchApiSynonymImport extends Plugin {
+
+  /**
+   * The plugin id.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The plugins label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The plugin description.
+   *
+   * @var string
+   */
+  public $description;
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Command/ExportCommand.php b/web/modules/contrib/search_api_synonym/src/Command/ExportCommand.php
new file mode 100644 (file)
index 0000000..60d6990
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+
+namespace Drupal\search_api_synonym\Command;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Command\Command;
+use Drupal\Console\Core\Command\Shared\ContainerAwareCommandTrait;
+use Drupal\Console\Core\Style\DrupalStyle;
+
+/**
+ * Drupal Console Command for export synonyms.
+ *
+ * @package Drupal\search_api_synonym
+ */
+class ExportCommand extends Command {
+
+  use ContainerAwareCommandTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function configure() {
+    $this
+      ->setName('searchapi:synonym:export')
+      ->setDescription($this->trans('commands.searchapi.synonym.export.description'))
+      ->addOption(
+        'plugin',
+        null,
+        InputOption::VALUE_REQUIRED,
+        $this->trans('commands.searchapi.synonym.export.options.plugin.description')
+      )
+      ->addOption(
+        'langcode',
+        null,
+        InputOption::VALUE_REQUIRED,
+        $this->trans('commands.searchapi.synonym.export.options.langcode.description')
+      )
+      ->addOption(
+        'type',
+        null,
+        InputOption::VALUE_OPTIONAL,
+        $this->trans('commands.searchapi.synonym.export.options.type.description'),
+        'all'
+      )
+      ->addOption(
+        'filter',
+        null,
+        InputOption::VALUE_OPTIONAL,
+        $this->trans('commands.searchapi.synonym.export.options.filter.description'),
+        'all'
+      )
+      ->addOption(
+        'incremental',
+        null,
+        InputOption::VALUE_OPTIONAL,
+        $this->trans('commands.searchapi.synonym.export.options.incremental.description')
+      )
+      ->addOption(
+        'file',
+        null,
+        InputOption::VALUE_OPTIONAL,
+        $this->trans('commands.searchapi.synonym.export.options.file.description')
+      );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function execute(InputInterface $input, OutputInterface $output) {
+    // Plugin manager
+    $pluginManager = \Drupal::service('plugin.manager.search_api_synonym.export');
+
+    // Options
+    $plugin = $input->getOption('plugin');
+    $langcode = $input->getOption('langcode');
+    $type = $input->getOption('type');
+    $filter = $input->getOption('filter');
+    $file = $input->getOption('file');
+    $incremental = $input->getOption('incremental');
+
+    // Command output
+    $io = new DrupalStyle($input, $output);
+
+    // Validate option: plugin
+    if (!$pluginManager->validatePlugin($plugin)) {
+      $error = TRUE;
+      $io->info($this->trans('commands.searchapi.synonym.export.messages.invalidplugin'));
+    }
+
+    // Validate option: langcode
+    if (empty($langcode)) {
+      $error = TRUE;
+      $io->info($this->trans('commands.searchapi.synonym.export.messages.invalidlangcode'));
+    }
+
+    // Validate option: type
+    if (!empty($type) && !$this->validateOptionType($type)) {
+      $error = TRUE;
+      $io->info($this->trans('commands.searchapi.synonym.export.messages.invalidtype'));
+    }
+
+    // Validate option: filter
+    if (!empty($filter) && !$this->validateOptionFilter($filter)) {
+      $error = TRUE;
+      $io->info($this->trans('commands.searchapi.synonym.export.messages.invalidfilter'));
+    }
+
+    // Prepare export
+    if (!isset($error)) {
+      $io->info($this->trans('commands.searchapi.synonym.export.messages.start'));
+
+      $options = [
+        'langcode' => $langcode,
+        'type' => $type,
+        'filter' => $filter,
+        'file' => $file,
+        'incremental' => (int) $incremental,
+      ];
+      $pluginManager->setPluginId($plugin);
+      $pluginManager->setExportOptions($options);
+
+      // Execute export
+      if ($result = $pluginManager->executeExport()) {
+
+        // Output result
+        $io->info($this->trans('commands.searchapi.synonym.export.messages.success'));
+        $io->info($result);
+      }
+    }
+  }
+
+  /**
+   * Validate that the type option is valid.
+   *
+   * @param string $type
+   *   Type value from --type command option.
+   *
+   * @return boolean
+   *   TRUE if valid, FALSE if invalid.
+   */
+  private function validateOptionType($type) {
+    $types = ['synonym', 'spelling_error', 'all'];
+    return in_array($type, $types);
+  }
+
+  /**
+   * Validate that the filter option is valid.
+   *
+   * @param string $filter
+   *   Type value from --filter command option.
+   *
+   * @return boolean
+   *   TRUE if valid, FALSE if invalid.
+   */
+  private function validateOptionFilter($filter) {
+    $filters = ['nospace', 'onlyspace', 'all'];
+    return in_array($filter, $filters);
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Entity/Synonym.php b/web/modules/contrib/search_api_synonym/src/Entity/Synonym.php
new file mode 100644 (file)
index 0000000..df9e2ce
--- /dev/null
@@ -0,0 +1,320 @@
+<?php
+
+namespace Drupal\search_api_synonym\Entity;
+
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\search_api_synonym\SynonymInterface;
+use Drupal\user\UserInterface;
+
+/**
+ * Defines the Synonym entity.
+ *
+ * @ingroup search_api_synonym
+ *
+ * @ContentEntityType(
+ *   id = "search_api_synonym",
+ *   label = @Translation("Search API Synonym"),
+ *   handlers = {
+ *     "views_data" = "Drupal\search_api_synonym\SynonymViewsData",
+ *     "list_builder" = "Drupal\search_api_synonym\SynonymListBuilder",
+ *     "form" = {
+ *       "add" = "Drupal\search_api_synonym\Form\SynonymForm",
+ *       "edit" = "Drupal\search_api_synonym\Form\SynonymForm",
+ *       "delete" = "Drupal\search_api_synonym\Form\SynonymDeleteForm",
+ *     },
+ *   },
+ *   base_table = "search_api_synonym",
+ *   data_table = "search_api_synonym_field_data",
+ *   admin_permission = "administer search api synonyms",
+ *   translatable = FALSE,
+ *   entity_keys = {
+ *     "id" = "sid",
+ *     "label" = "word",
+ *     "uuid" = "uuid",
+ *     "langcode" = "langcode"
+ *   },
+ *   links = {
+ *     "canonical" = "/admin/config/search/search-api-synonyms/{search_api_synonym}",
+ *     "edit-form" = "/admin/config/search/search-api-synonyms/{search_api_synonym}/edit",
+ *     "delete-form" = "/admin/config/search/search-api-synonyms/{search_api_synonym}/delete",
+ *     "collection" = "/admin/config/search/search-api-synonyms"
+ *   }
+ * )
+ */
+class Synonym extends ContentEntityBase implements SynonymInterface {
+
+  use EntityChangedTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
+    parent::preCreate($storage_controller, $values);
+    $values += [
+      'user_id' => \Drupal::currentUser()->id(),
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getType() {
+    return $this->get('type')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setType($type) {
+    $this->set('type', $type);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWord() {
+    return $this->get('word')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setWord($word) {
+    $this->set('word', $word);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSynonyms() {
+    return $this->get('synonyms')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSynonymsFormatted() {
+    $synonyms = $this->get('synonyms')->value;
+    $synonyms = str_replace(',', ', ', $synonyms);
+    return trim($synonyms);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSynonyms($synonyms) {
+    $this->set('synonyms', $synonyms);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCreatedTime() {
+    return $this->get('created')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCreatedTime($timestamp) {
+    $this->set('created', $timestamp);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwner() {
+    return $this->get('uid')->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwnerId() {
+    return $this->get('uid')->target_id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwnerId($uid) {
+    $this->set('uid', $uid);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwner(UserInterface $account) {
+    $this->set('uid', $account->id());
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isActive() {
+    return (bool) $this->get('status');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setActive($active) {
+    $this->set('status', $active ? SYNONYM_ACTIVE : SYNONYM_NOT_ACTIVE);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    /** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
+    /** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
+    $fields = parent::baseFieldDefinitions($entity_type);
+
+    $fields['sid'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('ID'))
+      ->setDescription(t('The ID of the Synonym entity.'))
+      ->setReadOnly(TRUE);
+
+    $fields['uuid'] = BaseFieldDefinition::create('uuid')
+      ->setLabel(t('UUID'))
+      ->setDescription(t('The UUID of the Synonym entity.'))
+      ->setReadOnly(TRUE);
+
+    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('Authored by'))
+      ->setDescription(t('The user ID of author of the Synonym entity.'))
+      ->setSettings([
+        'target_type' => 'user',
+        'handler' => 'default',
+        'required' => TRUE,
+      ])
+      ->setDefaultValueCallback('Drupal\node\Entity\Node::getCurrentUserId')
+      ->setDisplayOptions('view', [
+        'label' => 'hidden',
+        'type' => 'author',
+        'weight' => 0,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'entity_reference_autocomplete',
+        'weight' => 5,
+        'settings' => [
+          'match_operator' => 'CONTAINS',
+          'size' => '60',
+          'autocomplete_type' => 'tags',
+          'placeholder' => '',
+        ],
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['type'] = BaseFieldDefinition::create('list_string')
+      ->setLabel(t('Type'))
+      ->setDescription(t('The type of synonym.'))
+      ->setSettings([
+        'max_length' => 50,
+        'allowed_values' => array('synonym' => 'Synonym', 'spelling_error' => 'Spelling error'),
+      ])
+      ->setRequired(TRUE)
+      ->setDefaultValue('')
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -3,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'options_buttons',
+        'weight' => -3,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['word'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Word'))
+      ->setDescription(t('The word we are defining synonyms for.'))
+      ->setSettings([
+        'max_length' => 128,
+        'text_processing' => 0,
+      ])
+      ->setRequired(TRUE)
+      ->setDefaultValue('')
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -4,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'string_textfield',
+        'weight' => -4,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['synonyms'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Synonyms'))
+      ->setDescription(t('The synonyms to the word. Separate multiple by comma.'))
+      ->setSettings([
+        'max_length' => 1024,
+        'text_processing' => 0,
+      ])
+      ->setRequired(TRUE)
+      ->setDefaultValue('')
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -3,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'string_textfield',
+        'weight' => -3,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['status'] = BaseFieldDefinition::create('boolean')
+      ->setLabel(t('Activate synonym'))
+      ->setDescription(t('Only active synonyms will be used.'))
+      ->setDefaultValue(TRUE)
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'boolean',
+        'weight' => -2,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'boolean_checkbox',
+        'weight' => -2,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['langcode'] = BaseFieldDefinition::create('language')
+      ->setLabel(t('Language'))
+      ->setDescription(t('The language for the Synonym entity.'))
+      ->setDisplayOptions('form', [
+        'type' => 'language_select',
+        'weight' => 10,
+      ])
+      ->setDisplayConfigurable('form', TRUE);
+
+    $fields['created'] = BaseFieldDefinition::create('created')
+      ->setLabel(t('Created'))
+      ->setDescription(t('The time that the entity was created.'));
+
+    $fields['changed'] = BaseFieldDefinition::create('changed')
+      ->setLabel(t('Changed'))
+      ->setDescription(t('The time that the entity was last edited.'));
+
+    return $fields;
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Export/ExportPluginBase.php b/web/modules/contrib/search_api_synonym/src/Export/ExportPluginBase.php
new file mode 100644 (file)
index 0000000..69746fe
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\search_api_synonym\Export;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base class for plugins handling export of search api synonyms.
+ *
+ * @ingroup plugin_api
+ */
+abstract class ExportPluginBase extends PluginBase implements ExportPluginInterface, ContainerFactoryPluginInterface {
+
+  /**
+   * Config factory service.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, ConfigFactoryInterface $config_factory) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+        $configuration, $plugin_id, $plugin_definition, $container->get('config.factory')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfiguration() {
+    return $this->configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfiguration(array $configuration) {
+    $this->configuration += $configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+    $values = $form_state->getValues();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    parent::submitConfigurationForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    return [];
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Export/ExportPluginInterface.php b/web/modules/contrib/search_api_synonym/src/Export/ExportPluginInterface.php
new file mode 100644 (file)
index 0000000..8e685eb
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\search_api_synonym\Export;
+
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+
+/**
+ * Provides an interface for search api synonym export plugins.
+ *
+ * @ingroup plugin_api
+ */
+interface ExportPluginInterface extends PluginFormInterface, ConfigurablePluginInterface {
+
+  /**
+   * Get synonyms in the export format.
+   **
+   * @param array $synonyms
+   *   An array containing synonym objects.
+   *
+   * @return string
+   *   The formatted synonyms as a string ready to be saved to an export file.
+   */
+  public function getFormattedSynonyms(array $synonyms);
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Export/ExportPluginManager.php b/web/modules/contrib/search_api_synonym/src/Export/ExportPluginManager.php
new file mode 100644 (file)
index 0000000..834ba08
--- /dev/null
@@ -0,0 +1,254 @@
+<?php
+
+namespace Drupal\search_api_synonym\Export;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+
+/**
+ * Base class for search api synonym export plugin managers.
+ *
+ * @ingroup plugin_api
+ */
+class ExportPluginManager extends DefaultPluginManager {
+
+  /**
+   * Active plugin id
+   *
+   * @var string
+   */
+  protected $pluginId;
+
+  /**
+   * Export options.
+   *
+   * @var array
+   */
+  protected $exportOptions;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/search_api_synonym/export', $namespaces, $module_handler, 'Drupal\search_api_synonym\Export\ExportPluginInterface', 'Drupal\search_api_synonym\Annotation\SearchApiSynonymExport');
+    $this->alterInfo('search_api_synonym_export_info');
+    $this->setCacheBackend($cache_backend, 'search_api_synonym_export_info_plugins');
+  }
+
+  /**
+   * Set active plugin.
+   *
+   * @param string $plugin_id
+   *   The active plugin.
+   */
+  public function setPluginId($plugin_id) {
+    $this->pluginId = $plugin_id;
+  }
+
+  /**
+   * Get active plugin.
+   *
+   * @return string
+   *   The active plugin.
+   */
+  public function getPluginId() {
+    return $this->pluginId;
+  }
+
+  /**
+   * Set export options.
+   *
+   * @param array $export_options
+   *   Array with export options
+   */
+  public function setExportOptions(array $export_options) {
+    $this->exportOptions = $export_options;
+  }
+
+  /**
+   * Get export options.
+   *
+   * @return array
+   *   Array with export options
+   */
+  public function getExportOptions() {
+    return $this->exportOptions;
+  }
+
+  /**
+   * Get single export option.
+   *
+   * @param string $key
+   *   Option key
+   *
+   * @return string
+   *   Option value
+   */
+  public function getExportOption($key) {
+    return isset($this->exportOptions[$key]) ? $this->exportOptions[$key] : '';
+  }
+
+  /**
+   * Gets a list of available export plugins.
+   *
+   * @return array
+   *   An array with the plugin names as keys and the descriptions as values.
+   */
+  public function getAvailableExportPlugins() {
+    // Use plugin system to get list of available export plugins.
+    $plugins = $this->getDefinitions();
+
+    $output = [];
+    foreach ($plugins as $id => $definition) {
+      $output[$id] = $definition;
+    }
+
+    return $output;
+  }
+
+  /**
+   * Validate that a specific export plugin exists.
+   *
+   * @param string $plugin
+   *   The plugin machine name.
+   *
+   * @return boolean
+   *   TRUE if the plugin exists.
+   */
+  public function validatePlugin($plugin) {
+    if ($this->getDefinition($plugin, FALSE)) {
+      return TRUE;
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Execute the synonym export.
+   *
+   * @return mixed
+   *   Export result
+   */
+  public function executeExport() {
+    // Export plugin instance
+    $instance = $this->createInstance($this->getPluginId(), []);
+
+    // Get synonyms data matching the options.
+    $synonyms = $this->getSynonymsData();
+
+    // We only export if full export or if their is new synonyms.
+    if (!($this->getExportOption('incremental') && empty($synonyms))) {
+      // Get data in the plugin instance format
+      $data = $instance->getFormattedSynonyms($synonyms);
+
+      return $this->saveSynonymsFile($data);
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Get synonyms  matching the export options.
+   *
+   * @return array
+   *   Array with synonyms
+   */
+  private function getSynonymsData() {
+    // Create the db query.
+    $query = \Drupal::database()->select('search_api_synonym', 's');
+    $query->fields('s', ['sid', 'type', 'word', 'synonyms']);
+    $query->condition('s.status', 1);
+    $query->condition('s.langcode', $this->getExportOption('langcode'));
+    $query->orderBy('s.word');
+
+    // Add type condition if it is set and different from all.
+    $type = $this->getExportOption('type');
+    if ($type && $type != 'all') {
+      $query->condition('s.type', $type);
+    }
+
+    // Add filter condition if it is set and different from all.
+    $filter = $this->getExportOption('filter');
+    if ($filter && $filter != 'all') {
+      switch ($filter) {
+        case 'nospace':
+          $query->condition('s.word', '% %', 'NOT LIKE');
+          $query->condition('s.synonyms', '% %', 'NOT LIKE');
+          break;
+        case 'onlyspace':
+          $group = $query->orConditionGroup()
+            ->condition('s.word', '% %', 'LIKE')
+            ->condition('s.synonyms', '% %', 'LIKE');
+          $query = $query->condition($group);
+          break;
+      }
+    }
+
+    // Add changed condition if incremental option is set.
+    if ($incremental = $this->getExportOption('incremental')) {
+      $query->condition('s.changed', $incremental, '>=');
+    }
+
+    // Fetch the result.
+    return $query->execute()->fetchAllAssoc('sid');
+  }
+
+  /**
+   * Save synonyms data to a file.
+   *
+   * @param string $data
+   *   String with the synonyms data being written to a file.
+   *
+   * @return string
+   *   Return path to the saved synonyms file.
+   */
+  private function saveSynonymsFile($data) {
+    if ($file = $this->getExportOption('file')) {
+      $filename = $file;
+    }
+    else {
+      $filename = $this->generateFileName();
+    }
+
+    // Create folder if it does not exist.
+    $folder = 'public://synonyms';
+    file_prepare_directory($folder, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+
+    // Save file and return result.
+    $path = $folder . '/'. $filename;
+    return file_unmanaged_save_data($data, $path, FILE_EXISTS_REPLACE);
+  }
+
+  /**
+   * Generate an export file name based on export options.
+   *
+   * @return string
+   *   The generated file name.
+   */
+  private function generateFileName() {
+    $options = $this->getExportOptions();
+
+    // Add benning of file name
+    $name[] = 'synonyms';
+
+    // Add language code as the first part of the file name.
+    $name[] = "lang_{$options['langcode']}";
+
+    // Add type option to file name
+    if (!empty($options['type'])) {
+      $name[] = "type_{$options['type']}";
+    }
+
+    // Add filter option to file name
+    if (!empty($options['filter'])) {
+      $name[] = "filter_{$options['filter']}";
+    }
+
+    // Implode the name parts.
+    return implode('__', $name) . '.txt';
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Form/SynonymDeleteForm.php b/web/modules/contrib/search_api_synonym/src/Form/SynonymDeleteForm.php
new file mode 100644 (file)
index 0000000..1d3b46a
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\search_api_synonym\Form;
+
+use Drupal\Core\Entity\ContentEntityDeleteForm;
+
+/**
+ * Provides a form for deleting Synonym entities.
+ *
+ * @ingroup search_api_synonym
+ */
+class SynonymDeleteForm extends ContentEntityDeleteForm {
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Form/SynonymForm.php b/web/modules/contrib/search_api_synonym/src/Form/SynonymForm.php
new file mode 100644 (file)
index 0000000..87cd8a9
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\search_api_synonym\Form;
+
+use Drupal\Core\Entity\ContentEntityForm;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Form controller for Synonym edit forms.
+ *
+ * @ingroup search_api_synonym
+ */
+class SynonymForm extends ContentEntityForm {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    // Trim whitespaces from synonyms and save back into the form state.
+    /* @var \Drupal\search_api_synonym\SynonymInterface $entity */
+    $entity = $this->entity;
+    $synonyms = $entity->getSynonymsFormatted();
+    if (!empty($synonyms)) {
+      $entity->setSynonyms($synonyms);
+    }
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    // Trim whitespaces from synonyms and save back into the form state.
+    /* @var \Drupal\search_api_synonym\SynonymInterface $entity */
+    $entity = $this->entity;
+    $trimmed = array_map('trim', explode(',', $entity->getSynonyms()));
+    $entity->setSynonyms(implode(',', $trimmed));
+
+    // Save synonym.
+    $status = parent::save($form, $form_state);
+
+    switch ($status) {
+      case SAVED_NEW:
+        drupal_set_message($this->t('Created the %label Synonym.', [
+          '%label' => $entity->label(),
+        ]));
+        break;
+
+      default:
+        drupal_set_message($this->t('Saved the %label Synonym.', [
+          '%label' => $entity->label(),
+        ]));
+    }
+
+    $form_state->setRedirect($entity->toUrl('collection')->getRouteName());
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Form/SynonymImportForm.php b/web/modules/contrib/search_api_synonym/src/Form/SynonymImportForm.php
new file mode 100644 (file)
index 0000000..eefbffb
--- /dev/null
@@ -0,0 +1,254 @@
+<?php
+
+namespace Drupal\search_api_synonym\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Markup;
+use Drupal\search_api_synonym\Import\Importer;
+use Drupal\search_api_synonym\Import\ImportPluginManager;
+use Drupal\search_api_synonym\Import\Import;
+use Drupal\search_api_synonym\ImportException;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class SynonymImportForm.
+ *
+ * @package Drupal\search_api_synonym\Form
+ *
+ * @ingroup search_api_synonym
+ */
+class SynonymImportForm extends FormBase {
+
+  /**
+   * Import plugin manager.
+   *
+   * @var \Drupal\search_api_synonym\Import\ImportPluginManager
+   */
+  protected $pluginManager;
+
+  /**
+   * An array containing available import plugins.
+   *
+   * @var array
+   */
+  protected $availablePlugins = [];
+
+  /**
+   * Constructs a SynonymImportForm object.
+   *
+   * @param \Drupal\search_api_synonym\Import\ImportPluginManager $manager
+   *   Import plugin manager.
+   */
+  public function __construct(ImportPluginManager $manager) {
+    $this->pluginManager = $manager;
+
+    foreach ($manager->getAvailableImportPlugins() as $id => $definition) {
+      $this->availablePlugins[$id] = $manager->createInstance($id);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.search_api_synonym.import')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'search_api_synonym_import';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    // File.
+    $form['file_upload'] = [
+      '#type' => 'file',
+      '#title' => $this->t('File'),
+      '#description' => $this->t('Select the import file.'),
+      '#required' => FALSE,
+    ];
+
+    // Update
+    $form['update_existing'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Update existing'),
+      '#description' => $this->t('What should happen with existing synonyms?'),
+      '#options' => [
+        'merge' => $this->t('Merge'),
+        'overwrite' => $this->t('Overwrite')
+      ],
+      '#default_value' => 'merge',
+      '#required' => TRUE,
+    ];
+
+    // Synonym type.
+    $form['synonym_type'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Type '),
+      '#description' => $this->t('Which synonym type should the imported data be saved as?'),
+      '#options' => [
+        'synonym' => $this->t('Synonym'),
+        'spelling_error' => $this->t('Spelling error'),
+        'mixed' => $this->t('Mixed - Controlled by information in the source file')
+      ],
+      '#default_value' => 'synonym',
+      '#required' => TRUE,
+    ];
+
+    $message = $this->t('Notice: the source file must contain information per synonym about the synonym type. All synonyms without type information will be skipped during import!');
+    $message = Markup::create('<div class="messages messages--warning">' . $message . '</div>');
+    $form['synonym_type_notice'] = [
+      '#type' => 'item',
+      '#markup' => $message,
+      '#states' => [
+        'visible' => [
+          ':radio[name="synonym_type"]' => ['value' => 'mixed'],
+        ],
+      ],
+    ];
+
+    // Activate.
+    $form['status'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Activate synonyms'),
+      '#description' => $this->t('Mark import synonyms as active. Only active synonyms will be exported to the configured search backend.'),
+      '#default_value' => TRUE,
+      '#required' => TRUE,
+    ];
+
+    // Language code.
+    $form['langcode'] = [
+      '#type' => 'language_select',
+      '#title' => $this->t('Language'),
+      '#description' => $this->t('Which language should the imported data be saved as?'),
+      '#default_value' => \Drupal::languageManager()->getCurrentLanguage()->getId(),
+    ];
+
+    // Import plugin configuration.
+    $form['plugin'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Import format'),
+      '#description' => $this->t('Choose the import format to use.'),
+      '#options' => [],
+      '#default_value' => key($this->availablePlugins),
+      '#required' => TRUE,
+    ];
+
+    $form['plugin_settings'] = [
+      '#tree' => TRUE,
+    ];
+
+    foreach ($this->availablePlugins as $id => $instance) {
+      $definition = $instance->getPluginDefinition();
+      $form['plugin']['#options'][$id] = $definition['label'];
+      $form['plugin_settings'][$id] = [
+        '#type' => 'details',
+        '#title' => $this->t('@plugin plugin', ['@plugin' => $definition['label']]),
+        '#open' => TRUE,
+        '#tree' => TRUE,
+        '#states' => [
+          'visible' => [
+            ':radio[name="plugin"]' => ['value' => $id],
+          ],
+        ],
+      ];
+      $form['plugin_settings'][$id] += $instance->buildConfigurationForm([], $form_state);
+    }
+
+    // Actions.
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Import file'),
+      '#button_type' => 'primary',
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    parent::validateForm($form, $form_state);
+
+    $values = $form_state->getValues();
+    // Get plugin instance for active plugin.
+    $instance_active = $this->getPluginInstance($values['plugin']);
+
+    // Validate the uploaded file.
+    $extensions = $instance_active->allowedExtensions();
+    $validators = ['file_validate_extensions' => $extensions];
+
+    $file = file_save_upload('file_upload', $validators, FALSE, 0, FILE_EXISTS_RENAME);
+    if (isset($file)) {
+      if ($file) {
+        $form_state->setValue('file_upload', $file);
+      }
+      else {
+        $form_state->setErrorByName('file_upload', $this->t('The import file could not be uploaded.'));
+      }
+    }
+
+    // Call the form validation handler for each of the plugins.
+    foreach ($this->availablePlugins as $instance) {
+      $instance->validateConfigurationForm($form, $form_state);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    try {
+      // All values from the form.
+      $values = $form_state->getValues();
+
+      // Instance of active import plugin.
+      $plugin_id = $values['plugin'];
+      $instance = $this->getPluginInstance($plugin_id);
+
+      // Parse file.
+      $data = $instance->parseFile($values['file_upload'], (array) $values['plugin_settings'][$plugin_id]);
+
+      // Import data.
+      $importer = new Importer();
+      $results = $importer->execute($data, $values);
+
+      if (!empty($results['errors'])) {
+        $count = count($results['errors']);
+        $message = \Drupal::translation()->formatPlural($count,
+          '@count synonym failed import.',
+          '@count synonyms failed import.',
+          ['@count' => $count]
+        );
+        drupal_set_message($message);
+      }
+    }
+    catch (ImportException $e) {
+      $this->logger('search_api_synonym')->error($this->t('Failed to import file due to "%error".', ['%error' => $e->getMessage()]));
+      drupal_set_message($this->t('Failed to import file due to "%error".', ['%error' => $e->getMessage()]));
+    }
+  }
+
+  /**
+   * Returns an import plugin instance for a given plugin id.
+   *
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   *
+   * @return \Drupal\search_api_synonym\Import\ImportPluginInterface
+   *   An import plugin instance.
+   */
+  public function getPluginInstance($plugin_id) {
+    return $this->pluginManager->createInstance($plugin_id, []);
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Form/SynonymSettingsForm.php b/web/modules/contrib/search_api_synonym/src/Form/SynonymSettingsForm.php
new file mode 100644 (file)
index 0000000..54a2e6e
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+
+namespace Drupal\search_api_synonym\Form;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\search_api_synonym\Export\ExportPluginManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class SynonymSettingsForm.
+ *
+ * @package Drupal\search_api_synonym\Form
+ *
+ * @ingroup search_api_synonym
+ */
+class SynonymSettingsForm extends ConfigFormBase {
+
+  /**
+   * An array containing available export plugins.
+   *
+   * @var array
+   */
+  protected $availablePlugins = [];
+
+  /**
+   * Constructs a VacancySourceForm object.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The factory for configuration objects.
+   * @param \Drupal\search_api_synonym\Export\ExportPluginManager $manager
+   *   The synonyms export plugin manager.
+   */
+  public function __construct(ConfigFactoryInterface $config_factory, ExportPluginManager $manager) {
+    parent::__construct($config_factory);
+
+    foreach ($manager->getAvailableExportPlugins() as $id => $definition) {
+      $this->availablePlugins[$id] = $manager->createInstance($id);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('plugin.manager.search_api_synonym.export')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'search_api_synonym_settings';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return ['search_api_synonym.settings'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->config($this->getEditableConfigNames()[0]);
+
+    // Add cron settings
+    $cron = $config->get('cron');
+    $form['cron'] = [
+      '#title' => $this->t('Cron settings'),
+      '#type' => 'details',
+      '#open' => TRUE,
+      '#tree' => TRUE,
+    ];
+
+    $options = [];
+    foreach ($this->availablePlugins as $id => $source) {
+      $definition = $source->getPluginDefinition();
+      $options[$id] = $definition['label'];
+    }
+    $form['cron']['plugin'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Synonym export plugin'),
+      '#description' => $this->t('Select the export plugin being used by cron.'),
+      '#default_value' => $cron['plugin'] ? $cron['plugin'] : '',
+      '#options' => $options,
+    ];
+
+    $options = [900, 1800, 3600, 10800, 21600, 43200, 86400, 604800];
+    $form['cron']['interval'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Export synonyms every'),
+      '#description' => $this->t('How often should Drupal export synonyms?'),
+      '#default_value' => $cron['interval'] ? $cron['interval'] : 86400,
+      '#options' => array_map([\Drupal::service('date.formatter'), 'formatInterval'], array_combine($options, $options)),
+    ];
+
+    $form['cron']['type'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Type'),
+      '#description' => $this->t('Which synonym type should be exported by cron?'),
+      '#default_value' => $cron['type'] ? $cron['type'] : 'all',
+      '#options' => [
+        'all' => $this->t('All'),
+        'synonym' => $this->t('Synonyms'),
+        'spelling_error' => $this->t('Spelling errors')
+      ],
+    ];
+
+    $form['cron']['filter'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Filter'),
+      '#description' => $this->t('Which filters should be used when selecting synonyms.'),
+      '#default_value' => $cron['filter'] ? $cron['filter'] : 'none',
+      '#options' => [
+        'none' => $this->t('No filter'),
+        'nospace' => $this->t('Synonyms without spaces in the word'),
+        'onlyspace' => $this->t('Synonyms with spaces in the word')
+      ],
+    ];
+
+    $form['cron']['separate_files'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Separate files'),
+      '#description' => $this->t('Export synonyms with and without spaces into separate files.'),
+      '#default_value' => $cron['separate_files'] ? $cron['separate_files'] : '',
+      '#states' => [
+        'visible' => [
+          ':radio[name="cron[filter]"]' => ['value' => 'none'],
+        ],
+      ],
+    ];
+
+    $form['cron']['export_if_changed'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Only export if changes'),
+      '#description' => $this->t('Only export synonyms if their is either new or changed synonyms since last export.'),
+      '#default_value' => $cron['export_if_changed'] ? $cron['export_if_changed'] : FALSE,
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    parent::validateForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->config($this->getEditableConfigNames()[0])
+      ->set('cron', $form_state->getValue('cron'))
+      ->save();
+
+    parent::submitForm($form, $form_state);
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Import/ImportException.php b/web/modules/contrib/search_api_synonym/src/Import/ImportException.php
new file mode 100644 (file)
index 0000000..98da192
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\search_api_synonym;
+
+use Drupal\Component\Plugin\Exception;
+
+/**
+ * ImportException extending Generic Plugin exception class.
+ */
+class ImportException extends \Exception {
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Import/ImportPluginBase.php b/web/modules/contrib/search_api_synonym/src/Import/ImportPluginBase.php
new file mode 100644 (file)
index 0000000..78c0e2b
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\search_api_synonym\Import;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\file\Entity\File;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base class for plugins handling import of search api synonyms.
+ *
+ * @ingroup plugin_api
+ */
+abstract class ImportPluginBase extends PluginBase implements ImportPluginInterface, ContainerFactoryPluginInterface {
+
+  /**
+   * Config factory service.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, ConfigFactoryInterface $config_factory) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+        $configuration, $plugin_id, $plugin_definition, $container->get('config.factory')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function parseFile(File $file, array $settings = []) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function allowedExtensions() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfiguration() {
+    return $this->configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfiguration(array $configuration) {
+    $this->configuration += $configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+    $values = $form_state->getValues();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    parent::submitConfigurationForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    return [];
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Import/ImportPluginInterface.php b/web/modules/contrib/search_api_synonym/src/Import/ImportPluginInterface.php
new file mode 100644 (file)
index 0000000..4f7522d
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\search_api_synonym\Import;
+
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\file\Entity\File;
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+
+/**
+ * Provides an interface for search api synonym import plugins.
+ *
+ * @ingroup plugin_api
+ */
+interface ImportPluginInterface extends PluginFormInterface, ConfigurablePluginInterface {
+
+  /**
+   * Parse the import file.
+   *
+   * @param \Drupal\file\Entity\File $file
+   *   The temporary file object.
+   * @param array $settings
+   *   Array with plugin settings.
+   *
+   * @return string
+   *   The parsed file content.
+   */
+  public function parseFile(File $file, array $settings = []);
+
+  /**
+   * Plugin configuration form.
+   *
+   * @param array $form
+   *   Form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state array.
+   *
+   * @return array
+   *   Form array.
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state);
+
+  /**
+   * Validate configuration form.
+   *
+   * @param array $form
+   *   Form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state array.
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state);
+
+  /**
+   * Get a list of allowed file extensions.
+   *
+   * @return array
+   *   List of allowed extensions.
+   */
+  public function allowedExtensions();
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Import/ImportPluginManager.php b/web/modules/contrib/search_api_synonym/src/Import/ImportPluginManager.php
new file mode 100644 (file)
index 0000000..dd76be4
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+
+namespace Drupal\search_api_synonym\Import;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+
+/**
+ * Base class for search api synonym import plugin managers.
+ *
+ * @ingroup plugin_api
+ */
+class ImportPluginManager extends DefaultPluginManager {
+
+  /**
+   * Active plugin id
+   *
+   * @var string
+   */
+  protected $pluginId;
+
+  /**
+   * Import options.
+   *
+   * @var array
+   */
+  protected $importOptions;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/search_api_synonym/import', $namespaces, $module_handler, 'Drupal\search_api_synonym\Import\ImportPluginInterface', 'Drupal\search_api_synonym\Annotation\SearchApiSynonymImport');
+    $this->alterInfo('search_api_synonym_import_info');
+    $this->setCacheBackend($cache_backend, 'search_api_synonym_import_info_plugins');
+  }
+
+  /**
+   * Set active plugin
+   *
+   * @param string $plugin_id
+   *   The active plugin.
+   */
+  public function setPluginId($plugin_id) {
+    $this->pluginId = $plugin_id;
+  }
+
+  /**
+   * Get active plugin
+   *
+   * @return string
+   *   The active plugin.
+   */
+  public function getPluginId() {
+    return $this->pluginId;
+  }
+
+  /**
+   * Gets a list of available import plugins.
+   *
+   * @return array
+   *   An array with the plugin names as keys and the descriptions as values.
+   */
+  public function getAvailableImportPlugins() {
+    // Use plugin system to get list of available import plugins.
+    $plugins = $this->getDefinitions();
+
+    $output = [];
+    foreach ($plugins as $id => $definition) {
+      $output[$id] = $definition;
+    }
+
+    return $output;
+  }
+
+  /**
+   * Validate that a specific import plugin exists.
+   *
+   * @param string $plugin
+   *   The plugin machine name.
+   *
+   * @return boolean
+   *   TRUE if the plugin exists.
+   */
+  public function validatePlugin($plugin) {
+    if ($this->getDefinition($plugin, FALSE)) {
+      return TRUE;
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Import/Importer.php b/web/modules/contrib/search_api_synonym/src/Import/Importer.php
new file mode 100644 (file)
index 0000000..5defcc2
--- /dev/null
@@ -0,0 +1,275 @@
+<?php
+
+namespace Drupal\search_api_synonym\Import;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityRepositoryInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\search_api_synonym\Entity\Synonym;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Importer class.
+ *
+ * Process and import synonyms data.
+ *
+ * @package Drupal\search_api_synonym
+ */
+class Importer {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The entity repository.
+   *
+   * @var \Drupal\Core\Entity\EntityRepositoryInterface
+   */
+  protected $entityRepository;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The database connection used to check the IP against.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * Constructs Importer.
+   */
+  public function __construct() {
+    $this->entityManager = \Drupal::service('entity.manager');
+    $this->entityRepository = \Drupal::service('entity.repository');
+    $this->moduleHandler = \Drupal::service('module_handler');
+    $this->connection = \Drupal::service('database');
+  }
+
+  /**
+   * Execute the import of an array with synonyms.
+   *
+   * @param array $items
+   *   Raw synonyms data.
+   * @param array $settings
+   *   Import settings.
+   *
+   * @return array
+   *   Array with info about the result.
+   */
+  public function execute(array $items, array $settings) {
+    // Prepare items.
+    $items = $this->prepare($items, $settings);
+
+    // Create synonyms.
+    $results = $this->createSynonyms($items, $settings);
+
+    return $results;
+  }
+
+  /**
+   * Prepare and validate the data.
+   *
+   * @param array $items
+   *   Raw synonyms data.
+   * @param array $settings
+   *   Import settings.
+   *
+   * @return array
+   *   Array with prepared data.
+   */
+  private function prepare(array $items, array $settings) {
+    $prepared = [];
+
+    foreach ($items as $item) {
+      // Decide which synonym type to use.
+      if ($settings['synonym_type'] != 'mixed') {
+        $type = $settings['synonym_type'];
+      }
+      else {
+        $type = !empty($item['type']) ? $item['type'] : 'empty';
+      }
+
+      $prepared[$type][$item['word']][] = $item['synonym'];
+    }
+
+    return $prepared;
+  }
+
+  /**
+   * Create synonyms.
+   *
+   * @param array $items
+   *   Raw synonyms data.
+   * @param array $settings
+   *   Import settings.
+   *
+   * @return array
+   *   Array with info about the result.
+   */
+  public function createSynonyms(array $items, array $settings) {
+    $context = [];
+
+    // Import with batch.
+    $operations = [];
+
+    foreach ($items as $type => $item) {
+      // Continue with next item if type is not valid.
+      if ($type == 'empty') {
+        $context['results']['errors'][] = [
+          'word' => key($item),
+          'synonyms' => current($item)
+        ];
+        continue;
+      }
+
+      // Add each item to the batch.
+      foreach ($item as $word => $synonyms) {
+        $operations[] = [
+          '\Drupal\search_api_synonym\Import\Importer::createSynonym',
+          [$word, $synonyms, $type, $settings]
+        ];
+      }
+    }
+
+    $batch = [
+      'title' => t('Import synonyms...'),
+      'operations' => $operations,
+      'finished' => '\Drupal\search_api_synonym\Import\Importer::createSynonymBatchFinishedCallback',
+    ];
+    batch_set($batch);
+
+    return isset($context['results']) ? $context['results'] : NULL;
+  }
+
+  /**
+   * Create / update a synonym.
+   *
+   * @param string $word
+   *   The source word we add the synonym for.
+   * @param array $synonyms
+   *   Simple array with synonyms.
+   * @param string $type
+   *   The synonym type.
+   * @param array $settings
+   *   Import settings.
+   * @param array $context
+   *   Batch context - also used for storing results in non batch operations.
+   */
+  public static function createSynonym($word, array $synonyms, $type, array $settings, array &$context) {
+    $request_time = \Drupal::time()->getRequestTime();
+
+    // Check if we have an existing synonym entity we should update.
+    $sid = Importer::lookUpSynonym($word, $type, $settings['langcode']);
+
+    // Trim spaces from synonyms.
+    $synonyms = array_map('trim', $synonyms);
+
+    // Load and update existing synonym entity.
+    if ($sid) {
+      $entity = Synonym::load($sid);
+
+      // Update method = Merge.
+      if ($settings['update_existing'] == 'merge') {
+        $existing = $entity->getSynonyms();
+        $existing = array_map('trim', explode(',', $existing));
+        $synonyms = array_unique(array_merge($existing, $synonyms));
+      }
+
+      $synonyms_str = implode(',', $synonyms);
+      $entity->setSynonyms($synonyms_str);
+    }
+    // Create new entity.
+    else {
+      $entity = Synonym::create([
+        'langcode' => $settings['langcode'],
+      ]);
+      $uid = \Drupal::currentUser()->id();
+      $entity->setOwnerId($uid);
+      $entity->setCreatedTime($request_time);
+      $entity->setType($type);
+      $entity->setWord($word);
+      $synonyms_str = implode(',', $synonyms);
+      $entity->setSynonyms($synonyms_str);
+    }
+
+    $entity->setChangedTime($request_time);
+    $entity->setActive($settings['status']);
+
+    $entity->save();
+
+    if ($sid = $entity->id()) {
+      $context['results']['success'][] = $sid;
+    }
+    else {
+      $context['results']['errors'][] = [
+        'word' => $word,
+        'synonyms' => $synonyms
+      ];
+    }
+  }
+
+  /**
+   * Batch finished callback.
+   *
+   * @param bool $success
+   *   Was the batch successful or not?
+   * @param array $result
+   *   Array with the result of the import.
+   * @param array $operations
+   *   Batch operations.
+   * @param string $elapsed
+   *   Formatted string with the time batch operation was running.
+   */
+  public static function createSynonymBatchFinishedCallback($success, $result, $operations, $elapsed) {
+    if ($success) {
+      // Set message before returning to form.
+      if (!empty($result['success'])) {
+        $count = count($result['success']);
+        $message = \Drupal::translation()->formatPlural($count,
+          '@count synonym was successfully imported.',
+          '@count synonyms was successfully imported.',
+          ['@count' => $count]
+        );
+        drupal_set_message($message);
+      }
+    }
+  }
+
+  /**
+   * Look up synonym.
+   *
+   * @param string $word
+   *   The source word we add the synonym for.
+   * @param string $type
+   *   Synonym type.
+   * @param string $langcode
+   *   Language code.
+   *
+   * @return int
+   *   Entity id for the found synonym.
+   */
+  public static function lookUpSynonym($word, $type, $langcode) {
+    $query = \Drupal::database()->select('search_api_synonym', 's');
+    $query->fields('s', ['sid']);
+    $query->condition('s.type', $type);
+    $query->condition('s.word', $word, 'LIKE');
+    $query->condition('s.langcode', $langcode);
+    $query->range(0, 1);
+    return (int) $query->execute()->fetchField(0);
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/export/Solr.php b/web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/export/Solr.php
new file mode 100644 (file)
index 0000000..bf9ff4e
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\search_api_synonym\Plugin\search_api_synonym\export;
+
+use Drupal\search_api_synonym\Export\ExportPluginBase;
+use Drupal\search_api_synonym\Export\ExportPluginInterface;
+
+/**
+ * Provides a synonym export plugin for Apache Solr..
+ *
+ * @SearchApiSynonymExport(
+ *   id = "solr",
+ *   label = @Translation("Solr"),
+ *   description = @Translation("Synonym export plugin for Apache Solr")
+ * )
+ */
+class Solr extends ExportPluginBase implements ExportPluginInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormattedSynonyms(array $synonyms) {
+    $lines = [];
+
+    $lines[] = "#";
+    $lines[] = "# Synonyms file for Apache Solr generated by Search API Synonym.";
+    $lines[] = "# See file https://www.drupal.org/project/search_api_synonym.";
+    $lines[] = "#";
+    $lines[] = "";
+
+    // Generate a line for each synonym.
+    foreach ($synonyms as $synonym) {
+      $lines[] = $this->generateLine($synonym->word, $synonym->synonyms, $synonym->type);
+    }
+
+    return implode("\n", $lines);
+  }
+
+  /**
+   * Generate a single synonyms line for the export file.
+   *
+   * @param string $word
+   *   The main word.
+   *
+   * @param string $synonyms
+   *   The comma separated string with synonyms.
+   *
+   * @param string $type
+   *   Synonym (synonym) og Spelling error (spelling_error)
+   *
+   * @return string
+   *   Return the single line with synonyms and the corresponding word.
+   */
+  private function generateLine($word, $synonyms, $type) {
+    $line = '';
+
+    switch ($type) {
+      case 'synonym':
+        // We force using of equivalent mappings for type = synonym.
+        $line = "{$word}, {$synonyms}";
+        break;
+      case 'spelling_error':
+        $line = "{$word} => {$synonyms}";
+        break;
+    }
+    return $line;
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/import/CSV.php b/web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/import/CSV.php
new file mode 100644 (file)
index 0000000..31da97b
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+
+namespace Drupal\search_api_synonym\Plugin\search_api_synonym\import;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\file\Entity\File;
+use Drupal\search_api_synonym\Import\ImportPluginBase;
+use Drupal\search_api_synonym\Import\ImportPluginInterface;
+
+/**
+ * Import of CSV files.
+ *
+ * @SearchApiSynonymImport(
+ *   id = "csv",
+ *   label = @Translation("CSV"),
+ *   description = @Translation("Synonym import plugin from CSV / delimited file.")
+ * )
+ */
+class CSV extends ImportPluginBase implements ImportPluginInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function parseFile(File $file, array $settings = []) {
+    $data = [];
+    $delimiter = $settings['delimiter'];
+    $enclosure = $settings['enclosure'];
+    $header_row = $settings['header_row'];
+    
+    $i = 1;
+    if (($handle = fopen($file->getFileUri(), 'r')) !== FALSE) {
+      while (($row = fgetcsv($handle, 1000, $delimiter, $enclosure)) !== FALSE) {
+        if ($header_row && $i++ == 1) {
+          continue;
+        }
+
+        if (!empty($row[0]) && !empty($row[1])) {
+          $data[] = [
+            'word' => $row[0],
+            'synonym' => $row['1'],
+            'type' => !empty($row['2']) ? $row['2'] : ''
+          ];
+        }
+      }
+      fclose($handle);
+    }
+
+    return $data;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $example_url = 'internal:' . base_path() . drupal_get_path('module', 'search_api_synonym') . '/examples/example.csv';
+    $form['template'] = [
+      '#type' => 'item',
+      '#title' => $this->t('Example'),
+      '#markup' => Link::fromTextAndUrl(t('Download example file'), Url::fromUri($example_url))->toString()
+    ];
+    $form['delimiter'] = [
+      '#type' => 'select',
+      '#title' => t('Delimiter'),
+      '#description' => t('Field delimiter character used in the import file.'),
+      '#options' => [
+        ';' => $this->t('Semicolon'),
+        ',' => $this->t('Comma'),
+        '\t' => $this->t('Tab'),
+        '|' => $this->t('Pipe'),
+      ],
+      '#default_value' => ';',
+      '#required' => TRUE
+    ];
+    $form['enclosure'] = [
+      '#type' => 'select',
+      '#title' => t('Text qualifier'),
+      '#description' => t('Field enclosure character used in import file.'),
+      '#options' => [
+        '"' => '"',
+        "'" => "'",
+        '' => $this->t('None'),
+      ],
+      '#default_value' => '"'
+    ];
+    $form['header_row'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Header row'),
+      '#description' => $this->t('Does the file contain a header row that should be skipped in the import?'),
+      '#default_value' => FALSE,
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+    $values = $form_state->getValues();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function allowedExtensions() {
+    return ['csv txt'];
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/import/JSON.php b/web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/import/JSON.php
new file mode 100644 (file)
index 0000000..58901d0
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\search_api_synonym\Plugin\search_api_synonym\import;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\Component\Serialization\Json AS SerializationJSON;
+use Drupal\file\Entity\File;
+use Drupal\search_api_synonym\Import\ImportPluginBase;
+use Drupal\search_api_synonym\Import\ImportPluginInterface;
+
+/**
+ * Import of JSON files.
+ *
+ * @SearchApiSynonymImport(
+ *   id = "json",
+ *   label = @Translation("JSON"),
+ *   description = @Translation("Synonym import plugin from JSON file.")
+ * )
+ */
+class JSON extends ImportPluginBase implements ImportPluginInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function parseFile(File $file, array $settings = []) {
+    $data = [];
+    $json = file_get_contents($file->getFileUri());
+
+    if ($items = SerializationJSON::decode($json)) {
+      foreach ($items as $item) {
+        if (!empty($item['word']) && !empty($item['synonym'])) {
+          $data[] = [
+            'word' => $item['word'],
+            'synonym' => $item['synonym'],
+            'type' => !empty($item['type']) ? $item['type'] : ''
+          ];
+        }
+      }
+    }
+
+    return $data;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $example_url = 'internal:' . base_path() . drupal_get_path('module', 'search_api_synonym') . '/examples/example.json';
+    $form['template'] = [
+      '#type' => 'item',
+      '#title' => $this->t('Example'),
+      '#markup' => Link::fromTextAndUrl(t('Download example file'), Url::fromUri($example_url))->toString()
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function allowedExtensions() {
+    return ['json'];
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/import/Solr.php b/web/modules/contrib/search_api_synonym/src/Plugin/search_api_synonym/import/Solr.php
new file mode 100644 (file)
index 0000000..13a9852
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\search_api_synonym\Plugin\search_api_synonym\import;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\file\Entity\File;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\search_api_synonym\Import\ImportPluginBase;
+use Drupal\search_api_synonym\Import\ImportPluginInterface;
+
+/**
+ * Import of Solr synonyms.txt files.
+ *
+ * @SearchApiSynonymImport(
+ *   id = "solr",
+ *   label = @Translation("Solr"),
+ *   description = @Translation("Synonym import plugin from Solr synonyms.txt file.")
+ * )
+ */
+class Solr extends ImportPluginBase implements ImportPluginInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function parseFile(File $file, array $settings = []) {
+    $data = [];
+
+    // Read file.
+    $rows = file($file->getFileUri());
+
+    if (is_array($rows)) {
+      foreach ($rows as $row) {
+        $row = trim($row);
+
+        // Skip comment lines
+        if (empty($row) || substr($row, 0, 1) == '#') {
+          continue;
+        }
+
+        $parts = explode('=>', $row);
+
+        // Spelling error.
+        if (count($parts) == 2) {
+          $data[] = [
+            'word' => trim($parts[0]),
+            'synonym' => trim($parts['1']),
+            'type' => 'spelling_error'
+          ];
+        }
+        // Synonym.
+        else {
+          $data[] = [
+            'word' => trim(substr($row, 0, strpos($row, ','))),
+            'synonym' => trim(substr($row, strpos($row, ',') + 1)),
+            'type' => 'synonym'
+          ];
+        }
+      }
+    }
+
+    return $data;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $example_url = 'internal:' . base_path() . drupal_get_path('module', 'search_api_synonym') . '/examples/solr_synonyms.txt';
+    $form['template'] = [
+      '#type' => 'item',
+      '#title' => $this->t('Example'),
+      '#markup' => Link::fromTextAndUrl(t('Download example file'), Url::fromUri($example_url))->toString()
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function allowedExtensions() {
+    return ['txt'];
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/SynonymInterface.php b/web/modules/contrib/search_api_synonym/src/SynonymInterface.php
new file mode 100644 (file)
index 0000000..d673344
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+
+namespace Drupal\search_api_synonym;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\user\EntityOwnerInterface;
+
+/**
+ * Provides an interface for defining Synonym entities.
+ *
+ * @ingroup search_api_synonym
+ */
+interface SynonymInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface {
+  // Add get/set methods for your configuration properties here.
+
+  /**
+   * Gets the Synonym type.
+   *
+   * @return string
+   *   Type of the Synonym.
+   */
+  public function getType();
+
+  /**
+   * Sets the Synonym type.
+   *
+   * @param string $type
+   *   The Synonym type.
+   *
+   * @return \Drupal\search_api_synonym\SynonymInterface
+   *   The called Synonym entity.
+   */
+  public function setType($type);
+
+  /**
+   * Gets the Synonym word.
+   *
+   * @return string
+   *   Word of the Synonym.
+   */
+  public function getWord();
+
+  /**
+   * Sets the Synonym word.
+   *
+   * @param string $word
+   *   The Synonym word.
+   *
+   * @return \Drupal\search_api_synonym\SynonymInterface
+   *   The called Synonym entity.
+   */
+  public function setWord($word);
+
+  /**
+   * Gets the synonyms.
+   *
+   * @return string
+   *   The synonyms to the word.
+   */
+  public function getSynonyms();
+
+  /**
+   * Gets the synonyms formatted.
+   *
+   * Format the comma separated synonyms string with extra spaces.
+   *
+   * @return string
+   *   The synonyms to the word.
+   */
+  public function getSynonymsFormatted();
+
+  /**
+   * Sets the synonyms to the word.
+   *
+   * @param string $synonyms
+   *   The synonyms.
+   *
+   * @return \Drupal\search_api_synonym\SynonymInterface
+   *   The called Synonym entity.
+   */
+  public function setSynonyms($synonyms);
+
+  /**
+   * Gets the Synonym creation timestamp.
+   *
+   * @return int
+   *   Creation timestamp of the Synonym.
+   */
+  public function getCreatedTime();
+
+  /**
+   * Sets the Synonym creation timestamp.
+   *
+   * @param int $timestamp
+   *   The Synonym creation timestamp.
+   *
+   * @return \Drupal\search_api_synonym\SynonymInterface
+   *   The called Synonym entity.
+   */
+  public function setCreatedTime($timestamp);
+
+  /**
+   * Returns the Synonym active status indicator.
+   *
+   * Not active synonyms are not used by the search engine.
+   *
+   * @return bool
+   *   TRUE if the Synonym is active.
+   */
+  public function isActive();
+
+  /**
+   * Sets the active status of a Synonym.
+   *
+   * @param bool $active
+   *   TRUE to set this Synonym to active, FALSE to set it to not active.
+   *
+   * @return \Drupal\search_api_synonym\SynonymInterface
+   *   The called Synonym entity.
+   */
+  public function setActive($active);
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/SynonymListBuilder.php b/web/modules/contrib/search_api_synonym/src/SynonymListBuilder.php
new file mode 100644 (file)
index 0000000..99f14a1
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+
+namespace Drupal\search_api_synonym;
+
+use Drupal\Core\Datetime\DateFormatterInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityListBuilder;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a class to build a listing of Synonym entities.
+ *
+ * @ingroup search_api_synonym
+ */
+class SynonymListBuilder extends EntityListBuilder {
+
+  /**
+   * The date formatter service.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatterInterface
+   */
+  protected $dateFormatter;
+
+  /**
+   * Constructs a new NodeListBuilder object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
+   *   The entity storage class.
+   * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
+   *   The date formatter service.
+   */
+  public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, DateFormatterInterface $date_formatter) {
+    parent::__construct($entity_type, $storage);
+
+    $this->dateFormatter = $date_formatter;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('entity.manager')->getStorage($entity_type->id()),
+      $container->get('date.formatter')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    $header = [
+      'word' => $this->t('Word'),
+      'synonyms' => $this->t('Synonyms'),
+      'author' => [
+        'data' => $this->t('Author'),
+        'class' => [RESPONSIVE_PRIORITY_LOW],
+      ],
+      'status' => [
+        'data' => $this->t('Status'),
+        'class' => [RESPONSIVE_PRIORITY_LOW],
+      ],
+      'changed' => [
+        'data' => $this->t('Updated'),
+        'class' => [RESPONSIVE_PRIORITY_LOW],
+      ],
+    ];
+    if (\Drupal::languageManager()->isMultilingual()) {
+      $header['language_name'] = [
+        'data' => $this->t('Language'),
+        'class' => [RESPONSIVE_PRIORITY_LOW],
+      ];
+    }
+
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    $langcode = $entity->language()->getId();
+
+    /* @var $entity \Drupal\search_api_synonym\Entity\Synonym */
+    $row['word'] = $this->buildEditLink($entity->label(), $entity);
+    $row['synonyms'] = $this->buildEditLink($entity->getSynonyms(), $entity);
+    $row['author']['data'] = [
+      '#theme' => 'username',
+      '#account' => $entity->getOwner(),
+    ];
+    $row['status'] = $entity->isActive() ? $this->t('active') : $this->t('inactive');
+    $row['changed'] = $this->dateFormatter->format($entity->getChangedTime(), 'short');
+    $language_manager = \Drupal::languageManager();
+    if ($language_manager->isMultilingual()) {
+      $row['language_name'] = $language_manager->getLanguageName($langcode);
+    }
+
+    return $row + parent::buildRow($entity);
+  }
+
+  /**
+   * Build the edit link object.
+   *
+   * @param string $label
+   *   The label used in the link
+   *
+   * @param \Drupal\search_api_synonym\Entity\Synonym $entity
+   *   The synonym entity object.
+   *
+   * @return \Drupal\Core\Link
+   *   The build link object.
+   */
+  private function buildEditLink($label, $entity) {
+    return new Link(
+      $label,
+      new Url(
+        'entity.search_api_synonym.edit_form', [
+          'search_api_synonym' => $entity->id(),
+        ]
+      )
+    );
+  }
+
+}
diff --git a/web/modules/contrib/search_api_synonym/src/SynonymViewsData.php b/web/modules/contrib/search_api_synonym/src/SynonymViewsData.php
new file mode 100644 (file)
index 0000000..f33fc27
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+
+namespace Drupal\search_api_synonym;
+
+use Drupal\views\EntityViewsData;
+
+/**
+ * Provides Views data for Synonym entities.
+ */
+class SynonymViewsData extends EntityViewsData {
+  /**
+   * {@inheritdoc}
+   */
+  public function getViewsData() {
+    $data = parent::getViewsData();
+
+
+    $data['search_api_synonym_field_data']['table']['base']['help'] = $this->t('Synonyms managed by Search API Synonyms module.');
+    $data['search_api_synonym_field_data']['table']['base']['defaults']['field'] = 'word';
+    $data['search_api_synonym_field_data']['table']['wizard_id'] = 'synonym';
+
+    $data['search_api_synonym_field_data']['sid']['title'] = $this->t('Synonym ID');
+    $data['search_api_synonym_field_data']['sid']['help'] = $this->t('The unique id of the synonym entity.');
+
+    $data['search_api_synonym_field_data']['word']['title'] = $this->t('Word');
+    $data['search_api_synonym_field_data']['word']['help'] = $this->t('The word we are defining synonyms for.');
+
+    $data['search_api_synonym_field_data']['synonyms']['title'] = $this->t('Synonyms');
+    $data['search_api_synonym_field_data']['synonyms']['help'] = $this->t('The synonyms to the word.');
+
+    $data['search_api_synonym_field_data']['type']['title'] = $this->t('Type');
+    $data['search_api_synonym_field_data']['type']['help'] = $this->t('The type of synonym. Either synonym or spelling error.');
+
+    $data['search_api_synonym_field_data']['created']['title'] = $this->t('Create date');
+    $data['search_api_synonym_field_data']['created']['help'] = $this->t('Date and time of when the synonym was created.');
+
+    $data['search_api_synonym_field_data']['created_fulldata'] = [
+      'title' => $this->t('Created date'),
+      'help' => $this->t('Date in the form of CCYYMMDD.'),
+      'argument' => [
+        'field' => 'created',
+        'id' => 'date_fulldate',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['created_year_month'] = [
+      'title' => $this->t('Created year + month'),
+      'help' => $this->t('Date in the form of YYYYMM.'),
+      'argument' => [
+        'field' => 'created',
+        'id' => 'date_year_month',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['created_year'] = [
+      'title' => $this->t('Created year'),
+      'help' => $this->t('Date in the form of YYYY.'),
+      'argument' => [
+        'field' => 'created',
+        'id' => 'date_year',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['created_month'] = [
+      'title' => $this->t('Created month'),
+      'help' => $this->t('Date in the form of MM (01 - 12).'),
+      'argument' => [
+        'field' => 'created',
+        'id' => 'date_month',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['created_day'] = [
+      'title' => $this->t('Created day'),
+      'help' => $this->t('Date in the form of DD (01 - 31).'),
+      'argument' => [
+        'field' => 'created',
+        'id' => 'date_day',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['created_week'] = [
+      'title' => $this->t('Created week'),
+      'help' => $this->t('Date in the form of WW (01 - 53).'),
+      'argument' => [
+        'field' => 'created',
+        'id' => 'date_week',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['changed']['title'] = $this->t('Updated date');
+    $data['search_api_synonym_field_data']['changed']['help'] = $this->t('Date and time of when the synonym was last updated.');
+
+    $data['search_api_synonym_field_data']['changed_fulldata'] = [
+      'title' => $this->t('Changed date'),
+      'help' => $this->t('Date in the form of CCYYMMDD.'),
+      'argument' => [
+        'field' => 'changed',
+        'id' => 'date_fulldate',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['changed_year_month'] = [
+      'title' => $this->t('Changed year + month'),
+      'help' => $this->t('Date in the form of YYYYMM.'),
+      'argument' => [
+        'field' => 'changed',
+        'id' => 'date_year_month',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['changed_year'] = [
+      'title' => $this->t('Changed year'),
+      'help' => $this->t('Date in the form of YYYY.'),
+      'argument' => [
+        'field' => 'changed',
+        'id' => 'date_year',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['changed_month'] = [
+      'title' => $this->t('Changed month'),
+      'help' => $this->t('Date in the form of MM (01 - 12).'),
+      'argument' => [
+        'field' => 'changed',
+        'id' => 'date_month',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['changed_day'] = [
+      'title' => $this->t('Changed day'),
+      'help' => $this->t('Date in the form of DD (01 - 31).'),
+      'argument' => [
+        'field' => 'changed',
+        'id' => 'date_day',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['changed_week'] = [
+      'title' => $this->t('Changed week'),
+      'help' => $this->t('Date in the form of WW (01 - 53).'),
+      'argument' => [
+        'field' => 'changed',
+        'id' => 'date_week',
+      ],
+    ];
+
+    $data['search_api_synonym_field_data']['status']['title'] = $this->t('Active status');
+    $data['search_api_synonym_field_data']['status']['help'] = $this->t('Whether the synonym is active and used by search engines or is it no active.');
+    $data['search_api_synonym_field_data']['status']['filter']['label'] = $this->t('Active synonym status');
+    $data['search_api_synonym_field_data']['status']['filter']['type'] = 'yes-no';
+
+    $data['search_api_synonym_field_data']['uid']['title'] = $this->t('Author uid');
+    $data['search_api_synonym_field_data']['uid']['help'] = $this->t('If you need more fields than the uid add the synonym: author relationship');
+    $data['search_api_synonym_field_data']['uid']['relationship']['title'] = $this->t('Author');
+    $data['search_api_synonym_field_data']['uid']['relationship']['help'] = $this->t('The User ID of the synonym\'s author.');
+    $data['search_api_synonym_field_data']['uid']['relationship']['label'] = $this->t('author');
+
+    return $data;
+  }
+
+}