From 5e63575d944f83de9efca9f9ab5b9f233efb7db2 Mon Sep 17 00:00:00 2001 From: bachy Date: Sun, 22 Jul 2012 16:25:09 +0200 Subject: [PATCH 1/5] first import from dev Signed-off-by: bachy --- LICENSE.txt | 339 +++++++++++++++++++++++++++ search_api_page.admin.inc | 477 ++++++++++++++++++++++++++++++++++++++ search_api_page.css | 12 + search_api_page.info | 15 ++ search_api_page.install | 193 +++++++++++++++ search_api_page.module | 467 +++++++++++++++++++++++++++++++++++++ search_api_page.pages.inc | 248 ++++++++++++++++++++ 7 files changed, 1751 insertions(+) create mode 100644 LICENSE.txt create mode 100644 search_api_page.admin.inc create mode 100644 search_api_page.css create mode 100644 search_api_page.info create mode 100644 search_api_page.install create mode 100755 search_api_page.module create mode 100644 search_api_page.pages.inc diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/search_api_page.admin.inc b/search_api_page.admin.inc new file mode 100644 index 00000000..013672ee --- /dev/null +++ b/search_api_page.admin.inc @@ -0,0 +1,477 @@ + 'image', + '#path' => $base_path . 'enabled.png', + '#alt' => t('enabled'), + '#title' => t('enabled'), + ); + $t_enabled['class'] = array('search-api-status'); + $t_disabled['data'] = array( + '#theme' => 'image', + '#path' => $base_path . 'disabled.png', + '#alt' => t('disabled'), + '#title' => t('disabled'), + ); + $t_disabled['class'] = array('search-api-status'); + $t_enable = t('enable'); + $t_disable = t('disable'); + $t_edit = t('edit'); + $t_delete = t('delete'); + $pre = 'admin/config/search/search_api/page/'; + $pre_index = 'admin/config/search/search_api/index/'; + $enable = '/enable'; + $disable = '/disable'; + $edit = '/edit'; + $delete = '/delete'; + + foreach (search_api_page_load_multiple() as $page) { + $url = $pre . $page->machine_name; + $index = search_api_index_load($page->index_id); + $rows[] = array( + $page->enabled ? $t_enabled : $t_disabled, + theme('entity_status', array('status' => $page->status)), + l($page->name, $page->path), + l($page->path, $page->path), + l($index->name, $pre_index . $index->machine_name), + l($t_edit, $url . $edit), + ); + } + + return array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => t('There are no search pages defined yet.'), + ); +} + +/** + * Displays a form for adding a search page. + */ +function search_api_page_admin_add(array $form, array &$form_state) { + $form = array(); + if (empty($form_state['step_one'])) { + $indexes = search_api_index_load_multiple(FALSE); + if (!$indexes) { + drupal_set_message(t('There are no searches indexes which can be searched. Please create an index first.', array('@url' => url('admin/config/search/search_api/add_index'))), 'warning'); + return array(); + } + $index_options = array(); + foreach ($indexes as $index) { + if ($index->enabled) { + $index_options[$index->machine_name] = $index->name; + } + } + foreach ($indexes as $index) { + if (!$index->enabled) { + $index_options[$index->machine_name] = $index->name . ' (' . t('disabled') . ')'; + } + } + + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Search name'), + '#maxlength' => 50, + '#required' => TRUE, + ); + $form['machine_name'] = array( + '#type' => 'machine_name', + '#maxlength' => 51, + '#machine_name' => array( + 'exists' => 'search_api_index_load', + ), + ); + $form['index_id'] = array( + '#type' => 'select', + '#title' => t('Index'), + '#description' => t('Select the index this page should search. This cannot be changed later.'), + '#options' => $index_options, + '#required' => TRUE, + ); + $form['enabled'] = array( + '#type' => 'checkbox', + '#title' => t('Enabled'), + '#description' => t('This will only take effect if the selected index is also enabled.'), + '#default_value' => TRUE, + ); + $form['description'] = array( + '#type' => 'textarea', + '#title' => t('Search description'), + ); + $form['path'] = array( + '#type' => 'textfield', + '#title' => t('Path'), + '#description' => t('Set the path under which the search page will be accessible, when enabled.'), + '#maxlength' => 50, + '#required' => TRUE, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Create page'), + ); + + return $form; + } + + $index = search_api_index_load($form_state['step_one']['index_id']); + + if ($index->enabled) { + $modes = array(); + foreach ($index->query()->parseModes() as $mode => $info) { + $modes[$mode] = $info['name']; + } + } + else { + $modes = array(); + $modes['direct'] = t('Direct query'); + $modes['single'] = t('Single term'); + $modes['terms'] = t('Multiple terms'); + } + $form['mode'] = array( + '#type' => 'select', + '#title' => t('Query type'), + '#description' => t('Select how the query will be parsed.'), + '#options' => $modes, + '#default_value' => 'terms', + ); + + $fields = array(); + $index_fields = $index->getFields(); + foreach ($index->getFulltextFields() as $name) { + $fields[$name] = $index_fields[$name]['name']; + } + if (count($fields) > 1) { + $form['fields'] = array( + '#type' => 'select', + '#title' => t('Searched fields'), + '#description' => t('Select the fields that will be searched. If no fields are selected, all available fulltext fields will be searched.'), + '#options' => $fields, + '#size' => min(4, count($fields)), + '#multiple' => TRUE, + ); + } + else { + $form['fields'] = array( + '#type' => 'value', + '#value' => array(), + ); + } + + $form['per_page'] = array( + '#type' => 'select', + '#title' => t('Results per page'), + '#description' => t('Select how many items will be displayed on one page of the search result.'), + '#options' => drupal_map_assoc(array(5, 10, 20, 30, 40, 50, 60, 80, 100)), + '#default_value' => 10, + ); + + $form['get_per_page'] = array( + '#type' => 'checkbox', + '#title' => t('Allow GET override'), + '#description' => t('Allow the „Results per page“ setting to be overridden from the URL, using the "per_page" GET parameter.
' . + 'Example: http://example.com/search_results?per_page=7'), + '#default_value' => TRUE, + ); + + $view_modes = array( + 'search_api_page_result' => t('Themed as search results'), + ); + // For entities, we also add all entity view modes. + if ($entity_info = entity_get_info($index->item_type)) { + foreach ($entity_info['view modes'] as $mode => $mode_info) { + $view_modes[$mode] = $mode_info['label']; + } + } + if (count($view_modes) > 1) { + $form['view_mode'] = array( + '#type' => 'select', + '#title' => t('View mode'), + '#options' => $view_modes, + '#description' => t('Select how search results will be displayed.'), + '#size' => 1, + '#default_value' => 'search_api_page_result', + ); + } + else { + $form['view_mode'] = array( + '#type' => 'value', + '#value' => reset($view_modes), + ); + } + + if (module_exists('search_api_spellcheck') && ($server = $index->server()) && $server->supportsFeature('search_api_spellcheck')) { + $form['search_api_spellcheck'] = array( + '#type' => 'checkbox', + '#title' => t('Enable spell check'), + '#description' => t('Display "Did you mean … ?" above search results.'), + '#default_value' => TRUE, + ); + } + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Create page'), + ); + + return $form; +} + +/** + * Validation callback for search_api_page_admin_add(). + */ +function search_api_page_admin_add_validate(array $form, array &$form_state) { + if (empty($form_state['step_one'])) { + $form_state['values']['path'] = drupal_strtolower(trim($form_state['values']['path'])); + if (search_api_page_load_multiple(FALSE, array('path' => $form_state['values']['path']))) { + form_set_error('path', t('The entered path is already in use. Please enter a unique path.')); + } + } +} + +/** + * Submit callback for search_api_page_admin_add(). + */ +function search_api_page_admin_add_submit(array $form, array &$form_state) { + form_state_values_clean($form_state); + if (empty($form_state['step_one'])) { + $form_state['step_one'] = $form_state['values']; + $form_state['rebuild'] = TRUE; + return; + } + $values = $form_state['step_one']; + $values['options'] = $form_state['values']; + search_api_page_insert($values); + drupal_set_message(t('The search page was successfully created.')); + $form_state['redirect'] = 'admin/config/search/search_api/page'; +} + +/** + * Displays a form for editing or deleting a search page. + */ +function search_api_page_admin_edit(array $form, array &$form_state, Entity $page) { + $index = search_api_index_load($page->index_id); + $form_state['page'] = $page; + + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Search name'), + '#maxlength' => 50, + '#required' => TRUE, + '#default_value' => $page->name, + ); + $form['enabled'] = array( + '#type' => 'checkbox', + '#title' => t('Enabled'), + '#description' => t('This will only take effect if the selected index is also enabled.'), + '#default_value' => $page->enabled, + '#disabled' => !$index->enabled, + ); + $form['description'] = array( + '#type' => 'textarea', + '#title' => t('Search description'), + '#default_value' => $page->description, + ); + $form['index'] = array( + '#type' => 'item', + '#title' => t('Index'), + '#description' => l($index->name, 'admin/config/search/search_api/index/' . $index->machine_name), + ); + $form['path'] = array( + '#type' => 'textfield', + '#title' => t('Path'), + '#description' => t('Set the path under which the search page will be accessible, when enabled.'), + '#maxlength' => 50, + '#default_value' => $page->path, + ); + + if ($index->enabled) { + $modes = array(); + foreach ($index->query()->parseModes() as $mode => $info) { + $modes[$mode] = $info['name']; + } + } + else { + $modes = array(); + $modes['direct'] = array( + 'name' => t('Direct query'), + 'description' => t("Don't parse the query, just hand it to the search server unaltered. " . + "Might fail if the query contains syntax errors in regard to the specific server's query syntax."), + ); + $modes['single'] = array( + 'name' => t('Single term'), + 'description' => t('The query is interpreted as a single keyword, maybe containing spaces or special characters.'), + ); + $modes['terms'] = array( + 'name' => t('Multiple terms'), + 'description' => t('The query is interpreted as multiple keywords seperated by spaces. ' . + 'Keywords containing spaces may be "quoted". Quoted keywords must still be seperated by spaces.'), + ); + } + $form['options']['#tree'] = TRUE; + $form['options']['mode'] = array( + '#type' => 'select', + '#title' => t('Query type'), + '#description' => t('Select how the query will be parsed.'), + '#options' => $modes, + '#default_value' => $page->options['mode'], + ); + + $fields = array(); + $index_fields = $index->getFields(); + foreach ($index->getFulltextFields() as $name) { + $fields[$name] = $index_fields[$name]['name']; + } + if (count($fields) > 1) { + $form['options']['fields'] = array( + '#type' => 'select', + '#title' => t('Searched fields'), + '#description' => t('Select the fields that will be searched. If no fields are selected, all available fulltext fields will be searched.'), + '#options' => $fields, + '#size' => min(4, count($fields)), + '#multiple' => TRUE, + '#default_value' => $page->options['fields'], + ); + } + else { + $form['options']['fields'] = array( + '#type' => 'value', + '#value' => array(), + ); + } + + $form['options']['per_page'] = array( + '#type' => 'select', + '#title' => t('Results per page'), + '#description' => t('Select how many items will be displayed on one page of the search result.'), + '#options' => drupal_map_assoc(array(5, 10, 20, 30, 40, 50, 60, 80, 100)), + '#default_value' => $page->options['per_page'], + ); + + $form['options']['get_per_page'] = array( + '#type' => 'checkbox', + '#title' => t('Allow GET override'), + '#description' => t('Allow the „Results per page“ setting to be overridden from the URL, using the "per_page" GET parameter.
' . + 'Example: http://example.com/search_results?per_page=7'), + '#default_value' => !empty($page->options['get_per_page']), + ); + + $view_modes = array( + 'search_api_page_result' => t('Themed as search results'), + ); + // For entities, we also add all entity view modes. + if ($entity_info = entity_get_info($index->item_type)) { + foreach ($entity_info['view modes'] as $mode => $mode_info) { + $view_modes[$mode] = $mode_info['label']; + } + } + if (count($view_modes) > 1) { + $form['options']['view_mode'] = array( + '#type' => 'select', + '#title' => t('View mode'), + '#options' => $view_modes, + '#description' => t('Select how search results will be displayed.'), + '#size' => 1, + '#default_value' => isset($page->options['view_mode']) ? $page->options['view_mode'] : 'search_api_page_result', + ); + } + else { + $form['options']['view_mode'] = array( + '#type' => 'value', + '#value' => reset($view_modes), + ); + } + + if (module_exists('search_api_spellcheck') && ($server = $index->server()) && $server->supportsFeature('search_api_spellcheck')) { + $form['options']['search_api_spellcheck'] = array( + '#type' => 'checkbox', + '#title' => t('Enable spell check'), + '#description' => t('Display "Did you mean … ?" above search results.'), + '#default_value' => !empty($page->options['search_api_spellcheck']), + ); + } + + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save changes'), + ); + + if ($page->hasStatus(ENTITY_OVERRIDDEN)) { + $form['actions']['revert'] = array( + '#type' => 'fieldset', + '#title' => t('Revert search page'), + '#description' => t('This will revert all settings on this search page back to the defaults. This action cannot be undone.'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + 'revert' => array( + '#type' => 'submit', + '#value' => t('Revert search page'), + ), + ); + } + elseif ($page->hasStatus(ENTITY_CUSTOM)) { + $form['actions']['delete'] = array( + '#type' => 'fieldset', + '#title' => t('Delete search page'), + '#description' => t('This will delete the search page along with all of its settings.'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + 'delete' => array( + '#type' => 'submit', + '#value' => t('Delete search page'), + ), + ); + } + + + return $form; +} + +/** + * Validation callback for search_api_page_admin_edit(). + */ +function search_api_page_admin_edit_validate(array $form, array &$form_state) { + if ($form_state['values']['op'] == t('Save changes')) { + $form_state['values']['path'] = drupal_strtolower(trim($form_state['values']['path'])); + $pages = search_api_page_load_multiple(FALSE, array('path' => $form_state['values']['path'])); + if (count($pages) > 1 || (($page = array_shift($pages)) && $page->machine_name != $form_state['page']->machine_name)) { + form_set_error('path', t('The entered path is already in use. Please enter a unique path.')); + } + } +} + +/** + * Submit callback for search_api_page_admin_edit(). + */ +function search_api_page_admin_edit_submit(array $form, array &$form_state) { + $op = $form_state['values']['op']; + form_state_values_clean($form_state); + $form_state['redirect'] = 'admin/config/search/search_api/page'; + + if ($op == t('Delete search page') || $op == t('Revert search page')) { + $form_state['page']->delete(); + + if ($op == t('Revert search page')) { + drupal_set_message(t('The search page was successfully reverted.')); + } + else { + drupal_set_message(t('The search page was successfully deleted.')); + } + + return; + } + search_api_page_edit($form_state['page']->machine_name, $form_state['values']); + drupal_set_message(t('The changes were successfully saved.')); +} diff --git a/search_api_page.css b/search_api_page.css new file mode 100644 index 00000000..4cab7882 --- /dev/null +++ b/search_api_page.css @@ -0,0 +1,12 @@ + +.search-performance { + font-size: 80%; +} + +ol.search-results { + display: block; +} + +li.search-result { + display: block; +} diff --git a/search_api_page.info b/search_api_page.info new file mode 100644 index 00000000..9ff9d839 --- /dev/null +++ b/search_api_page.info @@ -0,0 +1,15 @@ + +name = Search pages +description = "Create search pages using Search API indexes." +dependencies[] = search_api +core = 7.x +package = Search + +configure = admin/config/search/search_api/page + +; Information added by drupal.org packaging script on 2012-06-26 +version = "7.x-1.0-beta2+5-dev" +core = "7.x" +project = "search_api_page" +datestamp = "1340671392" + diff --git a/search_api_page.install b/search_api_page.install new file mode 100644 index 00000000..0356d260 --- /dev/null +++ b/search_api_page.install @@ -0,0 +1,193 @@ + '', + 'fields' => array( + 'id' => array( + 'description' => 'The primary identifier for a search page.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'index_id' => array( + 'description' => 'The {search_api_index}.machine_name this page will search on.', + 'type' => 'varchar', + 'length' => 50, + 'not null' => TRUE, + ), + 'path' => array( + 'description' => 'The path at which this search page can be viewed.', + 'type' => 'varchar', + 'length' => 50, + 'not null' => TRUE, + ), + 'name' => array( + 'description' => 'The displayed name for a search page.', + 'type' => 'varchar', + 'length' => 50, + 'not null' => TRUE, + ), + 'machine_name' => array( + 'description' => 'The machine name for a search page.', + 'type' => 'varchar', + 'length' => 50, + 'not null' => TRUE, + ), + 'description' => array( + 'description' => 'The displayed description for a search page.', + 'type' => 'text', + 'not null' => FALSE, + ), + 'options' => array( + 'description' => 'The options used to configure the search page.', + 'type' => 'text', + 'serialize' => TRUE, + 'not null' => TRUE, + ), + 'enabled' => array( + 'description' => 'A flag indicating whether the search page is enabled.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 1, + ), + '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, + ), + ), + 'indexes' => array( + 'enabled' => array('enabled'), + 'index_id' => array('index_id'), + ), + 'unique' => array( + 'path' => array('path'), + 'machine_name' => array('machine_name'), + ), + 'primary key' => array('id'), + ); + + return $schema; +} + +/** + * Implements hook_update_dependencies(). + */ +function search_api_page_update_dependencies() { + // This update should run after primary IDs have been changed to machine names in the framework. + $dependencies['search_api_page'][7101] = array( + 'search_api' => 7102, + ); + return $dependencies; +} + +/** + * Make {search_api_page}.index_id the index' machine name. + */ +function search_api_page_update_7101() { + // Update of search_api_page: + db_drop_index('search_api_page', 'index_id'); + $spec = array( + 'description' => 'The {search_api_index}.machine_name this page will search on.', + 'type' => 'varchar', + 'length' => 50, + 'not null' => TRUE, + ); + db_change_field('search_api_page', 'index_id', 'index_id', $spec); + db_add_index('search_api_page', 'index_id', array('index_id')); + + foreach (db_query('SELECT id, machine_name FROM {search_api_index}') as $index) { + // We explicitly forbid numeric machine names, therefore we don't have to worry about conflicts here. + db_update('search_api_page') + ->fields(array( + 'index_id' => $index->machine_name, + )) + ->condition('index_id', $index->id) + ->execute(); + } +} + +/** + * Add a {search_api_page}.machine_name column. + */ +function search_api_page_update_7102() { + $tx = db_transaction(); + try { + // Add the machine_name field, along with its unique key index. + $spec = array( + 'description' => 'The machine name for a search page.', + 'type' => 'varchar', + 'length' => 50, + 'not null' => TRUE, + 'default' => '', + ); + db_add_field('search_api_page', 'machine_name', $spec); + + $names = array(); + foreach (db_query('SELECT id, name FROM {search_api_page}')->fetchAllKeyed() as $id => $name) { + $base = $name = drupal_strtolower(preg_replace('/[^a-z0-9]+/i', '_', $name)); + $i = 0; + while (isset($names[$name])) { + $name = $base . '_' . ++$i; + if (drupal_strlen($name) > 50) { + $suffix_len = drupal_strlen('_' . $i); + $base = drupal_substr($base, 0, 50 - $suffix_len); + $name = $base . '_' . ++$i; + } + } + $names[$name] = TRUE; + db_update('search_api_page') + ->fields(array( + 'machine_name' => $name, + )) + ->condition('id', $id) + ->execute(); + } + + db_add_unique_key('search_api_page', 'machine_name', array('machine_name')); + + // Add the status field. + $spec = array( + 'description' => 'The exportable status of the entity.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0x01, + 'size' => 'tiny', + ); + db_add_field('search_api_page', 'status', $spec); + + // Add the module field. + $spec = array( + 'description' => 'The name of the providing module if the entity has been defined in code.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ); + db_add_field('search_api_page', 'module', $spec); + } + catch (Exception $e) { + $tx->rollback(); + try { + db_drop_field('search_api_page', 'machine_name'); + db_drop_field('search_api_page', 'status'); + db_drop_field('search_api_page', 'module'); + } + catch (Exception $e1) { + // Ignore. + } + throw new DrupalUpdateException(t('An exception occurred during the update: @msg.', array('@msg' => $e->getMessage()))); + } +} diff --git a/search_api_page.module b/search_api_page.module new file mode 100755 index 00000000..a9a00cb0 --- /dev/null +++ b/search_api_page.module @@ -0,0 +1,467 @@ + 'Search pages', + 'description' => 'Create and configure search pages.', + 'page callback' => 'search_api_page_admin_overview', + 'access arguments' => array('administer search_api'), + 'file' => 'search_api_page.admin.inc', + 'type' => MENU_LOCAL_TASK, + ); + $items[$pre . '/add'] = array( + 'title' => 'Add search page', + 'description' => 'Add a new search page.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('search_api_page_admin_add'), + 'access arguments' => array('administer search_api'), + 'file' => 'search_api_page.admin.inc', + 'type' => MENU_LOCAL_ACTION, + ); + $items[$pre . '/%search_api_page'] = array( + 'title' => 'Edit search page', + 'description' => 'Configure or delete a search page.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('search_api_page_admin_edit', 5), + 'access arguments' => array('administer search_api'), + 'file' => 'search_api_page.admin.inc', + ); + + // During uninstallation, this would lead to a fatal error otherwise. + if (module_exists('search_api_page')) { + foreach (search_api_page_load_multiple(FALSE, array('enabled' => TRUE)) as $page) { + $items[$page->path] = array( + 'title' => $page->name, + 'description' => $page->description ? $page->description : '', + 'page callback' => 'search_api_page_view', + 'page arguments' => array((string) $page->machine_name), + 'access arguments' => array('access search_api_page'), + 'file' => 'search_api_page.pages.inc', + 'type' => MENU_SUGGESTED_ITEM, + ); + } + } + + return $items; +} + +/** + * Implements hook_theme(). + */ +function search_api_page_theme() { + $themes['search_api_page_results'] = array( + 'variables' => array( + 'index' => NULL, + 'results' => array('result count' => 0), + 'items' => array(), + 'view_mode' => 'search_api_page_result', + 'keys' => '', + ), + 'file' => 'search_api_page.pages.inc', + ); + $themes['search_api_page_result'] = array( + 'variables' => array( + 'index' => NULL, + 'result' => NULL, + 'item' => NULL, + 'keys' => '', + ), + 'file' => 'search_api_page.pages.inc', + ); + + return $themes; +} + +/** + * Implements hook_permission(). + */ +function search_api_page_permission() { + return array( + 'access search_api_page' => array( + 'title' => t('Access search pages'), + 'description' => t('Execute searches using the Search pages module.'), + ), + ); +} + +/** + * Implements hook_block_info(). + */ +function search_api_page_block_info() { + $blocks = array(); + foreach (search_api_page_load_multiple(FALSE, array('enabled' => TRUE)) as $page) { + $blocks[$page->machine_name] = array( + 'info' => t('Search block: !name', array('!name' => $page->name)), + ); + } + return $blocks; +} + +/** + * Implements hook_block_view(). + */ +function search_api_page_block_view($delta) { + $page = search_api_page_load($delta); + if ($page) { + $block = array(); + $block['subject'] = t($page->name); + $block['content'] = drupal_get_form('search_api_page_search_form_' . $page->machine_name, $page, NULL, TRUE); + return $block; + } +} + +/** + * Implements hook_forms(). + */ +function search_api_page_forms($form_id, $args) { + $forms = array(); + foreach (search_api_page_load_multiple(FALSE, array('enabled' => TRUE)) as $page) { + $forms['search_api_page_search_form_' . $page->machine_name] = array( + 'callback' => 'search_api_page_search_form', + 'callback arguments' => array(), + ); + } + return $forms; +} + +/** + * Implements hook_entity_info(). + */ +function search_api_page_entity_info() { + $info['search_api_page'] = array( + 'label' => t('Search page'), + 'controller class' => 'EntityAPIControllerExportable', + 'metadata controller class' => FALSE, + 'entity class' => 'Entity', + 'base table' => 'search_api_page', + 'uri callback' => 'search_api_page_url', + 'module' => 'search_api_page', + 'exportable' => TRUE, + 'entity keys' => array( + 'id' => 'id', + 'label' => 'name', + 'name' => 'machine_name', + ), + ); + + return $info; +} + +/** + * Implements hook_entity_property_info(). + */ +function search_api_page_entity_property_info() { + $info['search_api_page']['properties'] = array( + 'id' => array( + 'label' => t('ID'), + 'type' => 'integer', + 'description' => t('The primary identifier for a search page.'), + 'schema field' => 'id', + 'validation callback' => 'entity_metadata_validate_integer_positive', + ), + 'index_id' => array( + 'label' => t('Index ID'), + 'type' => 'token', + 'description' => t('The machine name of the index this search page uses.'), + 'schema field' => 'index_id', + ), + 'index' => array( + 'label' => t('Index'), + 'type' => 'search_api_index', + 'description' => t('The index this search page uses.'), + 'getter callback' => 'search_api_page_get_index', + ), + 'name' => array( + 'label' => t('Name'), + 'type' => 'text', + 'description' => t('The displayed name for a search page.'), + 'schema field' => 'name', + 'required' => TRUE, + ), + 'machine_name' => array( + 'label' => t('Machine name'), + 'type' => 'token', + 'description' => t('The internally used machine name for a search page.'), + 'schema field' => 'machine_name', + 'required' => TRUE, + ), + 'description' => array( + 'label' => t('Description'), + 'type' => 'text', + 'description' => t('The displayed description for a search page.'), + 'schema field' => 'description', + 'sanitize' => 'filter_xss', + ), + 'enabled' => array( + 'label' => t('Enabled'), + 'type' => 'boolean', + 'description' => t('A flag indicating whether the search page is enabled.'), + 'schema field' => 'enabled', + ), + ); + + return $info; +} + +/** + * Implements hook_search_api_index_update(). + */ +function search_api_page_search_api_index_update(SearchApiIndex $index) { + if (!$index->enabled && $index->original->enabled) { + foreach (search_api_page_load_multiple(FALSE, array('index_id' => $index->machine_name, 'enabled' => 1)) as $page) { + search_api_page_edit($page->id, array('enabled' => 0)); + } + } +} + +/** + * Implements hook_search_api_index_delete(). + */ +function search_api_page_search_api_index_delete(SearchApiIndex $index) { + // Only react on real delete, not revert. + if ($index->hasStatus(ENTITY_IN_CODE)) { + return; + } + + foreach (search_api_page_load_multiple(FALSE, array('index_id' => $index->machine_name)) as $page) { + search_api_page_delete($page->id); + } +} + +/** + * Implements hook_search_api_page_insert(). + * + * Rebuilds the menu table if a search page is created. + */ +function search_api_page_search_api_page_insert(Entity $page) { + menu_rebuild(); +} + +/** + * Implements hook_search_api_page_update(). + * + * Rebuilds the menu table if a search page is edited. + */ +function search_api_page_search_api_page_update(Entity $page) { + if ($page->enabled != $page->original->enabled || $page->path != $page->original->path) { + menu_rebuild(); + } +} + +/** + * Implements hook_search_api_page_delete(). + * + * Rebuilds the menu table if a search page is removed. + */ +function search_api_page_search_api_page_delete(Entity $page) { + menu_rebuild(); +} + +/** + * Entity URI callback. + */ +function search_api_page_url(Entity $page) { + return array('path' => $page->path); +} + +/** + * Entity property getter callback. + */ +function search_api_page_get_index(Entity $page) { + return search_api_index_load($page->index_id); +} + +/** + * Loads a search page. + * + * @param $id + * The page's id or machine name. + * @param $reset + * Whether to reset the internal cache. + * + * @return Entity + * A completely loaded page object, or NULL if no such page exists. + */ +function search_api_page_load($id, $reset = FALSE) { + $ret = entity_load_multiple_by_name('search_api_page', array($id), array(), $reset); + return $ret ? reset($ret) : FALSE; +} + +/** + * Load multiple search pages at once. + * + * @see entity_load() + * + * @param $ids + * An array of page IDs or machine names, or FALSE to load all pages. + * @param $conditions + * An array of conditions on the {search_api_page} table in the form + * 'field' => $value. + * @param $reset + * Whether to reset the internal entity_load cache. + * + * @return array + * An array of page objects keyed by machine name. + */ +function search_api_page_load_multiple($ids = FALSE, array $conditions = array(), $reset = FALSE) { + return entity_load_multiple_by_name('search_api_page', $ids, $conditions, $reset); +} + +/** + * Inserts a new search page into the database. + * + * @param array $values + * An array containing the values to be inserted. + * + * @return + * The newly inserted page's id, or FALSE on error. + */ +function search_api_page_insert(array $values) { + foreach (array('name', 'machine_name', 'index_id', 'path') as $var) { + if (!isset($values[$var])) { + throw new SearchApiException(t('Property @field has to be set for the new search page.', array('@field' => $var))); + } + } + if (empty($values['description'])) { + $values['description'] = NULL; + } + if (empty($values['options'])) { + $values['options'] = array(); + } + $fields = array( + 'name' => $values['name'], + 'machine_name' => $values['machine_name'], + 'description' => $values['description'], + 'enabled' => empty($values['enabled']) ? 0 : 1, + 'index_id' => $values['index_id'], + 'path' => $values['path'], + 'options' => $values['options'], + ); + if (isset($values['id'])) { + $fields['id'] = $values['id']; + } + + $page = entity_create('search_api_page', $fields); + $page->save(); + + return $page->id; +} + +/** + * Changes a page's settings. + * + * @param $id + * The edited page's ID. + * @param array $fields + * The new field values to set. + * + * @return + * 1 if fields were changed, 0 if the fields already had the desired values. + */ +function search_api_page_edit($id, array $fields) { + $page = search_api_page_load($id, TRUE); + $changeable = array('name' => 1, 'description' => 1, 'path' => 1, 'options' => 1, 'enabled' => 1); + foreach ($fields as $field => $value) { + if (isset($changeable[$field]) || $value === $page->$field) { + $page->$field = $value; + $new_values = TRUE; + } + } + // If there are no new values, just return 0. + if (empty($new_values)) { + return 0; + } + + $page->save(); + + return 1; +} + +/** + * Deletes a search page. + * + * @param $id + * The ID of the search page to delete. + * + * @return + * TRUE on success, FALSE on failure. + */ +function search_api_page_delete($id) { + $page = search_api_page_load($id, TRUE); + if (!$page) { + return FALSE; + } + $page->delete(); + + menu_rebuild(); + + return TRUE; +} + +/** + * Display a search form. + * + * @param Entity $page + * The search page for which this form is displayed. + * @param $keys + * The search keys. + * @param $compact + * Whether to display a compact form (e.g. for blocks) instead of a normal one. + */ +function search_api_page_search_form(array $form, array &$form_state, Entity $page, $keys = NULL, $compact = FALSE) { + $form['keys_' . $page->id] = array( + '#type' => 'textfield', + '#title' => t('Enter your keywords'), + '#title_display' => $compact ? 'invisible' : 'before', + '#default_value' => $keys, + '#size' => $compact ? 15 : 30, + ); + $form['base_' . $page->id] = array( + '#type' => 'value', + '#value' => $page->path, + ); + $form['id'] = array( + '#type' => 'hidden', + '#value' => $page->id, + ); + $form['submit_' . $page->id] = array( + '#type' => 'submit', + '#value' => t('Search'), + ); + + if (!$compact) { + $form = array( + '#type' => 'fieldset', + '#title' => $page->name, + 'form' => $form, + ); + if ($page->description) { + $form['text']['#markup'] = '

' . nl2br(check_plain($page->description)) . '

'; + $form['text']['#weight'] = -5; + } + } + + return $form; +} + +/** + * Validation callback for search_api_page_search_form(). + */ +function search_api_page_search_form_validate(array $form, array &$form_state) { + if (!trim($form_state['values']['keys_' . $form_state['values']['id']])) { + form_set_error('keys_' . $form_state['values']['id'], t('Please enter at least one keyword.')); + } +} + +/** + * Submit callback for search_api_page_search_form(). + */ +function search_api_page_search_form_submit(array $form, array &$form_state) { + $keys = trim($form_state['values']['keys_' . $form_state['values']['id']]); + // @todo Take care of "/"s in the keys + $form_state['redirect'] = $form_state['values']['base_' . $form_state['values']['id']] . '/' . $keys; +} \ No newline at end of file diff --git a/search_api_page.pages.inc b/search_api_page.pages.inc new file mode 100644 index 00000000..7b72a0db --- /dev/null +++ b/search_api_page.pages.inc @@ -0,0 +1,248 @@ +options['get_per_page']) && ((int) $_GET['per_page']) > 0) { + // Remember and later restore the true setting value so we don't + // accidentally permanently save the altered one. + $page->options['original_per_page'] = $page->options['per_page']; + $page->options['per_page'] = (int) $_GET['per_page']; + } + + $ret['form'] = drupal_get_form('search_api_page_search_form', $page, $keys); + + if ($keys) { + try { + $results = search_api_page_search_execute($page, $keys); + } + catch (SearchApiException $e) { + $ret['message'] = t('An error occurred while executing the search. Please try again or contact the site administrator if the problem persists.'); + watchdog('search_api_page', 'An error occurred while executing a search: !msg.', array('!msg' => $e->getMessage()), WATCHDOG_ERROR, l(t('search page'), $_GET['q'])); + } + + // If spellcheck results are returned then add them to the render array. + if (isset($results['search_api_spellcheck'])) { + $ret['search_api_spellcheck']['#theme'] = 'search_api_spellcheck'; + $ret['search_api_spellcheck']['#spellcheck'] = $results['search_api_spellcheck']; + // Let the theme function know where the key is stored by passing its arg + // number. We can work this out from the number of args in the page path. + $ret['search_api_spellcheck']['#options'] = array( + 'arg' => array(count(arg(NULL, $page->path))), + ); + } + + $ret['results']['#theme'] = 'search_api_page_results'; + $ret['results']['#index'] = search_api_index_load($page->index_id); + $ret['results']['#results'] = $results; + $ret['results']['#view_mode'] = isset($page->options['view_mode']) ? $page->options['view_mode'] : 'search_api_page_result'; + $ret['results']['#keys'] = $keys; + + if ($results['result count'] > $page->options['per_page']) { + pager_default_initialize($results['result count'], $page->options['per_page']); + $ret['pager']['#theme'] = 'pager'; + $ret['pager']['#quantity'] = 9; + } + + if (!empty($results['ignored'])) { + drupal_set_message(t('The following search keys are too short or too common and were therefore ignored: "@list".', array('@list' => implode(t('", "'), $results['ignored']))), 'warning'); + } + if (!empty($results['warnings'])) { + foreach ($results['warnings'] as $warning) { + drupal_set_message($warning, 'warning'); + } + } + } + + if (isset($page->options['original_per_page'])) { + $page->options['per_page'] = $page->options['original_per_page']; + unset($page->options['original_per_page']); + } + + return $ret; +} + +/** + * Executes a search. + * + * @param Entity $page + * The page for which a search should be executed. + * @param $keys + * The keywords to search for. + * + * @return array + * The search results as returned by SearchApiQueryInterface::execute(). + */ +function search_api_page_search_execute(Entity $page, $keys) { + $limit = $page->options['per_page']; + $offset = pager_find_page() * $limit; + $options = array( + 'search id' => 'search_api_page:' . $page->path, + 'parse mode' => $page->options['mode'], + ); + + if (!empty($page->options['search_api_spellcheck'])) { + $options['search_api_spellcheck'] = TRUE; + } + + $query = search_api_query($page->index_id, $options) + ->keys($keys) + ->range($offset, $limit); + if (!empty($page->options['fields'])) { + $query->fields($page->options['fields']); + } + return $query->execute(); +} + +/** + * Function for preprocessing the variables for the search_api_page_results + * theme. + * + * @param array $variables + * An associative array containing: + * - index: The index this search was executed on. + * - results: An array of search results, as returned by + * SearchApiQueryInterface::execute(). + * - keys: The keywords of the executed search. + */ +function template_preprocess_search_api_page_results(array &$variables) { + if (!empty($variables['results']['results'])) { + $variables['items'] = $variables['index']->loadItems(array_keys($variables['results']['results'])); + } +} + +/** + * Theme function for displaying search results. + * + * @param array $variables + * An associative array containing: + * - index: The index this search was executed on. + * - results: An array of search results, as returned by + * SearchApiQueryInterface::execute(). + * - items: The loaded items for all results, in an array keyed by ID. + * - view_mode: The view mode to use for displaying the individual results, + * or the special mode "search_api_page_result" to use the theme function + * of the same name. + * - keys: The keywords of the executed search. + */ +function theme_search_api_page_results(array $variables) { + drupal_add_css(drupal_get_path('module', 'search_api_page') . '/search_api_page.css'); + + $index = $variables['index']; + $results = $variables['results']; + $items = $variables['items']; + $keys = $variables['keys']; + + $output = '

' . format_plural($results['result count'], + 'The search found 1 result in @sec seconds.', + 'The search found @count results in @sec seconds.', + array('@sec' => round($results['performance']['complete'], 3))) . '

'; + + if (!$results['result count']) { + $output .= "\n

" . t('Your search yielded no results') . "

\n"; + return $output; + } + + $output .= "\n

" . t('Search results') . "

\n"; + + if ($variables['view_mode'] == 'search_api_page_result') { + $output .= '
    '; + foreach ($results['results'] as $item) { + $output .= '
  1. ' . theme('search_api_page_result', array('index' => $index, 'result' => $item, 'item' => isset($items[$item['id']]) ? $items[$item['id']] : NULL, 'keys' => $keys)) . '
  2. '; + } + $output .= '
'; + } + else { + // This option can only be set when the items are entities. + $output .= '
'; + $render = entity_view($index->item_type, $items, $variables['view_mode']); + $output .= render($render); + $output .= '
'; + } + + return $output; +} + +/** + * Theme function for displaying search results. + * + * @param array $variables + * An associative array containing: + * - index: The index this search was executed on. + * - result: One item of the search results, an array containing the keys + * 'id' and 'score'. + * - item: The loaded item corresponding to the result. + * - keys: The keywords of the executed search. + */ +function theme_search_api_page_result(array $variables) { + $index = $variables['index']; + $id = $variables['result']['id']; + $item = $variables['item']; + + $wrapper = $index->entityWrapper($item, FALSE); + + $url = $index->datasource()->getItemUrl($item); + $name = $index->datasource()->getItemLabel($item); + + if (!empty($variables['result']['excerpt'])) { + $text = $variables['result']['excerpt']; + } + else { + $fields = $index->options['fields']; + $fields = array_intersect_key($fields, drupal_map_assoc($index->getFulltextFields())); + $fields = search_api_extract_fields($wrapper, $fields); + $text = ''; + $length = 0; + foreach ($fields as $field_name => $field) { + if (search_api_is_list_type($field['type']) || !isset($field['value'])) { + continue; + } + $val_length = drupal_strlen($field['value']); + if ($val_length > $length) { + $text = $field['value']; + $length = $val_length; + + $format = NULL; + if (($pos = strrpos($field_name, ':')) && substr($field_name, $pos + 1) == 'value') { + $tmp = $wrapper; + try { + foreach (explode(':', substr($field_name, 0, $pos)) as $part) { + if (!isset($tmp->$part)) { + $tmp = NULL; + } + $tmp = $tmp->$part; + } + } + catch (EntityMetadataWrapperException $e) { + $tmp = NULL; + } + if ($tmp && $tmp->type() == 'text_formatted' && isset($tmp->format)) { + $format = $tmp->format->value(); + } + } + } + } + if ($text && function_exists('text_summary')) { + $text = text_summary($text, $format); + } + } + + $output = '

' . ($url ? l($name, $url['path'], $url['options']) : check_plain($name)) . "

\n"; + if ($text) { + $output .= $text; + } + + return $output; +} From 256475e7169fad9b5798ed800cbb6bdf2b4b03dd Mon Sep 17 00:00:00 2001 From: bachy Date: Sun, 22 Jul 2012 16:27:00 +0200 Subject: [PATCH 2/5] disable form patch http://drupal.org/node/1512086 Signed-off-by: bachy --- disable_form-1512086-0_1.patch | 53 +++++++ search_api_page.admin.inc | 16 +++ search_api_page.pages.inc | 4 +- search_api_page.pages.inc.orig | 248 +++++++++++++++++++++++++++++++++ 4 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 disable_form-1512086-0_1.patch create mode 100644 search_api_page.pages.inc.orig diff --git a/disable_form-1512086-0_1.patch b/disable_form-1512086-0_1.patch new file mode 100644 index 00000000..7cdf3883 --- /dev/null +++ b/disable_form-1512086-0_1.patch @@ -0,0 +1,53 @@ +diff --git a/search_api_page.admin.inc b/search_api_page.admin.inc +old mode 100644 +new mode 100755 +index 013672e..a08ee06 +--- a/search_api_page.admin.inc ++++ b/search_api_page.admin.inc +@@ -177,6 +177,14 @@ function search_api_page_admin_add(array $form, array &$form_state) { + '#default_value' => 10, + ); + ++ $form['result_page_search_form'] = array( ++ '#type' => 'radios', ++ '#title' => t('Search form on result page'), ++ '#default_value' => 1, ++ '#options' => array('1' => t('Enabled'), '0' => t('Disabled')), ++ '#description' => t('Enable or disable the search form on the result page'), ++ ); ++ + $form['get_per_page'] = array( + '#type' => 'checkbox', + '#title' => t('Allow GET override'), +@@ -359,6 +367,14 @@ function search_api_page_admin_edit(array $form, array &$form_state, Entity $pag + '#default_value' => $page->options['per_page'], + ); + ++ $form['result_page_search_form'] = array( ++ '#type' => 'radios', ++ '#title' => t('Search form on result page'), ++ '#default_value' => $page->options['per_page'], ++ '#options' => array('1' => t('Enabled'), '0' => t('Disabled')), ++ '#description' => t('Enable or disable the search form on the result page'), ++ ); ++ + $form['options']['get_per_page'] = array( + '#type' => 'checkbox', + '#title' => t('Allow GET override'), +diff --git a/search_api_page.pages.inc b/search_api_page.pages.inc +old mode 100644 +new mode 100755 +index 23f7b30..d43a465 +--- a/search_api_page.pages.inc ++++ b/search_api_page.pages.inc +@@ -22,7 +22,9 @@ function search_api_page_view($id, $keys = NULL) { + $page->options['per_page'] = (int) $_GET['per_page']; + } + +- $ret['form'] = drupal_get_form('search_api_page_search_form', $page, $keys); ++ if ($page->options['result_page_search_form']) { ++ $ret['form'] = drupal_get_form('search_api_page_search_form', $page, $keys); ++ } + + if ($keys) { + $results = search_api_page_search_execute($page, $keys); diff --git a/search_api_page.admin.inc b/search_api_page.admin.inc index 013672ee..a08ee060 100644 --- a/search_api_page.admin.inc +++ b/search_api_page.admin.inc @@ -177,6 +177,14 @@ function search_api_page_admin_add(array $form, array &$form_state) { '#default_value' => 10, ); + $form['result_page_search_form'] = array( + '#type' => 'radios', + '#title' => t('Search form on result page'), + '#default_value' => 1, + '#options' => array('1' => t('Enabled'), '0' => t('Disabled')), + '#description' => t('Enable or disable the search form on the result page'), + ); + $form['get_per_page'] = array( '#type' => 'checkbox', '#title' => t('Allow GET override'), @@ -359,6 +367,14 @@ function search_api_page_admin_edit(array $form, array &$form_state, Entity $pag '#default_value' => $page->options['per_page'], ); + $form['result_page_search_form'] = array( + '#type' => 'radios', + '#title' => t('Search form on result page'), + '#default_value' => $page->options['per_page'], + '#options' => array('1' => t('Enabled'), '0' => t('Disabled')), + '#description' => t('Enable or disable the search form on the result page'), + ); + $form['options']['get_per_page'] = array( '#type' => 'checkbox', '#title' => t('Allow GET override'), diff --git a/search_api_page.pages.inc b/search_api_page.pages.inc index 7b72a0db..7de2e9cd 100644 --- a/search_api_page.pages.inc +++ b/search_api_page.pages.inc @@ -22,7 +22,9 @@ function search_api_page_view($id, $keys = NULL) { $page->options['per_page'] = (int) $_GET['per_page']; } - $ret['form'] = drupal_get_form('search_api_page_search_form', $page, $keys); + if ($page->options['result_page_search_form']) { + $ret['form'] = drupal_get_form('search_api_page_search_form', $page, $keys); + } if ($keys) { try { diff --git a/search_api_page.pages.inc.orig b/search_api_page.pages.inc.orig new file mode 100644 index 00000000..7b72a0db --- /dev/null +++ b/search_api_page.pages.inc.orig @@ -0,0 +1,248 @@ +options['get_per_page']) && ((int) $_GET['per_page']) > 0) { + // Remember and later restore the true setting value so we don't + // accidentally permanently save the altered one. + $page->options['original_per_page'] = $page->options['per_page']; + $page->options['per_page'] = (int) $_GET['per_page']; + } + + $ret['form'] = drupal_get_form('search_api_page_search_form', $page, $keys); + + if ($keys) { + try { + $results = search_api_page_search_execute($page, $keys); + } + catch (SearchApiException $e) { + $ret['message'] = t('An error occurred while executing the search. Please try again or contact the site administrator if the problem persists.'); + watchdog('search_api_page', 'An error occurred while executing a search: !msg.', array('!msg' => $e->getMessage()), WATCHDOG_ERROR, l(t('search page'), $_GET['q'])); + } + + // If spellcheck results are returned then add them to the render array. + if (isset($results['search_api_spellcheck'])) { + $ret['search_api_spellcheck']['#theme'] = 'search_api_spellcheck'; + $ret['search_api_spellcheck']['#spellcheck'] = $results['search_api_spellcheck']; + // Let the theme function know where the key is stored by passing its arg + // number. We can work this out from the number of args in the page path. + $ret['search_api_spellcheck']['#options'] = array( + 'arg' => array(count(arg(NULL, $page->path))), + ); + } + + $ret['results']['#theme'] = 'search_api_page_results'; + $ret['results']['#index'] = search_api_index_load($page->index_id); + $ret['results']['#results'] = $results; + $ret['results']['#view_mode'] = isset($page->options['view_mode']) ? $page->options['view_mode'] : 'search_api_page_result'; + $ret['results']['#keys'] = $keys; + + if ($results['result count'] > $page->options['per_page']) { + pager_default_initialize($results['result count'], $page->options['per_page']); + $ret['pager']['#theme'] = 'pager'; + $ret['pager']['#quantity'] = 9; + } + + if (!empty($results['ignored'])) { + drupal_set_message(t('The following search keys are too short or too common and were therefore ignored: "@list".', array('@list' => implode(t('", "'), $results['ignored']))), 'warning'); + } + if (!empty($results['warnings'])) { + foreach ($results['warnings'] as $warning) { + drupal_set_message($warning, 'warning'); + } + } + } + + if (isset($page->options['original_per_page'])) { + $page->options['per_page'] = $page->options['original_per_page']; + unset($page->options['original_per_page']); + } + + return $ret; +} + +/** + * Executes a search. + * + * @param Entity $page + * The page for which a search should be executed. + * @param $keys + * The keywords to search for. + * + * @return array + * The search results as returned by SearchApiQueryInterface::execute(). + */ +function search_api_page_search_execute(Entity $page, $keys) { + $limit = $page->options['per_page']; + $offset = pager_find_page() * $limit; + $options = array( + 'search id' => 'search_api_page:' . $page->path, + 'parse mode' => $page->options['mode'], + ); + + if (!empty($page->options['search_api_spellcheck'])) { + $options['search_api_spellcheck'] = TRUE; + } + + $query = search_api_query($page->index_id, $options) + ->keys($keys) + ->range($offset, $limit); + if (!empty($page->options['fields'])) { + $query->fields($page->options['fields']); + } + return $query->execute(); +} + +/** + * Function for preprocessing the variables for the search_api_page_results + * theme. + * + * @param array $variables + * An associative array containing: + * - index: The index this search was executed on. + * - results: An array of search results, as returned by + * SearchApiQueryInterface::execute(). + * - keys: The keywords of the executed search. + */ +function template_preprocess_search_api_page_results(array &$variables) { + if (!empty($variables['results']['results'])) { + $variables['items'] = $variables['index']->loadItems(array_keys($variables['results']['results'])); + } +} + +/** + * Theme function for displaying search results. + * + * @param array $variables + * An associative array containing: + * - index: The index this search was executed on. + * - results: An array of search results, as returned by + * SearchApiQueryInterface::execute(). + * - items: The loaded items for all results, in an array keyed by ID. + * - view_mode: The view mode to use for displaying the individual results, + * or the special mode "search_api_page_result" to use the theme function + * of the same name. + * - keys: The keywords of the executed search. + */ +function theme_search_api_page_results(array $variables) { + drupal_add_css(drupal_get_path('module', 'search_api_page') . '/search_api_page.css'); + + $index = $variables['index']; + $results = $variables['results']; + $items = $variables['items']; + $keys = $variables['keys']; + + $output = '

' . format_plural($results['result count'], + 'The search found 1 result in @sec seconds.', + 'The search found @count results in @sec seconds.', + array('@sec' => round($results['performance']['complete'], 3))) . '

'; + + if (!$results['result count']) { + $output .= "\n

" . t('Your search yielded no results') . "

\n"; + return $output; + } + + $output .= "\n

" . t('Search results') . "

\n"; + + if ($variables['view_mode'] == 'search_api_page_result') { + $output .= '
    '; + foreach ($results['results'] as $item) { + $output .= '
  1. ' . theme('search_api_page_result', array('index' => $index, 'result' => $item, 'item' => isset($items[$item['id']]) ? $items[$item['id']] : NULL, 'keys' => $keys)) . '
  2. '; + } + $output .= '
'; + } + else { + // This option can only be set when the items are entities. + $output .= '
'; + $render = entity_view($index->item_type, $items, $variables['view_mode']); + $output .= render($render); + $output .= '
'; + } + + return $output; +} + +/** + * Theme function for displaying search results. + * + * @param array $variables + * An associative array containing: + * - index: The index this search was executed on. + * - result: One item of the search results, an array containing the keys + * 'id' and 'score'. + * - item: The loaded item corresponding to the result. + * - keys: The keywords of the executed search. + */ +function theme_search_api_page_result(array $variables) { + $index = $variables['index']; + $id = $variables['result']['id']; + $item = $variables['item']; + + $wrapper = $index->entityWrapper($item, FALSE); + + $url = $index->datasource()->getItemUrl($item); + $name = $index->datasource()->getItemLabel($item); + + if (!empty($variables['result']['excerpt'])) { + $text = $variables['result']['excerpt']; + } + else { + $fields = $index->options['fields']; + $fields = array_intersect_key($fields, drupal_map_assoc($index->getFulltextFields())); + $fields = search_api_extract_fields($wrapper, $fields); + $text = ''; + $length = 0; + foreach ($fields as $field_name => $field) { + if (search_api_is_list_type($field['type']) || !isset($field['value'])) { + continue; + } + $val_length = drupal_strlen($field['value']); + if ($val_length > $length) { + $text = $field['value']; + $length = $val_length; + + $format = NULL; + if (($pos = strrpos($field_name, ':')) && substr($field_name, $pos + 1) == 'value') { + $tmp = $wrapper; + try { + foreach (explode(':', substr($field_name, 0, $pos)) as $part) { + if (!isset($tmp->$part)) { + $tmp = NULL; + } + $tmp = $tmp->$part; + } + } + catch (EntityMetadataWrapperException $e) { + $tmp = NULL; + } + if ($tmp && $tmp->type() == 'text_formatted' && isset($tmp->format)) { + $format = $tmp->format->value(); + } + } + } + } + if ($text && function_exists('text_summary')) { + $text = text_summary($text, $format); + } + } + + $output = '

' . ($url ? l($name, $url['path'], $url['options']) : check_plain($name)) . "

\n"; + if ($text) { + $output .= $text; + } + + return $output; +} From 2030a6f810b1a062226895916945730e496e59fa Mon Sep 17 00:00:00 2001 From: bachy Date: Sun, 22 Jul 2012 17:02:31 +0200 Subject: [PATCH 3/5] =?UTF-8?q?bug=20fixe=20on=20=C3=A9dit=20page=20for=20?= =?UTF-8?q?precedent=20patch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: bachy --- search_api_page.admin.inc | 2 +- search_api_page.module | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/search_api_page.admin.inc b/search_api_page.admin.inc index a08ee060..d0f541d1 100644 --- a/search_api_page.admin.inc +++ b/search_api_page.admin.inc @@ -370,7 +370,7 @@ function search_api_page_admin_edit(array $form, array &$form_state, Entity $pag $form['result_page_search_form'] = array( '#type' => 'radios', '#title' => t('Search form on result page'), - '#default_value' => $page->options['per_page'], + '#default_value' => isset($page->options['result_page_search_form']) ? $page->options['result_page_search_form'] : 1, '#options' => array('1' => t('Enabled'), '0' => t('Disabled')), '#description' => t('Enable or disable the search form on the result page'), ); diff --git a/search_api_page.module b/search_api_page.module index a9a00cb0..16179de6 100755 --- a/search_api_page.module +++ b/search_api_page.module @@ -364,7 +364,7 @@ function search_api_page_insert(array $values) { */ function search_api_page_edit($id, array $fields) { $page = search_api_page_load($id, TRUE); - $changeable = array('name' => 1, 'description' => 1, 'path' => 1, 'options' => 1, 'enabled' => 1); + $changeable = array('name' => 1, 'description' => 1, 'path' => 1, 'options' => 1, 'enabled' => 1, 'result_page_search_form'=>1); foreach ($fields as $field => $value) { if (isset($changeable[$field]) || $value === $page->$field) { $page->$field = $value; From 68126583b1befe910d797449065c7cbb4c5736c3 Mon Sep 17 00:00:00 2001 From: bachy Date: Mon, 23 Jul 2012 17:53:59 +0200 Subject: [PATCH 4/5] addtemplate patch http://drupal.org/node/1313676#comment-6169000 Signed-off-by: bachy --- addtemplates-1313676-16.patch | 457 ++++++++++++++++++++++++++++++++ search-api-page-result.tpl.php | 56 ++++ search-api-page-results.tpl.php | 53 ++++ search_api_page.info | 1 + search_api_page.info.orig | 15 ++ search_api_page.module | 89 +++++++ search_api_page.pages.inc | 151 +++++------ search_api_page.pages.inc.orig | 4 +- 8 files changed, 744 insertions(+), 82 deletions(-) create mode 100644 addtemplates-1313676-16.patch create mode 100644 search-api-page-result.tpl.php create mode 100644 search-api-page-results.tpl.php create mode 100644 search_api_page.info.orig diff --git a/addtemplates-1313676-16.patch b/addtemplates-1313676-16.patch new file mode 100644 index 00000000..f834afbe --- /dev/null +++ b/addtemplates-1313676-16.patch @@ -0,0 +1,457 @@ +diff --git a/search-api-page-result.tpl.php b/search-api-page-result.tpl.php +new file mode 100644 +index 0000000..bf56e48 +--- /dev/null ++++ b/search-api-page-result.tpl.php +@@ -0,0 +1,56 @@ ++'. check_plain(print_r($info_split, 1)) .''; ?> ++ * @endcode ++ * ++ * @see template_preprocess() ++ * @see template_preprocess_search_result() ++ * @see template_process() ++ */ ++?> ++> ++

++ ++

++
++ ++

++ ++ ++

++ ++
++ +diff --git a/search-api-page-results.tpl.php b/search-api-page-results.tpl.php +new file mode 100644 +index 0000000..9bbf343 +--- /dev/null ++++ b/search-api-page-results.tpl.php +@@ -0,0 +1,53 @@ ++ ++ ++
++ ++ ++ ++

++ ++ ++ ++ ++
++ item_type, $items, $variables['view_mode'])); ?> ++
++ ++ ++ ++

++ ++
++ +diff --git a/search_api_page.info b/search_api_page.info +index 148c481..263ce24 100644 +--- a/search_api_page.info ++++ b/search_api_page.info +@@ -4,5 +4,6 @@ description = "Create search pages using Search API indexes." + dependencies[] = search_api + core = 7.x + package = Search ++stylesheets[all][] = search_api_page.css + + configure = admin/config/search/search_api/page +diff --git a/search_api_page.module b/search_api_page.module +index a9a00cb..6e15c45 100755 +--- a/search_api_page.module ++++ b/search_api_page.module +@@ -60,8 +60,12 @@ function search_api_page_theme() { + 'items' => array(), + 'view_mode' => 'search_api_page_result', + 'keys' => '', ++ 'page_machine_name' => NULL, ++ 'spellcheck' => NULL, ++ 'pager' => NULL, + ), + 'file' => 'search_api_page.pages.inc', ++ 'template' => 'search-api-page-results', + ); + $themes['search_api_page_result'] = array( + 'variables' => array( +@@ -69,14 +73,99 @@ function search_api_page_theme() { + 'result' => NULL, + 'item' => NULL, + 'keys' => '', ++ 'list_classes' => '', + ), + 'file' => 'search_api_page.pages.inc', ++ 'template' => 'search-api-page-result', ++ ); ++ $themes['search_performance'] = array( ++ 'render element' => 'element', ++ ); ++ $themes['search_results_list'] = array( ++ 'render element' => 'element', + ); + + return $themes; + } + + /** ++ * Implements theme for rendering search-performance ++ */ ++function theme_search_performance($variables) { ++ $element = array_shift($variables); ++ ++ return $element['#markup']; ++} ++ ++/** ++ * Returns HTML for a list of search results. ++ * Taken from theme_item_list(). ++ * ++ * @param $variables ++ * An associative array containing: ++ * - items: An array of items to be displayed in the list. If an item is a ++ * string, then it is used as is. If an item is an array, then the "data" ++ * element of the array is used as the contents of the list item. If an item ++ * is an array with a "children" element, those children are displayed in a ++ * nested list. All other elements are treated as attributes of the list ++ * item element. ++ * - type: The type of list to return (e.g. "ul", "ol"). ++ * - attributes: The attributes applied to the list element. ++ */ ++function theme_search_results_list($variables) { ++ // Pull Element array from the $variables array. ++ $variables = $variables['element']; ++ ++ $items = $variables['items']; // Full data ++ $type = $variables['type']; ++ ++ // CSS classes for ul ++ $attributes = (!empty($variables['attributes'])) ? $variables['attributes'] : array(); ++ $attributes['class'] = array_merge( ++ array('item-list', 'search-results-list'), ++ (isset($attributes['class'])) ? $attributes['class'] : array() ++ ); ++ ++ // Render items within a list ++ if (!empty($items)) { ++ $output = "<$type" . drupal_attributes($attributes) . '>'; ++ $num_items = count($items); ++ ++ // Parse search results as tokens to access items with full data. ++ $i = 0; ++ foreach ($variables['results'] as $result) { ++ // Set css classes. ++ $item_attributes = array(); ++ if ($i == 0) { ++ $item_attributes['class'][] = 'first'; ++ } ++ if ($i == $num_items - 1) { ++ $item_attributes['class'][] = 'last'; ++ } ++ (($i+1)%2) ? $item_attributes['class'][] = 'odd': $item_attributes['class'][] = 'even'; ++ ++ // Define render array. ++ $data = theme( ++ 'search_api_page_result', array( ++ 'index' => $variables['index'], // Use full results index. ++ 'result' => $result, ++ 'item' => isset($items[$result['id']]) ? ++ $items[$result['id']] : ++ NULL, ++ 'keys' => $variables['keys'], ++ 'list_classes' => drupal_attributes($item_attributes), ++ ) ++ ); ++ $output .= $data . "\n"; ++ $i++; ++ } ++ $output .= ""; ++ ++ return $output; ++ } ++} ++ ++/** + * Implements hook_permission(). + */ + function search_api_page_permission() { +diff --git a/search_api_page.pages.inc b/search_api_page.pages.inc +index 7b72a0d..c514909 100644 +--- a/search_api_page.pages.inc ++++ b/search_api_page.pages.inc +@@ -33,14 +33,18 @@ function search_api_page_view($id, $keys = NULL) { + watchdog('search_api_page', 'An error occurred while executing a search: !msg.', array('!msg' => $e->getMessage()), WATCHDOG_ERROR, l(t('search page'), $_GET['q'])); + } + +- // If spellcheck results are returned then add them to the render array. ++ // Load spellcheck. + if (isset($results['search_api_spellcheck'])) { +- $ret['search_api_spellcheck']['#theme'] = 'search_api_spellcheck'; +- $ret['search_api_spellcheck']['#spellcheck'] = $results['search_api_spellcheck']; ++ $ret['results']['#spellcheck'] = array( ++ '#theme' => 'search_api_spellcheck', ++ '#spellcheck' => $results['search_api_spellcheck'], + // Let the theme function know where the key is stored by passing its arg + // number. We can work this out from the number of args in the page path. +- $ret['search_api_spellcheck']['#options'] = array( +- 'arg' => array(count(arg(NULL, $page->path))), ++ '#options' => array( ++ 'arg' => array(count(arg(NULL, $page->path))), ++ ), ++ '#prefix' => '

', ++ '#suffix' => '

', + ); + } + +@@ -49,11 +53,12 @@ function search_api_page_view($id, $keys = NULL) { + $ret['results']['#results'] = $results; + $ret['results']['#view_mode'] = isset($page->options['view_mode']) ? $page->options['view_mode'] : 'search_api_page_result'; + $ret['results']['#keys'] = $keys; ++ $ret['results']['#page_machine_name'] = $page->machine_name; + ++ // Load pager. + if ($results['result count'] > $page->options['per_page']) { + pager_default_initialize($results['result count'], $page->options['per_page']); +- $ret['pager']['#theme'] = 'pager'; +- $ret['pager']['#quantity'] = 9; ++ $ret['results']['#pager'] = theme('pager'); + } + + if (!empty($results['ignored'])) { +@@ -108,96 +113,66 @@ function search_api_page_search_execute(Entity $page, $keys) { + + /** + * Function for preprocessing the variables for the search_api_page_results +- * theme. ++ * template. + * + * @param array $variables + * An associative array containing: +- * - index: The index this search was executed on. +- * - results: An array of search results, as returned by ++ * - $index: The index this search was executed on. ++ * - $results: An array of search results, as returned by + * SearchApiQueryInterface::execute(). +- * - keys: The keywords of the executed search. +- */ +-function template_preprocess_search_api_page_results(array &$variables) { +- if (!empty($variables['results']['results'])) { +- $variables['items'] = $variables['index']->loadItems(array_keys($variables['results']['results'])); +- } +-} +- +-/** +- * Theme function for displaying search results. ++ * - $keys: The keywords of the executed search. + * +- * @param array $variables +- * An associative array containing: +- * - index: The index this search was executed on. +- * - results: An array of search results, as returned by +- * SearchApiQueryInterface::execute(). +- * - items: The loaded items for all results, in an array keyed by ID. +- * - view_mode: The view mode to use for displaying the individual results, +- * or the special mode "search_api_page_result" to use the theme function +- * of the same name. +- * - keys: The keywords of the executed search. ++ * @see search_api_page-results.tpl.php + */ +-function theme_search_api_page_results(array $variables) { +- drupal_add_css(drupal_get_path('module', 'search_api_page') . '/search_api_page.css'); +- +- $index = $variables['index']; ++function template_preprocess_search_api_page_results(array &$variables) { + $results = $variables['results']; +- $items = $variables['items']; + $keys = $variables['keys']; + +- $output = '

' . format_plural($results['result count'], +- 'The search found 1 result in @sec seconds.', +- 'The search found @count results in @sec seconds.', +- array('@sec' => round($results['performance']['complete'], 3))) . '

'; +- +- if (!$results['result count']) { +- $output .= "\n

" . t('Your search yielded no results') . "

\n"; +- return $output; +- } +- +- $output .= "\n

" . t('Search results') . "

\n"; +- +- if ($variables['view_mode'] == 'search_api_page_result') { +- $output .= '
    '; +- foreach ($results['results'] as $item) { +- $output .= '
  1. ' . theme('search_api_page_result', array('index' => $index, 'result' => $item, 'item' => isset($items[$item['id']]) ? $items[$item['id']] : NULL, 'keys' => $keys)) . '
  2. '; +- } +- $output .= '
'; +- } +- else { +- // This option can only be set when the items are entities. +- $output .= '
'; +- $render = entity_view($index->item_type, $items, $variables['view_mode']); +- $output .= render($render); +- $output .= '
'; +- } ++ $variables['items'] = $variables['index']->loadItems(array_keys($variables['results']['results'])); ++ $variables['result_count'] = $results['result count']; ++ $variables['sec'] = round($results['performance']['complete'], 3); ++ $variables['search_performance'] = array( ++ '#theme' => 'search_performance', ++ '#markup' => format_plural( ++ $results['result count'], ++ 'The search found 1 result in @sec seconds.', ++ 'The search found @count results in @sec seconds.', ++ array('@sec' => $variables['sec']) ++ ), ++ '#prefix' => '

', ++ '#suffix' => '

', ++ ); + +- return $output; ++ $variables['search_results'] = array( ++ '#theme' => 'search_results_list', ++ 'results' => $variables['results']['results'], ++ 'items' => $variables['items'], ++ 'index' => $variables['index'], ++ 'type' => 'ul', ++ ); + } + + /** +- * Theme function for displaying search results. ++ * Process variables for search-result.tpl.php. + * +- * @param array $variables +- * An associative array containing: +- * - index: The index this search was executed on. +- * - result: One item of the search results, an array containing the keys +- * 'id' and 'score'. +- * - item: The loaded item corresponding to the result. +- * - keys: The keywords of the executed search. ++ * The $variables array contains the following arguments: ++ * - $result ++ * ++ * @see search_api_page-result.tpl.php + */ +-function theme_search_api_page_result(array $variables) { ++function template_preprocess_search_api_page_result(&$variables) { + $index = $variables['index']; +- $id = $variables['result']['id']; ++ $variables['id'] = $variables['result']['id']; ++ $variables['excerpt'] = $variables['result']['excerpt']; + $item = $variables['item']; + + $wrapper = $index->entityWrapper($item, FALSE); + +- $url = $index->datasource()->getItemUrl($item); +- $name = $index->datasource()->getItemLabel($item); ++ $variables['url'] = $index->datasource()->getItemUrl($item); ++ $variables['title'] = $index->datasource()->getItemLabel($item); + +- if (!empty($variables['result']['excerpt'])) { +- $text = $variables['result']['excerpt']; ++ if (!empty($variables['excerpt'])) { ++ $text = $variables['excerpt']; + } + else { + $fields = $index->options['fields']; +@@ -239,10 +214,24 @@ function theme_search_api_page_result(array $variables) { + } + } + +- $output = '

' . ($url ? l($name, $url['path'], $url['options']) : check_plain($name)) . "

\n"; +- if ($text) { +- $output .= $text; ++ // Check for existence. User search does not include snippets. ++ $variables['snippet'] = isset($text) ? $text : ''; ++ ++ // Meta information ++ global $language; ++ if (isset($item->language) && $item->language != $language->language && $item->language != LANGUAGE_NONE) { ++ $variables['title_attributes_array']['xml:lang'] = $item->language; ++ $variables['content_attributes_array']['xml:lang'] = $item->language; + } + +- return $output; ++ $info = array(); ++ if (!empty($item->name)) { ++ $info['user'] = $item->name; ++ } ++ if (!empty($item->created)) { ++ $info['date'] = format_date($item->created, 'short'); ++ } ++ // Provide separated and grouped meta information.. ++ $variables['info_split'] = $info; ++ $variables['info'] = implode(' - ', $info); + } diff --git a/search-api-page-result.tpl.php b/search-api-page-result.tpl.php new file mode 100644 index 00000000..bf56e480 --- /dev/null +++ b/search-api-page-result.tpl.php @@ -0,0 +1,56 @@ +'. check_plain(print_r($info_split, 1)) .''; ?> + * @endcode + * + * @see template_preprocess() + * @see template_preprocess_search_result() + * @see template_process() + */ +?> +> +

+ +

+
+ +

+ + +

+ +
+ diff --git a/search-api-page-results.tpl.php b/search-api-page-results.tpl.php new file mode 100644 index 00000000..9bbf3430 --- /dev/null +++ b/search-api-page-results.tpl.php @@ -0,0 +1,53 @@ + + +
+ + + +

+ + + + +
+ item_type, $items, $variables['view_mode'])); ?> +
+ + + +

+ +
+ diff --git a/search_api_page.info b/search_api_page.info index 9ff9d839..3bf4eb86 100644 --- a/search_api_page.info +++ b/search_api_page.info @@ -4,6 +4,7 @@ description = "Create search pages using Search API indexes." dependencies[] = search_api core = 7.x package = Search +stylesheets[all][] = search_api_page.css configure = admin/config/search/search_api/page diff --git a/search_api_page.info.orig b/search_api_page.info.orig new file mode 100644 index 00000000..9ff9d839 --- /dev/null +++ b/search_api_page.info.orig @@ -0,0 +1,15 @@ + +name = Search pages +description = "Create search pages using Search API indexes." +dependencies[] = search_api +core = 7.x +package = Search + +configure = admin/config/search/search_api/page + +; Information added by drupal.org packaging script on 2012-06-26 +version = "7.x-1.0-beta2+5-dev" +core = "7.x" +project = "search_api_page" +datestamp = "1340671392" + diff --git a/search_api_page.module b/search_api_page.module index 16179de6..73faaada 100755 --- a/search_api_page.module +++ b/search_api_page.module @@ -60,8 +60,12 @@ function search_api_page_theme() { 'items' => array(), 'view_mode' => 'search_api_page_result', 'keys' => '', + 'page_machine_name' => NULL, + 'spellcheck' => NULL, + 'pager' => NULL, ), 'file' => 'search_api_page.pages.inc', + 'template' => 'search-api-page-results', ); $themes['search_api_page_result'] = array( 'variables' => array( @@ -69,13 +73,98 @@ function search_api_page_theme() { 'result' => NULL, 'item' => NULL, 'keys' => '', + 'list_classes' => '', ), 'file' => 'search_api_page.pages.inc', + 'template' => 'search-api-page-result', + ); + $themes['search_performance'] = array( + 'render element' => 'element', + ); + $themes['search_results_list'] = array( + 'render element' => 'element', ); return $themes; } +/** + * Implements theme for rendering search-performance + */ +function theme_search_performance($variables) { + $element = array_shift($variables); + + return $element['#markup']; +} + +/** + * Returns HTML for a list of search results. + * Taken from theme_item_list(). + * + * @param $variables + * An associative array containing: + * - items: An array of items to be displayed in the list. If an item is a + * string, then it is used as is. If an item is an array, then the "data" + * element of the array is used as the contents of the list item. If an item + * is an array with a "children" element, those children are displayed in a + * nested list. All other elements are treated as attributes of the list + * item element. + * - type: The type of list to return (e.g. "ul", "ol"). + * - attributes: The attributes applied to the list element. + */ +function theme_search_results_list($variables) { + // Pull Element array from the $variables array. + $variables = $variables['element']; + + $items = $variables['items']; // Full data + $type = $variables['type']; + + // CSS classes for ul + $attributes = (!empty($variables['attributes'])) ? $variables['attributes'] : array(); + $attributes['class'] = array_merge( + array('item-list', 'search-results-list'), + (isset($attributes['class'])) ? $attributes['class'] : array() + ); + + // Render items within a list + if (!empty($items)) { + $output = "<$type" . drupal_attributes($attributes) . '>'; + $num_items = count($items); + + // Parse search results as tokens to access items with full data. + $i = 0; + foreach ($variables['results'] as $result) { + // Set css classes. + $item_attributes = array(); + if ($i == 0) { + $item_attributes['class'][] = 'first'; + } + if ($i == $num_items - 1) { + $item_attributes['class'][] = 'last'; + } + (($i+1)%2) ? $item_attributes['class'][] = 'odd': $item_attributes['class'][] = 'even'; + + // Define render array. + $data = theme( + 'search_api_page_result', array( + 'index' => $variables['index'], // Use full results index. + 'result' => $result, + 'item' => isset($items[$result['id']]) ? + $items[$result['id']] : + NULL, + 'keys' => $variables['keys'], + 'list_classes' => drupal_attributes($item_attributes), + ) + ); + $output .= $data . "\n"; + $i++; + } + $output .= ""; + + return $output; + } +} + /** * Implements hook_permission(). */ diff --git a/search_api_page.pages.inc b/search_api_page.pages.inc index 7de2e9cd..521104b5 100644 --- a/search_api_page.pages.inc +++ b/search_api_page.pages.inc @@ -22,7 +22,7 @@ function search_api_page_view($id, $keys = NULL) { $page->options['per_page'] = (int) $_GET['per_page']; } - if ($page->options['result_page_search_form']) { + if (isset($page->options['result_page_search_form']) && $page->options['result_page_search_form']) { $ret['form'] = drupal_get_form('search_api_page_search_form', $page, $keys); } @@ -35,14 +35,18 @@ function search_api_page_view($id, $keys = NULL) { watchdog('search_api_page', 'An error occurred while executing a search: !msg.', array('!msg' => $e->getMessage()), WATCHDOG_ERROR, l(t('search page'), $_GET['q'])); } - // If spellcheck results are returned then add them to the render array. + // Load spellcheck. if (isset($results['search_api_spellcheck'])) { - $ret['search_api_spellcheck']['#theme'] = 'search_api_spellcheck'; - $ret['search_api_spellcheck']['#spellcheck'] = $results['search_api_spellcheck']; + $ret['results']['#spellcheck'] = array( + '#theme' => 'search_api_spellcheck', + '#spellcheck' => $results['search_api_spellcheck'], // Let the theme function know where the key is stored by passing its arg // number. We can work this out from the number of args in the page path. - $ret['search_api_spellcheck']['#options'] = array( - 'arg' => array(count(arg(NULL, $page->path))), + '#options' => array( + 'arg' => array(count(arg(NULL, $page->path))), + ), + '#prefix' => '

', + '#suffix' => '

', ); } @@ -51,11 +55,12 @@ function search_api_page_view($id, $keys = NULL) { $ret['results']['#results'] = $results; $ret['results']['#view_mode'] = isset($page->options['view_mode']) ? $page->options['view_mode'] : 'search_api_page_result'; $ret['results']['#keys'] = $keys; + $ret['results']['#page_machine_name'] = $page->machine_name; + // Load pager. if ($results['result count'] > $page->options['per_page']) { pager_default_initialize($results['result count'], $page->options['per_page']); - $ret['pager']['#theme'] = 'pager'; - $ret['pager']['#quantity'] = 9; + $ret['results']['#pager'] = theme('pager'); } if (!empty($results['ignored'])) { @@ -110,96 +115,66 @@ function search_api_page_search_execute(Entity $page, $keys) { /** * Function for preprocessing the variables for the search_api_page_results - * theme. + * template. * * @param array $variables * An associative array containing: - * - index: The index this search was executed on. - * - results: An array of search results, as returned by + * - $index: The index this search was executed on. + * - $results: An array of search results, as returned by * SearchApiQueryInterface::execute(). - * - keys: The keywords of the executed search. + * - $keys: The keywords of the executed search. + * + * @see search_api_page-results.tpl.php */ function template_preprocess_search_api_page_results(array &$variables) { - if (!empty($variables['results']['results'])) { - $variables['items'] = $variables['index']->loadItems(array_keys($variables['results']['results'])); - } -} - -/** - * Theme function for displaying search results. - * - * @param array $variables - * An associative array containing: - * - index: The index this search was executed on. - * - results: An array of search results, as returned by - * SearchApiQueryInterface::execute(). - * - items: The loaded items for all results, in an array keyed by ID. - * - view_mode: The view mode to use for displaying the individual results, - * or the special mode "search_api_page_result" to use the theme function - * of the same name. - * - keys: The keywords of the executed search. - */ -function theme_search_api_page_results(array $variables) { - drupal_add_css(drupal_get_path('module', 'search_api_page') . '/search_api_page.css'); - - $index = $variables['index']; $results = $variables['results']; - $items = $variables['items']; $keys = $variables['keys']; - $output = '

' . format_plural($results['result count'], - 'The search found 1 result in @sec seconds.', - 'The search found @count results in @sec seconds.', - array('@sec' => round($results['performance']['complete'], 3))) . '

'; + $variables['items'] = $variables['index']->loadItems(array_keys($variables['results']['results'])); + $variables['result_count'] = $results['result count']; + $variables['sec'] = round($results['performance']['complete'], 3); + $variables['search_performance'] = array( + '#theme' => 'search_performance', + '#markup' => format_plural( + $results['result count'], + 'The search found 1 result in @sec seconds.', + 'The search found @count results in @sec seconds.', + array('@sec' => $variables['sec']) + ), + '#prefix' => '

', + '#suffix' => '

', + ); - if (!$results['result count']) { - $output .= "\n

" . t('Your search yielded no results') . "

\n"; - return $output; - } - - $output .= "\n

" . t('Search results') . "

\n"; - - if ($variables['view_mode'] == 'search_api_page_result') { - $output .= '
    '; - foreach ($results['results'] as $item) { - $output .= '
  1. ' . theme('search_api_page_result', array('index' => $index, 'result' => $item, 'item' => isset($items[$item['id']]) ? $items[$item['id']] : NULL, 'keys' => $keys)) . '
  2. '; - } - $output .= '
'; - } - else { - // This option can only be set when the items are entities. - $output .= '
'; - $render = entity_view($index->item_type, $items, $variables['view_mode']); - $output .= render($render); - $output .= '
'; - } - - return $output; + $variables['search_results'] = array( + '#theme' => 'search_results_list', + 'results' => $variables['results']['results'], + 'items' => $variables['items'], + 'index' => $variables['index'], + 'type' => 'ul', + ); } /** - * Theme function for displaying search results. + * Process variables for search-result.tpl.php. * - * @param array $variables - * An associative array containing: - * - index: The index this search was executed on. - * - result: One item of the search results, an array containing the keys - * 'id' and 'score'. - * - item: The loaded item corresponding to the result. - * - keys: The keywords of the executed search. + * The $variables array contains the following arguments: + * - $result + * + * @see search_api_page-result.tpl.php */ -function theme_search_api_page_result(array $variables) { +function template_preprocess_search_api_page_result(&$variables) { $index = $variables['index']; - $id = $variables['result']['id']; + $variables['id'] = $variables['result']['id']; + $variables['excerpt'] = $variables['result']['excerpt']; $item = $variables['item']; $wrapper = $index->entityWrapper($item, FALSE); - $url = $index->datasource()->getItemUrl($item); - $name = $index->datasource()->getItemLabel($item); + $variables['url'] = $index->datasource()->getItemUrl($item); + $variables['title'] = $index->datasource()->getItemLabel($item); - if (!empty($variables['result']['excerpt'])) { - $text = $variables['result']['excerpt']; + if (!empty($variables['excerpt'])) { + $text = $variables['excerpt']; } else { $fields = $index->options['fields']; @@ -241,10 +216,24 @@ function theme_search_api_page_result(array $variables) { } } - $output = '

' . ($url ? l($name, $url['path'], $url['options']) : check_plain($name)) . "

\n"; - if ($text) { - $output .= $text; + // Check for existence. User search does not include snippets. + $variables['snippet'] = isset($text) ? $text : ''; + + // Meta information + global $language; + if (isset($item->language) && $item->language != $language->language && $item->language != LANGUAGE_NONE) { + $variables['title_attributes_array']['xml:lang'] = $item->language; + $variables['content_attributes_array']['xml:lang'] = $item->language; } - return $output; + $info = array(); + if (!empty($item->name)) { + $info['user'] = $item->name; + } + if (!empty($item->created)) { + $info['date'] = format_date($item->created, 'short'); + } + // Provide separated and grouped meta information.. + $variables['info_split'] = $info; + $variables['info'] = implode(' - ', $info); } diff --git a/search_api_page.pages.inc.orig b/search_api_page.pages.inc.orig index 7b72a0db..90d5d0af 100644 --- a/search_api_page.pages.inc.orig +++ b/search_api_page.pages.inc.orig @@ -22,7 +22,9 @@ function search_api_page_view($id, $keys = NULL) { $page->options['per_page'] = (int) $_GET['per_page']; } - $ret['form'] = drupal_get_form('search_api_page_search_form', $page, $keys); + if (isset($page->options['result_page_search_form']) && $page->options['result_page_search_form']) { + $ret['form'] = drupal_get_form('search_api_page_search_form', $page, $keys); + } if ($keys) { try { From 476ccd94fe3f3b57187127c3a7d65186f1f051aa Mon Sep 17 00:00:00 2001 From: bachy Date: Fri, 5 Oct 2012 14:20:50 +0200 Subject: [PATCH 5/5] theme able results Signed-off-by: bachy --- .gitignore | 1 + search-api-page-results.tpl.php | 2 +- search_api_page.module | 9 +++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..efc2538e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +patch_commit_2030a6f810b1.patch \ No newline at end of file diff --git a/search-api-page-results.tpl.php b/search-api-page-results.tpl.php index 9bbf3430..3d1888ef 100644 --- a/search-api-page-results.tpl.php +++ b/search-api-page-results.tpl.php @@ -40,7 +40,7 @@ - +
item_type, $items, $variables['view_mode'])); ?>
diff --git a/search_api_page.module b/search_api_page.module index 73faaada..aec880b7 100755 --- a/search_api_page.module +++ b/search_api_page.module @@ -46,6 +46,15 @@ function search_api_page_menu() { } } + $ret['results']['#theme'] = 'materiobase_results'; + $ret['results']['#index'] = search_api_index_load($page->index_id); + $ret['results']['#results'] = $results; + $ret['results']['#view_mode'] = isset($page->options['view_mode']) ? $page->options['view_mode'] : 'search_api_page_result'; + $ret['results']['#keys'] = $keys; + $ret['results']['#page_machine_name'] = $page->machine_name; + + + return $items; }