--- /dev/null
+ 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.
--- /dev/null
+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
--- /dev/null
+{
+ "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": { }
+}
--- /dev/null
+cron:
+ plugin: solr
+ interval: 86400
+ type: all
+ filter: none
+ separate_files: 1
+ export_if_changed: 1
--- /dev/null
+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: { }
--- /dev/null
+# 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.'
--- /dev/null
+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".'
+
--- /dev/null
+"word";"synonym";"type"
+"cms";"content management system";"synonym"
+"dropal";"drupal";"spelling_error"
+"cq5,sitecore,sharepoint";"drupal";"spelling_error"
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+#
+# Apache Solr - Synonyms.txt example file.
+#
+
+# Synonym
+cms, content management system
+
+# Spelling errors
+dropal => drupal
+cq5, sitecore, sharepoint => drupal
--- /dev/null
+<?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);
+}
--- /dev/null
+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
--- /dev/null
+<?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();
+ }
+}
--- /dev/null
+entity.search_api_synonym.add_form:
+ route_name: 'entity.search_api_synonym.add_form'
+ title: 'Add synonym'
+ appears_on:
+ - entity.search_api_synonym.collection
+
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+<?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]);
+ }
+
+}
--- /dev/null
+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.'
--- /dev/null
+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'
--- /dev/null
+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 }
--- /dev/null
+<?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;
+
+}
--- /dev/null
+<?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;
+
+}
--- /dev/null
+<?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);
+ }
+
+}
--- /dev/null
+<?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;
+ }
+
+}
--- /dev/null
+<?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 [];
+ }
+
+}
--- /dev/null
+<?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);
+
+}
--- /dev/null
+<?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';
+ }
+
+}
--- /dev/null
+<?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 {
+
+}
--- /dev/null
+<?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());
+ }
+
+}
--- /dev/null
+<?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, []);
+ }
+
+}
--- /dev/null
+<?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);
+ }
+
+}
--- /dev/null
+<?php
+
+namespace Drupal\search_api_synonym;
+
+use Drupal\Component\Plugin\Exception;
+
+/**
+ * ImportException extending Generic Plugin exception class.
+ */
+class ImportException extends \Exception {
+
+}
--- /dev/null
+<?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 [];
+ }
+
+}
--- /dev/null
+<?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();
+
+}
--- /dev/null
+<?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;
+ }
+ }
+
+}
--- /dev/null
+<?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);
+ }
+
+}
--- /dev/null
+<?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;
+ }
+
+}
--- /dev/null
+<?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'];
+ }
+
+}
--- /dev/null
+<?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'];
+ }
+
+}
--- /dev/null
+<?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'];
+ }
+
+}
--- /dev/null
+<?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);
+
+}
--- /dev/null
+<?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(),
+ ]
+ )
+ );
+ }
+
+}
--- /dev/null
+<?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;
+ }
+
+}