first import
This commit is contained in:
339
sites/all/modules/search_api_autocomplete/LICENSE.txt
Normal file
339
sites/all/modules/search_api_autocomplete/LICENSE.txt
Normal file
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
65
sites/all/modules/search_api_autocomplete/README.txt
Normal file
65
sites/all/modules/search_api_autocomplete/README.txt
Normal file
@@ -0,0 +1,65 @@
|
||||
Search API autocomplete
|
||||
-----------------------
|
||||
|
||||
Adds autocomplete capabilities for Search API searches.
|
||||
|
||||
|
||||
Information for users
|
||||
---------------------
|
||||
|
||||
- Necessary server feature
|
||||
|
||||
The server on which the search will be executed has to support the
|
||||
"search_api_autocomplete" feature in order for autocompletion to work. Searches
|
||||
on other servers won't be affected by this module.
|
||||
Currently, only the Solr service class [1] is known to support this feature.
|
||||
|
||||
[1] http://drupal.org/project/search_api_solr
|
||||
|
||||
- Necessary setup
|
||||
|
||||
After having installed and enabled the module, you have to do some
|
||||
administrative steps to activate the autocomplete functionality. Autocompletion
|
||||
can be enabled and configured for each search separately.
|
||||
|
||||
To activate autocompletion for an index's searches, go to the index's
|
||||
„Autocomplete“ tab. There, you see all available searches for the index and can
|
||||
enable (and afterwards configure) autocompletion for each of them. All fulltext
|
||||
key fields on the searches should then become autocompletion fields.
|
||||
|
||||
- Supported searches
|
||||
|
||||
Currently, only search forms built by search pages or search views are
|
||||
supported directly. However, other modules can easily also use this
|
||||
functionality. See the "Information for developers" for details.
|
||||
|
||||
- Caution! -
|
||||
If your view uses contextual filters, those can generally not be inferred in
|
||||
the autocompletion function which might lead to problems of different kinds,
|
||||
including display of confidential information (if such information would be
|
||||
available without contextual filters), wrong suggestions or complete absence
|
||||
of suggestions.
|
||||
Therefore, you should create another display without contextual filters, if
|
||||
necessary, and make sure that this doesn't lead to any leaks.
|
||||
If you want to fix this in a custom way for your site, take a look at
|
||||
example_search_api_query_alter() for suggestions.
|
||||
|
||||
|
||||
Information for developers
|
||||
--------------------------
|
||||
|
||||
- Supporting autocompletion with a service class
|
||||
|
||||
To support autocompletion with a service class, the class has to support the
|
||||
"search_api_autocomplete" feature. This will necessitate the service class to
|
||||
have a getAutocompleteSuggestions() method as detailed in the interface in
|
||||
search_api_autocomplete.interface.php.
|
||||
|
||||
- Supporting autocompletion on a search form
|
||||
|
||||
If you have a search form not generated by the Search views or Search pages
|
||||
modules, you can use hook_search_api_autocomplete_types() to tell this module
|
||||
about it. For details, see the hook documentation in the
|
||||
search_api_autocomplete.api.php file, or look at the existing implementations
|
||||
in search_api_autocomplete.search_api_page.inc and
|
||||
search_api_autocomplete.search_api_views.inc.
|
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains page callbacks and related functions for the admin UI.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Form displaying an overview over all searches available for autocompletion.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index for which autocompletion searches should be configured.
|
||||
*
|
||||
* @see search_api_autocomplete_admin_overview_submit()
|
||||
* @see search_api_autocomplete_admin_overview_submit_delete()
|
||||
* @ingroup forms
|
||||
*/
|
||||
function search_api_autocomplete_admin_overview(array $form, array &$form_state, SearchApiIndex $index) {
|
||||
$form = array();
|
||||
$form_state['index'] = $index;
|
||||
$index_id = $index->machine_name;
|
||||
|
||||
$server = $index->server();
|
||||
if (!$server || !$server->supportsFeature('search_api_autocomplete')) {
|
||||
drupal_set_message(t("The server this index currently lies on doesn't support autocompletion. " .
|
||||
'To use autocompletion, you will have to move this index to a server supporting this feature.'), 'error');
|
||||
if (search_api_autocomplete_search_load_multiple(FALSE, array('index_id' => $index_id))) {
|
||||
$form['description'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => t('Delete autocompletion settings'),
|
||||
'#description' => t("If you won't use autocompletion with this index anymore, you can delete all autocompletion settings associated with it. " .
|
||||
"This will delete all autocompletion settings on this index. Settings on other indexes won't be influenced."),
|
||||
);
|
||||
$form['button'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Delete autocompletion settings'),
|
||||
'#submit' => array('search_api_autocomplete_admin_overview_submit_delete'),
|
||||
);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
$form['#tree'] = TRUE;
|
||||
$types = search_api_autocomplete_get_types();
|
||||
$searches = search_api_autocomplete_search_load_multiple(FALSE, array('index_id' => $index_id));
|
||||
$show_status = FALSE;
|
||||
foreach ($types as $type => $info) {
|
||||
if (empty($info['list searches'])) {
|
||||
continue;
|
||||
}
|
||||
$t_searches = $info['list searches']($index);
|
||||
if (empty($t_searches)) {
|
||||
$t_searches = array();
|
||||
}
|
||||
foreach ($t_searches as $id => $search) {
|
||||
if (isset($searches[$id])) {
|
||||
$types[$type]['searches'][$id] = $searches[$id];
|
||||
$show_status |= $searches[$id]->hasStatus(ENTITY_IN_CODE);
|
||||
unset($searches[$id]);
|
||||
}
|
||||
else {
|
||||
$search += array(
|
||||
'machine_name' => $id,
|
||||
'index_id' => $index_id,
|
||||
'type' => $type,
|
||||
'enabled' => 0,
|
||||
'options' => array(),
|
||||
);
|
||||
$search['options'] += array(
|
||||
'result count' => TRUE,
|
||||
'fields' => array(),
|
||||
);
|
||||
$types[$type]['searches'][$id] = entity_create('search_api_autocomplete_search', $search);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($searches as $id => $search) {
|
||||
$search->unavailable = TRUE;
|
||||
$type = isset($types[$search->type]) ? $search->type : '';
|
||||
$types[$type]['searches'][$id] = $search;
|
||||
$show_status |= $search->hasStatus(ENTITY_IN_CODE);
|
||||
}
|
||||
$base_path = 'admin/config/search/search_api/index/' . $index_id . '/autocomplete/';
|
||||
foreach ($types as $type => $info) {
|
||||
if (empty($info['searches'])) {
|
||||
continue;
|
||||
}
|
||||
if (!$type) {
|
||||
$info += array(
|
||||
'name' => t('Unavailable search types'),
|
||||
'description' => t('The modules providing these searches were disabled or uninstalled. ' .
|
||||
"If you won't use them anymore, you can delete their settings."),
|
||||
);
|
||||
}
|
||||
$form[$type] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $info['name'],
|
||||
);
|
||||
if (!empty($info['description'])) {
|
||||
$form[$type]['#description'] = '<p>' . $info['description'] . '</p>';
|
||||
}
|
||||
$form[$type]['searches']['#theme'] = 'tableselect';
|
||||
$form[$type]['searches']['#header'] = array();
|
||||
if ($show_status) {
|
||||
$form[$type]['searches']['#header']['status'] = t('Status');
|
||||
}
|
||||
$form[$type]['searches']['#header'] += array(
|
||||
'name' => t('Name'),
|
||||
'operations' => t('Operations'),
|
||||
);
|
||||
$form[$type]['searches']['#empty'] = '';
|
||||
$form[$type]['searches']['#js_select'] = TRUE;
|
||||
foreach ($info['searches'] as $id => $search) {
|
||||
$form[$type]['searches'][$id] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $search->enabled,
|
||||
'#parents' => array('searches', $id),
|
||||
);
|
||||
if (!empty($search->unavailabe)) {
|
||||
$form[$type]['searches'][$id]['#default_value'] = FALSE;
|
||||
$form[$type]['searches'][$id]['#disabled'] = TRUE;
|
||||
}
|
||||
$form_state['searches'][$id] = $search;
|
||||
$options = &$form[$type]['searches']['#options'][$id];
|
||||
if ($show_status) {
|
||||
$options['status'] = isset($search->status) ? theme('entity_status', array('status' => $search->status)) : '';;
|
||||
}
|
||||
$options['name'] = $search->name;
|
||||
$items = array();
|
||||
if (empty($search->unavailabe) && !empty($search->id)) {
|
||||
$items[] = l(t('edit'), $base_path . $id . '/edit');
|
||||
}
|
||||
if (!empty($search->status) && ($search->hasStatus(ENTITY_CUSTOM))) {
|
||||
$title = $search->hasStatus(ENTITY_IN_CODE) ? t('revert') : t('delete');
|
||||
$items[] = l($title, $base_path . $id . '/delete');
|
||||
}
|
||||
if ($items) {
|
||||
$variables = array(
|
||||
'items' => $items,
|
||||
'attributes' => array('class' => array('inline')),
|
||||
);
|
||||
$options['operations'] = theme('item_list', $variables);
|
||||
}
|
||||
else {
|
||||
$options['operations'] = '';
|
||||
}
|
||||
unset($options);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($form)) {
|
||||
$form['message']['#markup'] = '<p>' . t('There are currently no searches known for this index.') . '</p>';
|
||||
}
|
||||
else {
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save'),
|
||||
);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for search_api_autocomplete_admin_overview().
|
||||
*
|
||||
* @see search_api_autocomplete_admin_overview()
|
||||
*/
|
||||
function search_api_autocomplete_admin_overview_submit(array $form, array &$form_state) {
|
||||
$msg = t('The settings have been saved.');
|
||||
foreach ($form_state['values']['searches'] as $id => $enabled) {
|
||||
$search = $form_state['searches'][$id];
|
||||
if ($search->enabled != $enabled) {
|
||||
$change = TRUE;
|
||||
if (!empty($search->is_new)) {
|
||||
$options['query']['destination'] = $_GET['q'];
|
||||
$options['fragment'] = 'module-search_api_autocomplete';
|
||||
$vars['@perm_url'] = url('admin/people/permissions', $options);
|
||||
$msg = t('The settings have been saved. Please remember to set the <a href="@perm_url">permissions</a> for the newly enabled searches.', $vars);
|
||||
}
|
||||
$search->enabled = $enabled;
|
||||
$search->save();
|
||||
}
|
||||
}
|
||||
drupal_set_message(empty($change) ? t('No values were changed.') : $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for search_api_autocomplete_admin_overview(), when all
|
||||
* settings for the index should be deleted.
|
||||
*
|
||||
* @see search_api_autocomplete_admin_overview()
|
||||
*/
|
||||
function search_api_autocomplete_admin_overview_submit_delete(array $form, array &$form_state) {
|
||||
$index = $form_state['index'];
|
||||
$ids = array_keys(search_api_autocomplete_search_load_multiple(FALSE, array('index_id' => $index->machine_name)));
|
||||
if ($ids) {
|
||||
entity_delete_multiple('search_api_autocomplete_search', $ids);
|
||||
drupal_set_message(t('All autocompletion settings stored for this index were deleted.'));
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('There were no settings to delete.'), 'warning');
|
||||
}
|
||||
$form_state['redirect'] = 'admin/config/search/search_api/index/' . $index->machine_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form for editing the autocompletion settings for a search.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search whose settings should be edited.
|
||||
*
|
||||
* @see search_api_autocomplete_admin_search_edit_validate()
|
||||
* @see search_api_autocomplete_admin_search_edit_submit()
|
||||
* @ingroup forms
|
||||
*/
|
||||
function search_api_autocomplete_admin_search_edit(array $form, array &$form_state, SearchApiAutocompleteSearch $search) {
|
||||
drupal_set_title(t('Edit %search', array('%search' => $search->name)), PASS_THROUGH);
|
||||
|
||||
$form_state['search'] = $search;
|
||||
$form_state['type'] = $type = search_api_autocomplete_get_types($search->type);
|
||||
if (!$type) {
|
||||
drupal_set_message(t('No information about the type of this search was found.'), 'error');
|
||||
return array();
|
||||
}
|
||||
$form['#tree'] = TRUE;
|
||||
$form['enabled'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enabled'),
|
||||
'#default_value' => $search->enabled,
|
||||
);
|
||||
$form['options']['results'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Display result count estimates'),
|
||||
'#description' => t('Display the estimated number of result for each suggestion. ' .
|
||||
'This option might not have an effect for some servers or types of suggestion.'),
|
||||
'#default_value' => !empty($search->options['results']),
|
||||
);
|
||||
|
||||
// Add a list of fields to include for autocomplete searches.
|
||||
$fields = $search->index()->getFields();
|
||||
$fulltext_fields = $search->index()->getFulltextFields();
|
||||
$options = array();
|
||||
foreach ($fulltext_fields as $field) {
|
||||
$options[$field] = $fields[$field]['name'];
|
||||
}
|
||||
$form['options']['fields'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Fields to use'),
|
||||
'#description' => t('Select the fields which should be searched for matches when looking for autocompletion suggestions. Leave blank to use the same fields as the underlying search.'),
|
||||
'#options' => $options,
|
||||
'#default_value' => empty($search->options['fields']) ? array() : drupal_map_assoc($search->options['fields']),
|
||||
'#attributes' => array('class' => array('search-api-checkboxes-list')),
|
||||
);
|
||||
|
||||
$custom_form = empty($form['options']['custom']) ? array() : $form['options']['custom'];
|
||||
if (!empty($type['config form']) && function_exists($type['config form']) && is_array($custom_form = $type['config form']($custom_form, $form_state, $search))) {
|
||||
$form['options']['custom'] = $custom_form;
|
||||
}
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate callback for search_api_autocomplete_admin_search_edit().
|
||||
*
|
||||
* @see search_api_autocomplete_admin_search_edit()
|
||||
* @see search_api_autocomplete_admin_search_edit_submit()
|
||||
*/
|
||||
function search_api_autocomplete_admin_search_edit_validate(array $form, array &$form_state) {
|
||||
if (empty($form_state['type']['config form'])) {
|
||||
return;
|
||||
}
|
||||
$f = $form_state['type']['config form'] . '_validate';
|
||||
if (function_exists($f)) {
|
||||
$custom_form = empty($form['options']['custom']) ? array() : $form['options']['custom'];
|
||||
$f($form['options']['custom'], $form_state, $form_state['values']['options']['custom']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for search_api_autocomplete_admin_search_edit().
|
||||
*
|
||||
* @see search_api_autocomplete_admin_search_edit()
|
||||
* @see search_api_autocomplete_admin_search_edit_validate()
|
||||
*/
|
||||
function search_api_autocomplete_admin_search_edit_submit(array $form, array &$form_state) {
|
||||
$values = &$form_state['values'];
|
||||
if (!empty($form_state['type']['config form'])) {
|
||||
$f = $form_state['type']['config form'] . '_submit';
|
||||
if (function_exists($f)) {
|
||||
$custom_form = empty($form['options']['custom']) ? array() : $form['options']['custom'];
|
||||
$f($form['options']['custom'], $form_state, $values['options']['custom']);
|
||||
}
|
||||
}
|
||||
|
||||
$search = $form_state['search'];
|
||||
$search->enabled = $values['enabled'];
|
||||
$search->options['results'] = $values['options']['results'];
|
||||
$search->options['fields'] = array_keys(array_filter($values['options']['fields']));
|
||||
if (isset($values['options']['custom'])) {
|
||||
// Take care of custom options that aren't changed in the config form.
|
||||
$old = empty($search->options['custom']) ? array() : $search->options['custom'];
|
||||
$search->options['custom'] = $values['options']['custom'] + $old;
|
||||
}
|
||||
|
||||
$search->save();
|
||||
drupal_set_message(t('The autocompletion settings for the search have been saved.'));
|
||||
$form_state['redirect'] = 'admin/config/search/search_api/index/' . $search->index_id . '/autocomplete';
|
||||
}
|
||||
|
||||
/**
|
||||
* Form for deleting the autocompletion settings of a search.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search whose settings should be deleted.
|
||||
*
|
||||
* @see search_api_autocomplete_admin_search_delete_submit()
|
||||
* @ingroup forms
|
||||
*/
|
||||
function search_api_autocomplete_admin_search_delete(array $form, array &$form_state, SearchApiAutocompleteSearch $search) {
|
||||
$form_state['search'] = $search;
|
||||
return confirm_form(
|
||||
$form,
|
||||
t('Do you really want to delete the autocompletion settings for search %search?', array('%search' => $search->name)),
|
||||
'admin/config/search/search_api/index/' . $search->index_id . '/autocomplete'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for search_api_autocomplete_admin_search_delete().
|
||||
*
|
||||
* @see search_api_autocomplete_admin_search_delete()
|
||||
*/
|
||||
function search_api_autocomplete_admin_search_delete_submit(array $form, array &$form_state) {
|
||||
$form_state['search']->delete();
|
||||
drupal_set_message(t('The autocompletion settings for the search have been deleted.'));
|
||||
$form_state['redirect'] = 'admin/config/search/search_api/index/' . $form_state['search']->index_id . '/autocomplete';
|
||||
}
|
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided by the Search API autocomplete module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Inform the module about types of searches for which autocompletion is available.
|
||||
*
|
||||
* The implementation has to take care of altering the search form accordingly
|
||||
* itself. This should be done by loading the appropriate
|
||||
* SearchApiAutocompleteSearch entity and calling its alterElement() method with
|
||||
* the textfield element to which autocompletion should be added. See
|
||||
* example_form_example_search_form_alter() for an example.
|
||||
*
|
||||
* @return array
|
||||
* An array with search types as the keys, mapped to arrays containing the
|
||||
* following entries:
|
||||
* - name: The category name for searches of this type.
|
||||
* - description: A short description of this type (may contain HTML).
|
||||
* - list searches: Callback function that returns a list of all known
|
||||
* searches of this type for a given index. See
|
||||
* example_list_autocomplete_searches() for the expected function signature.
|
||||
* - create query: Callback function to create a search query for a search of
|
||||
* this type and some user input. See example_create_autocomplete_query()
|
||||
* for the expected function signature.
|
||||
* - config form: (optional) Callback function for adding a form for
|
||||
* type-specific options to a search's autocomplete settings form. See
|
||||
* example_autocomplete_config_form() for the expected function signature.
|
||||
* This function name will also be the base for custom validation and submit
|
||||
* callbacks, with "_validate" or "_submit" appended, respectively.
|
||||
*
|
||||
* @see example_list_autocomplete_searches()
|
||||
* @see example_create_autocomplete_query()
|
||||
* @see example_autocomplete_config_form()
|
||||
* @see example_autocomplete_config_form_validate()
|
||||
* @see example_autocomplete_config_form_submit()
|
||||
* @see example_form_example_search_form_alter()
|
||||
*/
|
||||
function hook_search_api_autocomplete_types() {
|
||||
$types['example'] = array(
|
||||
'name' => t('Example searches'),
|
||||
'description' => t('Searches provided by the <em>Example</em> module.'),
|
||||
'list searches' => 'example_list_autocomplete_searches',
|
||||
'create query' => 'example_create_autocomplete_query',
|
||||
'config form' => 'example_autocomplete_config_form',
|
||||
);
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts on searches being loaded from the database.
|
||||
*
|
||||
* This hook is invoked during search loading, which is handled by
|
||||
* entity_load(), via the EntityCRUDController.
|
||||
*
|
||||
* @param array $searches
|
||||
* An array of search entities being loaded, keyed by machine name.
|
||||
*
|
||||
* @see hook_entity_load()
|
||||
*/
|
||||
function hook_search_api_autocomplete_search_load(array $searches) {
|
||||
$result = db_query('SELECT pid, foo FROM {mytable} WHERE pid IN(:ids)', array(':ids' => array_keys($entities)));
|
||||
foreach ($result as $record) {
|
||||
$entities[$record->pid]->foo = $record->foo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds when a search is inserted.
|
||||
*
|
||||
* This hook is invoked after the search is inserted into the database.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search that is being inserted.
|
||||
*
|
||||
* @see hook_entity_insert()
|
||||
*/
|
||||
function hook_search_api_autocomplete_search_insert(SearchApiAutocompleteSearch $search) {
|
||||
db_insert('mytable')
|
||||
->fields(array(
|
||||
'id' => entity_id('search_api_autocomplete_search', $search),
|
||||
'extra' => print_r($search, TRUE),
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts on a search being inserted or updated.
|
||||
*
|
||||
* This hook is invoked before the search is saved to the database.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search that is being inserted or updated.
|
||||
*
|
||||
* @see hook_entity_presave()
|
||||
*/
|
||||
function hook_search_api_autocomplete_search_presave(SearchApiAutocompleteSearch $search) {
|
||||
$search->name = 'foo';
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to a search being updated.
|
||||
*
|
||||
* This hook is invoked after the search has been updated in the database.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search that is being updated.
|
||||
*
|
||||
* @see hook_entity_update()
|
||||
*/
|
||||
function hook_search_api_autocomplete_search_update(SearchApiAutocompleteSearch $search) {
|
||||
db_update('mytable')
|
||||
->fields(array('extra' => print_r($search, TRUE)))
|
||||
->condition('id', entity_id('search_api_autocomplete_search', $search))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to search deletion.
|
||||
*
|
||||
* This hook is invoked after the search has been removed from the database.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search that is being deleted.
|
||||
*
|
||||
* @see hook_entity_delete()
|
||||
*/
|
||||
function hook_search_api_autocomplete_search_delete(SearchApiAutocompleteSearch $search) {
|
||||
db_delete('mytable')
|
||||
->condition('pid', entity_id('search_api_autocomplete_search', $search))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define default search configurations.
|
||||
*
|
||||
* @return
|
||||
* An array of default searches, keyed by machine names.
|
||||
*
|
||||
* @see hook_default_search_api_autocomplete_search_alter()
|
||||
*/
|
||||
function hook_default_search_api_autocomplete_search() {
|
||||
$defaults['main'] = entity_create('search_api_autocomplete_search', array(
|
||||
// …
|
||||
));
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter default search configurations.
|
||||
*
|
||||
* @param array $defaults
|
||||
* An array of default searches, keyed by machine names.
|
||||
*
|
||||
* @see hook_default_search_api_autocomplete_search()
|
||||
*/
|
||||
function hook_default_search_api_autocomplete_search_alter(array &$defaults) {
|
||||
$defaults['main']->name = 'custom name';
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a list of searches for the given index.
|
||||
*
|
||||
* All searches returned must have a unique and well-defined machine name. The
|
||||
* implementing module for this type is responsible for being able to map a
|
||||
* specific search always to the same distinct machine name.
|
||||
* Since the machine names have to be globally unique, they should be prefixed
|
||||
* with the search type / module name.
|
||||
*
|
||||
* Also, name and machine name have to respect the length constraints from
|
||||
* search_api_autocomplete_schema().
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index whose searches should be returned.
|
||||
*
|
||||
* @return array
|
||||
* An array of searches, keyed by their machine name. The values are arrays
|
||||
* with the following keys:
|
||||
* - name: A human-readable name for this search.
|
||||
* - options: (optional) An array of options to use for this search.
|
||||
* Type-specific options should go into the "custom" nested key in these
|
||||
* options.
|
||||
*/
|
||||
function example_list_autocomplete_searches(SearchApiIndex $index) {
|
||||
$ret = array();
|
||||
$result = db_query('SELECT name, machine_name, extra FROM {example_searches} WHERE index_id = :id', array($index->machine_name));
|
||||
foreach ($result as $row) {
|
||||
$id = 'example_' . $row->machine_name;
|
||||
$ret[$id] = array(
|
||||
'name' => $row->name,
|
||||
);
|
||||
if ($row->extra) {
|
||||
$ret[$id]['options']['custom']['extra'] = $row->extra;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the query that would be issued for the given search for the complete keys.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search for which to create the query.
|
||||
* @param $complete
|
||||
* A string containing the complete search keys.
|
||||
* @param $incomplete
|
||||
* A string containing the incomplete last search key.
|
||||
*
|
||||
* @return SearchApiQueryInterface
|
||||
* The query that would normally be executed when only $complete was entered
|
||||
* as the search keys for the given search.
|
||||
*/
|
||||
function example_create_autocomplete_query(SearchApiAutocompleteSearch $search, $complete, $incomplete) {
|
||||
$query = search_api_query($search->index_id);
|
||||
if ($complete) {
|
||||
$query->keys($complete);
|
||||
}
|
||||
if (!empty($search->options['custom']['extra'])) {
|
||||
list($f, $v) = explode('=', $search->options['custom']['extra'], 2);
|
||||
$query->condition($f, $v);
|
||||
}
|
||||
if (!empty($search->options['custom']['user_filters'])) {
|
||||
foreach (explode("\n", $search->options['custom']['user_filters']) as $line) {
|
||||
list($f, $v) = explode('=', $line, 2);
|
||||
$query->condition($f, $v);
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form callback for configuring autocompletion for searches of the "example" type.
|
||||
*
|
||||
* The returned form array will be nested into an outer form, so you should not
|
||||
* rely on knowing the array structure (like the elements' parents) and should
|
||||
* not set "#tree" to FALSE for any element.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search whose config form should be presented.
|
||||
*
|
||||
* @see example_autocomplete_config_form_validate()
|
||||
* @see example_autocomplete_config_form_submit()
|
||||
*/
|
||||
function example_autocomplete_config_form(array $form, array &$form_state, SearchApiAutocompleteSearch $search) {
|
||||
$form['user_filters'] = array(
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('Custom filters'),
|
||||
'#description' => t('Enter additional filters set on the autocompletion search. ' .
|
||||
'Write one filter on each line, the field and its value separated by an equals sign (=).'),
|
||||
'#default_value' => empty($search->options['custom']['user_filters']) ? '' : $search->options['custom']['user_filters'],
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation callback for example_autocomplete_config_form().
|
||||
*
|
||||
* The configured SearchApiAutocompleteSearch object can be found in
|
||||
* $form_state['search'].
|
||||
*
|
||||
* @param array $form
|
||||
* The type-specific config form, as returned by the "config form" callback.
|
||||
* @param array $form_state
|
||||
* The complete form state of the form.
|
||||
* @param array $values
|
||||
* The portion of $form_state['values'] that corresponds to the type-specific
|
||||
* config form.
|
||||
*
|
||||
* @see example_autocomplete_config_form()
|
||||
* @see example_autocomplete_config_form_submit()
|
||||
*/
|
||||
function example_autocomplete_config_form_validate(array $form, array &$form_state, array &$values) {
|
||||
$f = array();
|
||||
foreach (explode("\n", $values['user_filters']) as $line) {
|
||||
if (preg_match('/^\s*([a-z0-9_:]+)\s*=\s*(.*\S)\s*$/i', $line, $m)) {
|
||||
$f[] = $m[1] . '=' . $m[2];
|
||||
}
|
||||
else {
|
||||
form_error($form, t('Write one filter on each line, the field and its value separated by an equals sign (=).'));
|
||||
}
|
||||
}
|
||||
$values['user_filters'] = $f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for example_autocomplete_config_form().
|
||||
*
|
||||
* After calling this function, the value of $values (if set) will automatically
|
||||
* be written to $search->options['custom']. This function just has to take care
|
||||
* of sanitizing the data as necessary. Also, values already present in
|
||||
* $search->options['custom'], but not in the form, will automatically be
|
||||
* protected from being overwritten.
|
||||
*
|
||||
* The configured SearchApiAutocompleteSearch object can be found in
|
||||
* $form_state['search'].
|
||||
*
|
||||
* @param array $form
|
||||
* The type-specific config form, as returned by the "config form" callback.
|
||||
* @param array $form_state
|
||||
* The complete form state of the form.
|
||||
* @param array $values
|
||||
* The portion of $form_state['values'] that corresponds to the type-specific
|
||||
* config form.
|
||||
*
|
||||
* @see example_autocomplete_config_form()
|
||||
* @see example_autocomplete_config_form_validate()
|
||||
*/
|
||||
function example_autocomplete_config_form_submit(array $form, array &$form_state, array &$values) {
|
||||
$values['user_filters'] = implode("\n", $values['user_filters']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*
|
||||
* Alters the example_search_form form to add autocompletion, if enabled by the
|
||||
* user.
|
||||
*/
|
||||
function example_form_example_search_form_alter(array &$form, array &$form_state) {
|
||||
// Compute the machine name that would be generated for this search in the
|
||||
// 'list searches' callback.
|
||||
$search_id = 'example_' . $form_state['search id'];
|
||||
// Look up the corresponding autocompletion configuration, if it exists.
|
||||
$search = search_api_autocomplete_search_load($search_id);
|
||||
// Check whether autocompletion for the search is enabled.
|
||||
// (This is also checked automatically later, so could be skipped here.)
|
||||
if (!empty($search->enabled)) {
|
||||
// If it is, pass the textfield for the search keywords to the
|
||||
// alterElement() method of the search object.
|
||||
$search->alterElement($form['keys']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_search_api_query_alter().
|
||||
*
|
||||
* This example hook implementation shows how a custom module could fix the
|
||||
* problem with Views contextual filters in a specific context.
|
||||
*/
|
||||
function example_search_api_query_alter(SearchApiQueryInterface $query) {
|
||||
// Check whether this is an appropriate automcomplete query.
|
||||
if ($query->getOption('search id') === 'search_api_autocomplete:example') {
|
||||
// If it is, add the necessary filters that would otherwise be added by
|
||||
// contextual filters. This is easy if the argument comes from the global
|
||||
// user or a similar global source. If the argument comes from the URL or
|
||||
// some other page-specific source, however, you would need to somehow pass
|
||||
// that information along to this function.
|
||||
global $user;
|
||||
$query->condition('group', $user->data['group']);
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
.search-api-autocomplete-suggestion {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-api-autocomplete-suggestion .autocomplete-suggestion-note {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.search-api-autocomplete-suggestion .autocomplete-user-input {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-api-autocomplete-suggestion .autocomplete-suggestion-results {
|
||||
position: absolute;
|
||||
right: 0.2em;
|
||||
}
|
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the SearchApiAutocompleteSearch class.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Class describing the settings for a certain search for which autocompletion
|
||||
* is available.
|
||||
*/
|
||||
class SearchApiAutocompleteSearch extends Entity {
|
||||
|
||||
// Entity properties, loaded from the database:
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $machine_name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $index_id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
public $enabled;
|
||||
|
||||
/**
|
||||
* An array of options for this search, containing any of the following:
|
||||
* - results: Boolean indicating whether to also list the estimated number of
|
||||
* results for each suggestion (if possible).
|
||||
* - fields: Array containing the fulltext fields to use for autocompletion.
|
||||
* - custom: An array of type-specific settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $options = array();
|
||||
|
||||
// Inferred properties, for caching:
|
||||
|
||||
/**
|
||||
* @var SearchApiIndex
|
||||
*/
|
||||
protected $index;
|
||||
|
||||
/**
|
||||
* @var SearchApiServer
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $values
|
||||
* The entity properties.
|
||||
*/
|
||||
public function __construct(array $values = array()) {
|
||||
parent::__construct($values, 'search_api_autocomplete_search');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchApiIndex
|
||||
* The index this search belongs to.
|
||||
*/
|
||||
public function index() {
|
||||
if (!isset($this->index)) {
|
||||
$this->index = search_api_index_load($this->index_id);
|
||||
if (!$this->index) {
|
||||
$this->index = FALSE;
|
||||
}
|
||||
}
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchApiServer
|
||||
* The server this search would at the moment be executed on.
|
||||
*/
|
||||
public function server() {
|
||||
if (!isset($this->server)) {
|
||||
if (!$this->index() || !$this->index()->server) {
|
||||
$this->server = FALSE;
|
||||
}
|
||||
else {
|
||||
$this->server = $this->index()->server();
|
||||
if (!$this->server) {
|
||||
$this->server = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
* TRUE if the server this search is currently associated with supports the
|
||||
* autocompletion feature; FALSE otherwise.
|
||||
*/
|
||||
public function supportsAutocompletion() {
|
||||
return $this->server() && $this->server()->supportsFeature('search_api_autocomplete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for altering a textfield form element to use autocompletion.
|
||||
*/
|
||||
public function alterElement(array &$element, array $fields = array()) {
|
||||
if (search_api_autocomplete_access($this)) {
|
||||
$fields_string = $fields ? implode(' ', $fields) : ' ';
|
||||
$element['#attached']['css'][] = drupal_get_path('module', 'search_api_autocomplete') . '/search_api_autocomplete.css';
|
||||
$element['#autocomplete_path'] = 'search_api_autocomplete/' . $this->machine_name . '/' . $fields_string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a string with search keywords into two parts.
|
||||
*
|
||||
* The first part consists of all words the user has typed completely, the
|
||||
* second one contains the beginning of the last, possibly incomplete word.
|
||||
*
|
||||
* @return array
|
||||
* An array with $keys split into exactly two parts, both of which may be
|
||||
* empty.
|
||||
*/
|
||||
public function splitKeys($keys) {
|
||||
$keys = ltrim($keys);
|
||||
// If there is whitespace or a quote on the right, all words have been
|
||||
// completed.
|
||||
if (rtrim($keys, " \t\n\r\0\x0B\"") != $keys) {
|
||||
return array(rtrim($keys), '');
|
||||
}
|
||||
if (preg_match('/^(.*?)\s*"?([\S]*)$/', $keys, $m)) {
|
||||
return array($m[1], $m[2]);
|
||||
}
|
||||
return array('', $keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the query that would be issued for this search for the complete keys.
|
||||
*
|
||||
* @param $complete
|
||||
* A string containing the complete search keys.
|
||||
* @param $incomplete
|
||||
* A string containing the incomplete last search key.
|
||||
*
|
||||
* @return SearchApiQueryInterface
|
||||
* The query that would normally be executed when only $complete was entered
|
||||
* as the search keys for this search.
|
||||
*/
|
||||
public function getQuery($complete, $incomplete) {
|
||||
$info = search_api_autocomplete_get_types($this->type);
|
||||
if (empty($info['create query'])) {
|
||||
return NULL;
|
||||
}
|
||||
$query = $info['create query']($this, $complete, $incomplete);
|
||||
if ($complete && !$query->getKeys()) {
|
||||
$query->keys($complete);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
name = Search API autocomplete
|
||||
description = "Adds autocomplete functionality to most searches on servers supportings this feature."
|
||||
dependencies[] = search_api
|
||||
core = 7.x
|
||||
package = Search
|
||||
|
||||
files[] = search_api_autocomplete.entity.php
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-01-04
|
||||
version = "7.x-1.0-beta2+11-dev"
|
||||
core = "7.x"
|
||||
project = "search_api_autocomplete"
|
||||
datestamp = "1357263969"
|
||||
|
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the Search API autocomplete module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function search_api_autocomplete_schema() {
|
||||
$schema['search_api_autocomplete_search'] = array(
|
||||
'description' => 'Stores autocomplete settings for searches on Search API indexes.',
|
||||
'fields' => array(
|
||||
'id' => array(
|
||||
'description' => 'The primary identifier for a search.',
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'machine_name' => array(
|
||||
'description' => 'A string identifier for a search.',
|
||||
'type' => 'varchar',
|
||||
'length' => 100,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => 'A human-readable name for the search.',
|
||||
'type' => 'varchar',
|
||||
'length' => 50,
|
||||
),
|
||||
'index_id' => array(
|
||||
'description' => 'The {search_api_index}.machine_name this search belongs to.',
|
||||
'type' => 'varchar',
|
||||
'length' => 50,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'type' => array(
|
||||
'description' => 'The type of search, usually a module name.',
|
||||
'type' => 'varchar',
|
||||
'length' => 50,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'enabled' => array(
|
||||
'description' => 'A flag indicating whether autocompletion for this search is enabled.',
|
||||
'type' => 'int',
|
||||
'size' => 'tiny',
|
||||
'not null' => TRUE,
|
||||
'default' => 1,
|
||||
),
|
||||
'options' => array(
|
||||
'description' => 'The options used to configure autocompletion for this search.',
|
||||
'type' => 'text',
|
||||
'serialize' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'status' => array(
|
||||
'description' => 'The exportable status of the entity.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0x01,
|
||||
'size' => 'tiny',
|
||||
),
|
||||
'module' => array(
|
||||
'description' => 'The name of the providing module if the entity has been defined in code.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
),
|
||||
'primary key' => array('id'),
|
||||
'unique keys' => array(
|
||||
'machine_name' => array('machine_name'),
|
||||
),
|
||||
'indexes' => array(
|
||||
'enabled' => array('enabled'),
|
||||
),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update permissions to the new system with search-specific permissions.
|
||||
*/
|
||||
function search_api_autocomplete_update_7101() {
|
||||
$roles = db_select('role_permission', 'r')
|
||||
->fields('r', array('rid'))
|
||||
->condition('permission', 'use search_api_autocomplete')
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$searches = db_select('search_api_autocomplete_search', 's')
|
||||
->fields('s', array('machine_name'))
|
||||
->execute()
|
||||
->fetchCol();
|
||||
try {
|
||||
$tx = db_transaction();
|
||||
|
||||
db_delete('role_permission')
|
||||
->condition('permission', 'use search_api_autocomplete')
|
||||
->execute();
|
||||
if ($roles && $searches) {
|
||||
$insert = db_insert('role_permission')
|
||||
->fields(array('rid', 'permission', 'module'));
|
||||
foreach ($roles as $rid) {
|
||||
foreach ($searches as $id) {
|
||||
$insert->values(array(
|
||||
'rid' => $rid,
|
||||
'permission' => 'use search_api_autocomplete for ' . $id,
|
||||
'module' => 'search_api_autocomplete',
|
||||
));
|
||||
}
|
||||
}
|
||||
$insert->execute();
|
||||
}
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$tx->rollback();
|
||||
throw new DrupalUpdateException(t('A database error occurred during update: @msg', array('@msg' => $e->getMessage())));
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the SearchApiAutocompleteInterface.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Interface describing the method a service class has to add to support autocompletion.
|
||||
*
|
||||
* Please note that this interface is purely documentational. You shouldn't, and
|
||||
* can't, implement it explicitly.
|
||||
*/
|
||||
interface SearchApiAutocompleteInterface extends SearchApiServiceInterface {
|
||||
|
||||
/**
|
||||
* Get autocompletion suggestions for some user input.
|
||||
*
|
||||
* For example, when given the user input "teach us", with "us" being
|
||||
* considered incomplete, the following might be returned:
|
||||
* @code
|
||||
* array(
|
||||
* array(
|
||||
* 'prefix' => t('Did you mean:'),
|
||||
* 'user_input' => 'reach us',
|
||||
* ),
|
||||
* array(
|
||||
* 'user_input' => 'teach us',
|
||||
* 'suggestion_suffix' => 'ers',
|
||||
* ),
|
||||
* array(
|
||||
* 'user_input' => 'teach us',
|
||||
* 'suggestion_suffix' => ' swimming',
|
||||
* ),
|
||||
* 'teach users swimming',
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @param SearchApiQueryInterface $query
|
||||
* A query representing the completed user input so far.
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* An object containing details about the search the user is on, and
|
||||
* settings for the autocompletion. See the class documentation for details.
|
||||
* Especially $search->options should be checked for settings, like whether
|
||||
* to try and estimate result counts for returned suggestions.
|
||||
* @param string $incomplete_key
|
||||
* The start of another fulltext keyword for the search, which should be
|
||||
* completed. Might be empty, in which case all user input up to now was
|
||||
* considered completed. Then, additional keywords for the search could be
|
||||
* suggested.
|
||||
* @param string $user_input
|
||||
* The complete user input for the fulltext search keywords so far.
|
||||
*
|
||||
* @return array
|
||||
* An array of suggestion. Each suggestion is either a simple string
|
||||
* containing the whole suggested keywords, or an array containing the
|
||||
* following keys:
|
||||
* - prefix: For special suggestions, some kind of prefix describing them.
|
||||
* - suggestion_prefix: A suggested prefix for the entered input.
|
||||
* - user_input: The input entered by the user. Defaults to $user_input.
|
||||
* - suggestion_suffix: A suggested suffix for the entered input.
|
||||
* - results: If available, the estimated number of results for these keys.
|
||||
* One of "suggestion_prefix" and "suggestion_suffix" has to be present, all
|
||||
* other keys are optional. The search keys inserted for the suggestion will
|
||||
* be a direct concatenation (no spaces in between) of "suggestion_prefix",
|
||||
* "user_input" and "suggestion_suffix".
|
||||
*/
|
||||
public function getAutocompleteSuggestions(SearchApiQueryInterface $query, SearchApiAutocompleteSearch $search, $incomplete_key, $user_input);
|
||||
|
||||
}
|
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Adds autocomplete capabilities for Search API searches.
|
||||
*/
|
||||
|
||||
// Include the files with the module-specific implementations.
|
||||
require_once('search_api_autocomplete.search_api_page.inc');
|
||||
require_once('search_api_autocomplete.search_api_views.inc');
|
||||
|
||||
/**
|
||||
* Implements hook_search_api_autocomplete_types().
|
||||
*
|
||||
* Adds search types for search pages and views. The actual implementations lie
|
||||
* in the above include files.
|
||||
*/
|
||||
function search_api_autocomplete_search_api_autocomplete_types() {
|
||||
$types = array();
|
||||
if (module_exists('search_api_page')) {
|
||||
$types['search_api_page'] = array(
|
||||
'name' => t('Search pages'),
|
||||
'description' => t('Searches provided by the <em>Search pages</em> module.'),
|
||||
'list searches' => 'search_api_autocomplete_pages_searches',
|
||||
'create query' => 'search_api_autocomplete_pages_query',
|
||||
);
|
||||
}
|
||||
if (module_exists('search_api_views')) {
|
||||
$types['search_api_views'] = array(
|
||||
'name' => t('Search views'),
|
||||
'description' => t('Searches provided by the <em>Search views</em> module.'),
|
||||
'list searches' => 'search_api_autocomplete_views_searches',
|
||||
'create query' => 'search_api_autocomplete_views_query',
|
||||
'config form' => 'search_api_autocomplete_views_config_form',
|
||||
);
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function search_api_autocomplete_menu() {
|
||||
// Autocompletion path
|
||||
$items['search_api_autocomplete/%search_api_autocomplete_search'] = array(
|
||||
'title' => 'Search API autocomplete',
|
||||
'page callback' => 'search_api_autocomplete_autocomplete',
|
||||
'page arguments' => array(1),
|
||||
'access callback' => 'search_api_autocomplete_access',
|
||||
'access arguments' => array(1),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'search_api_autocomplete.pages.inc',
|
||||
);
|
||||
|
||||
// Admin UI
|
||||
$items['admin/config/search/search_api/index/%search_api_index/autocomplete'] = array(
|
||||
'title' => 'Autocomplete',
|
||||
'description' => 'Add autocompletion to searches for this index.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('search_api_autocomplete_admin_overview', 5),
|
||||
'access arguments' => array('administer search_api'),
|
||||
'weight' => -1,
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'context' => MENU_CONTEXT_INLINE | MENU_CONTEXT_PAGE,
|
||||
'file' => 'search_api_autocomplete.admin.inc',
|
||||
);
|
||||
$items['admin/config/search/search_api/index/%/autocomplete/%search_api_autocomplete_search/edit'] = array(
|
||||
'title' => 'Edit autocompletion settings',
|
||||
'description' => 'Edit the autocompletion settings of a search.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('search_api_autocomplete_admin_search_edit', 7),
|
||||
'access arguments' => array('administer search_api'),
|
||||
'file' => 'search_api_autocomplete.admin.inc',
|
||||
);
|
||||
$items['admin/config/search/search_api/index/%/autocomplete/%search_api_autocomplete_search/delete'] = array(
|
||||
'title' => 'Delete autocompletion settings',
|
||||
'description' => 'Delete the autocompletion settings of a search.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('search_api_autocomplete_admin_search_delete', 7),
|
||||
'access arguments' => array('administer search_api'),
|
||||
'file' => 'search_api_autocomplete.admin.inc',
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function search_api_autocomplete_theme() {
|
||||
$themes['search_api_autocomplete_suggestion'] = array(
|
||||
'variables' => array(
|
||||
'prefix' => NULL,
|
||||
'suggestion_prefix' => '',
|
||||
'user_input' => '',
|
||||
'suggestion_suffix' => '',
|
||||
'results' => NULL,
|
||||
),
|
||||
'file' => 'search_api_autocomplete.pages.inc',
|
||||
);
|
||||
|
||||
return $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_info().
|
||||
*/
|
||||
function search_api_autocomplete_entity_info() {
|
||||
$info['search_api_autocomplete_search'] = array(
|
||||
'label' => t('Autocomplete search'),
|
||||
'controller class' => 'EntityAPIControllerExportable',
|
||||
'entity class' => 'SearchApiAutocompleteSearch',
|
||||
'base table' => 'search_api_autocomplete_search',
|
||||
'uri callback' => 'search_api_autocomplete_search_url',
|
||||
'module' => 'search_api_autocomplete',
|
||||
'exportable' => TRUE,
|
||||
'entity keys' => array(
|
||||
'id' => 'id',
|
||||
'name' => 'machine_name',
|
||||
'label' => 'name',
|
||||
),
|
||||
);
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_permission().
|
||||
*/
|
||||
function search_api_autocomplete_permission() {
|
||||
$perms = array();
|
||||
foreach (search_api_autocomplete_search_load_multiple() as $id => $search) {
|
||||
$perms['use search_api_autocomplete for ' . $id] = array(
|
||||
'title' => t('Use autocomplete for the %search search', array('%search' => $search->name)),
|
||||
);
|
||||
}
|
||||
return $perms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback for search autocompletion.
|
||||
*
|
||||
* @param $search
|
||||
* The autocomplete search which is being accessed.
|
||||
* @param $account
|
||||
* (optional) The account to check, if not given use currently logged in user.
|
||||
*
|
||||
* @return
|
||||
* TRUE, if the search is enabled, supports autocompletion and the user has
|
||||
* the necessary permission. FALSE otherwise.
|
||||
*/
|
||||
function search_api_autocomplete_access(SearchApiAutocompleteSearch $search, $account = NULL) {
|
||||
return $search->enabled && user_access('use search_api_autocomplete for ' . $search->machine_name, $account) && $search->supportsAutocompletion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_search_api_index_delete().
|
||||
*/
|
||||
function search_api_autocomplete_search_api_index_delete(SearchApiIndex $index) {
|
||||
if (!$index->hasStatus(ENTITY_IN_CODE)) {
|
||||
$ids = db_query('SELECT id FROM {search_api_autocomplete_search} WHERE index_id = :id',
|
||||
array(':id' => $index->machine_name))->fetchCol();
|
||||
if ($ids) {
|
||||
entity_delete_multiple('search_api_autocomplete_search', $ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about all search types, or a specific one.
|
||||
*
|
||||
* @param $type
|
||||
* (optional) The name of a type.
|
||||
*
|
||||
* @return
|
||||
* If $type was not given, an array containing information about all search
|
||||
* types. Otherwise, either information about the specified type, or NULL if
|
||||
* the type is not known.
|
||||
*
|
||||
* @see hook_search_api_autocomplete_types()
|
||||
*/
|
||||
function search_api_autocomplete_get_types($type = NULL) {
|
||||
$types = &drupal_static(__FUNCTION__);
|
||||
if (!isset($types)) {
|
||||
$types = module_invoke_all('search_api_autocomplete_types');
|
||||
}
|
||||
if (isset($type)) {
|
||||
return isset($types[$type]) ? $types[$type] : NULL;
|
||||
}
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an autocomplete search entity.
|
||||
*
|
||||
* @param $id
|
||||
* Either the ID or machine name of an autocomplete search.
|
||||
* @param $reset
|
||||
* Whether to reset the internal cache.
|
||||
*
|
||||
* @return SearchApiAutocompleteSearch
|
||||
* The specified autocomplete search entity, or FALSE if it doesn't exist.
|
||||
*/
|
||||
function search_api_autocomplete_search_load($id, $reset = FALSE) {
|
||||
$ret = search_api_autocomplete_search_load_multiple(array($id), array(), $reset);
|
||||
return $ret ? reset($ret) : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads autocomplete search entities.
|
||||
*
|
||||
* @param $ids
|
||||
* An array of IDs or machine names, or FALSE to load all searches.
|
||||
* @param $conditions
|
||||
* An associative array of conditions on the {search_api_autocomplete_search}
|
||||
* table.
|
||||
* @param $reset
|
||||
* Whether to reset the internal cache.
|
||||
*
|
||||
* @return array
|
||||
* An array of all autocomplete search entities that meet the criteria.
|
||||
*
|
||||
* @see entity_load()
|
||||
*/
|
||||
function search_api_autocomplete_search_load_multiple($ids = FALSE, array $conditions = array(), $reset = FALSE) {
|
||||
return entity_load_multiple_by_name('search_api_autocomplete_search', $ids, $conditions, $reset);
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains page callbacks and theme functions for the frontend UI.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Page callback for getting autocomplete suggestions.
|
||||
*/
|
||||
function search_api_autocomplete_autocomplete(SearchApiAutocompleteSearch $search, $fields, $keys = '') {
|
||||
$ret = array();
|
||||
if ($search->supportsAutocompletion()) {
|
||||
$server = $search->server();
|
||||
list($complete, $incomplete) = $search->splitKeys($keys);
|
||||
$keys = preg_replace('/\s+/', ' ', trim($keys));
|
||||
$query = $search->getQuery($complete, $incomplete);
|
||||
if ($query) {
|
||||
// @todo Maybe make range configurable?
|
||||
$query->range(0, 10);
|
||||
$query->setOption('search id', 'search_api_autocomplete:' . $search->machine_name);
|
||||
if (!empty($search->options['fields'])) {
|
||||
$query->fields($search->options['fields']);
|
||||
}
|
||||
elseif (trim($fields)) {
|
||||
$fields = explode(' ', $fields);
|
||||
$query->fields($fields);
|
||||
}
|
||||
$query->preExecute();
|
||||
$suggestions = $server->getAutocompleteSuggestions($query, $search, $incomplete, $keys);
|
||||
if ($suggestions) {
|
||||
foreach ($suggestions as $suggestion) {
|
||||
// Convert suggestion strings into an array.
|
||||
if (is_string($suggestion)) {
|
||||
$pos = strpos($suggestion, $keys);
|
||||
if ($pos === FALSE) {
|
||||
$suggestion = array(
|
||||
'user_input' => '',
|
||||
'suggestion_suffix' => $suggestion,
|
||||
);
|
||||
}
|
||||
else {
|
||||
$suggestion = array(
|
||||
'suggestion_prefix' => substr($suggestion, 0, $pos),
|
||||
'user_input' => $keys,
|
||||
'suggestion_suffix' => substr($suggestion, $pos + strlen($keys)),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Add defaults.
|
||||
$suggestion += array(
|
||||
'prefix' => NULL,
|
||||
'suggestion_prefix' => '',
|
||||
'user_input' => $keys,
|
||||
'suggestion_suffix' => '',
|
||||
'results' => NULL,
|
||||
);
|
||||
if (empty($search->options['results'])) {
|
||||
unset($suggestion['results']);
|
||||
}
|
||||
$key = $suggestion['suggestion_prefix'] . $suggestion['user_input'] . $suggestion['suggestion_suffix'];
|
||||
if (!isset($ret[$key])) {
|
||||
$ret[$key] = theme('search_api_autocomplete_suggestion', $suggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drupal_json_output($ret);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - prefix: For special suggestions, some kind of prefix describing them.
|
||||
* - suggestion_prefix: A suggested prefix for the entered input.
|
||||
* - user_input: The input entered by the user.
|
||||
* - suggestion_suffix: A suggested suffix for the entered input.
|
||||
* - results: If available, the estimated number of results for these keys.
|
||||
*/
|
||||
function theme_search_api_autocomplete_suggestion(array $variables) {
|
||||
extract($variables);
|
||||
$output = '';
|
||||
if ($prefix) {
|
||||
$output .= "<span class=\"autocomplete-suggestion-note\">$prefix</span> ";
|
||||
}
|
||||
if ($suggestion_prefix) {
|
||||
$output .= "<span class=\"autocomplete-suggestion-prefix\">$suggestion_prefix</span>";
|
||||
}
|
||||
if ($user_input) {
|
||||
$output .= "<span class=\"autocomplete-user-input\">$user_input</span>";
|
||||
}
|
||||
if ($suggestion_suffix) {
|
||||
$output .= "<span class=\"autocomplete-suggestion-suffix\">$suggestion_suffix</span>";
|
||||
}
|
||||
if ($results) {
|
||||
$output .= " <span class=\"autocomplete-suggestion-results\">$results</span>";
|
||||
}
|
||||
|
||||
return "<div class=\"search-api-autocomplete-suggestion\">$output</div>";
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains code for integrating with the "Search pages" module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*
|
||||
* Adds autocompletion to the keywords field on search pages, if enabled by the
|
||||
* user.
|
||||
*/
|
||||
function search_api_autocomplete_form_search_api_page_search_form_alter(array &$form, array &$form_state) {
|
||||
if (isset($form['form'])) {
|
||||
$form = &$form['form'];
|
||||
}
|
||||
$id = 'search_api_page_' . $form['id']['#value'];
|
||||
$search = search_api_autocomplete_search_load($id);
|
||||
if ($search && $search->enabled) {
|
||||
$search->alterElement($form['keys_' . $form['id']['#value']]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of search pages for the given index.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index whose searches should be returned.
|
||||
*
|
||||
* @return array
|
||||
* An array of searches, keyed by their machine name. The values are arrays
|
||||
* with the following keys:
|
||||
* - name: A human-readable name for this search.
|
||||
* - options: (optional) An array of options to use for this search.
|
||||
* Type-specific options should go into the "custom" nested key in these
|
||||
* options.
|
||||
*/
|
||||
function search_api_autocomplete_pages_searches(SearchApiIndex $index) {
|
||||
$ret = array();
|
||||
foreach (search_api_page_load_multiple(FALSE, array('index_id' => $index->machine_name)) as $page) {
|
||||
$id = 'search_api_page_' . $page->id;
|
||||
$ret[$id]['name'] = $page->name;
|
||||
$ret[$id]['options']['custom']['page_id'] = $page->id;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the query that would be issued for the given search for the complete keys.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search for which to create the query.
|
||||
* @param $complete
|
||||
* A string containing the complete search keys.
|
||||
* @param $incomplete
|
||||
* A string containing the incomplete last search key.
|
||||
*
|
||||
* @return SearchApiQueryInterface
|
||||
* The query that would normally be executed when only $complete was entered
|
||||
* as the search keys for the given search.
|
||||
*/
|
||||
function search_api_autocomplete_pages_query(SearchApiAutocompleteSearch $search, $complete, $incomplete) {
|
||||
$page = search_api_page_load($search->options['custom']['page_id']);
|
||||
// Copied from search_api_page_search_execute().
|
||||
$query = search_api_query($page->index_id, array('parse mode' => $page->options['mode']))
|
||||
->keys($complete);
|
||||
if (!empty($page->options['fields'])) {
|
||||
$query->fields($page->options['fields']);
|
||||
}
|
||||
return $query;
|
||||
}
|
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains code for integrating with the "Search views" module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*
|
||||
* Adds autocompletion to input fields for fulltext keywords on views with
|
||||
* exposed filters.
|
||||
*/
|
||||
function search_api_autocomplete_form_views_exposed_form_alter(array &$form, array &$form_state) {
|
||||
$view = $form_state['view'];
|
||||
if (substr($view->base_table, 0, 17) != 'search_api_index_') {
|
||||
return;
|
||||
}
|
||||
$search_id = 'search_api_views_' . $view->name;
|
||||
$search = search_api_autocomplete_search_load($search_id);
|
||||
if (!$search || !search_api_autocomplete_access($search)) {
|
||||
return;
|
||||
}
|
||||
$index_id = substr($view->base_table, 17);
|
||||
$index = search_api_index_load($index_id);
|
||||
if (empty($index->options['fields'])) {
|
||||
return;
|
||||
}
|
||||
$fields = $index->getFulltextFields(TRUE);
|
||||
// Add the "Search: Fulltext search" filter as another text field.
|
||||
$fields[] = 'search_api_views_fulltext';
|
||||
// We need the _entity_views_field_identifier() function to translate Search
|
||||
// API field names into Views identifiers.
|
||||
module_load_include('views.inc', 'entity', 'views/entity');
|
||||
foreach ($fields as $search_field) {
|
||||
$field = _entity_views_field_identifier($search_field, array());
|
||||
if (!empty($view->filter[$field]->options['expose']['identifier'])) {
|
||||
$key = $view->filter[$field]->options['expose']['identifier'];
|
||||
if (isset($form[$key]) && $form[$key]['#type'] == 'textfield') {
|
||||
if ($field == 'search_api_views_fulltext') {
|
||||
$fields = $view->filter[$field]->options['fields'];
|
||||
}
|
||||
else {
|
||||
$fields = array($search_field);
|
||||
}
|
||||
$search->alterElement($form[$key], $fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of search views for the given index.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index whose searches should be returned.
|
||||
*
|
||||
* @return array
|
||||
* An array of searches, keyed by their machine name. The values are arrays
|
||||
* with the following keys:
|
||||
* - name: A human-readable name for this search.
|
||||
* - options: (optional) An array of options to use for this search.
|
||||
* Type-specific options should go into the "custom" nested key in these
|
||||
* options.
|
||||
*/
|
||||
function search_api_autocomplete_views_searches(SearchApiIndex $index) {
|
||||
$ret = array();
|
||||
$base_table = 'search_api_index_' . $index->machine_name;
|
||||
foreach (views_get_all_views() as $name => $view) {
|
||||
if ($view->base_table === $base_table) {
|
||||
// @todo Check whether there is an exposed fulltext filter
|
||||
$ret['search_api_views_' . $name] = array(
|
||||
'name' => !empty($view->human_name) ? $view->human_name : $name,
|
||||
);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the query that would be issued for the given search for the complete keys.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search for which to create the query.
|
||||
* @param $complete
|
||||
* A string containing the complete search keys.
|
||||
* @param $incomplete
|
||||
* A string containing the incomplete last search key.
|
||||
*
|
||||
* @return SearchApiQueryInterface
|
||||
* The query that would normally be executed when only $complete was entered
|
||||
* as the search keys for the given search.
|
||||
*/
|
||||
function search_api_autocomplete_views_query(SearchApiAutocompleteSearch $search, $complete, $incomplete) {
|
||||
$views_id = substr($search->machine_name, 17);
|
||||
$view = views_get_view($views_id);
|
||||
$view->set_display(isset($search->options['custom']['display']) ? $search->options['custom']['display'] : NULL);
|
||||
$view->pre_execute();
|
||||
$view->build();
|
||||
$query = $view->query->getSearchApiQuery();
|
||||
$query->keys($complete);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form callback for a Views-specific autocomplete configuration form.
|
||||
*
|
||||
* @param SearchApiAutocompleteSearch $search
|
||||
* The search being configured.
|
||||
*
|
||||
* @see example_autocomplete_config_form()
|
||||
* @see search_api_autocomplete_views_config_form_submit()
|
||||
*/
|
||||
function search_api_autocomplete_views_config_form(array $form, array &$form_state, SearchApiAutocompleteSearch $search) {
|
||||
$views_id = substr($search->machine_name, 17);
|
||||
$view = views_get_view($views_id);
|
||||
$options = array();
|
||||
foreach ($view->display as $id => $display) {
|
||||
$options[$id] = $display->display_title;
|
||||
}
|
||||
$form['display'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Views display'),
|
||||
'#description' => t('Please select the Views display whose settings should be used for autocomplete queries.<br />' .
|
||||
"<strong>Note:</strong> Autocompletion doesn't work well with contextual filters. Please see the <a href='@readme_url'>README.txt</a> file for details.",
|
||||
array('@readme_url' => url(drupal_get_path('module', 'search_api_autocomplete') . '/README.txt'))),
|
||||
'#options' => $options,
|
||||
'#default_value' => isset($search->options['custom']['display']) ? $search->options['custom']['display'] : 'default',
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for search_api_autocomplete_views_config_form().
|
||||
*
|
||||
* @see example_autocomplete_config_form_submit()
|
||||
* @see search_api_autocomplete_views_config_form()
|
||||
*/
|
||||
function search_api_autocomplete_views_config_form_submit(array $form, array &$form_state, array &$values) {
|
||||
$views_id = substr($form_state['search']->machine_name, 17);
|
||||
$view = views_get_view($views_id);
|
||||
$view->set_display($values['display']);
|
||||
$view->pre_execute();
|
||||
if ($view->argument) {
|
||||
drupal_set_message(t('You have selected a display with contextual filters. This can lead to various problems. Please see the <a href="@readme_url">README.txt</a> file for details.',
|
||||
array('@readme_url' => url(drupal_get_path('module', 'search_api_autocomplete') . '/README.txt'))), 'warning');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user