first import

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-08 11:40:19 +02:00
commit 1bc61b12ad
8435 changed files with 1582817 additions and 0 deletions

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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