diff options
Diffstat (limited to 'mod/search')
| -rw-r--r-- | mod/search/README.txt | 454 | ||||
| -rw-r--r-- | mod/search/manifest.xml | 7 | ||||
| -rw-r--r-- | mod/search/pages/search/index.php | 19 | ||||
| -rw-r--r-- | mod/search/search_hooks.php | 209 | ||||
| -rw-r--r-- | mod/search/start.php | 26 | ||||
| -rw-r--r-- | mod/search/views/default/search/comments/entity.php | 11 | ||||
| -rw-r--r-- | mod/search/views/default/search/css.php | 10 | ||||
| -rw-r--r-- | mod/search/views/default/search/entity.php | 2 | ||||
| -rw-r--r-- | mod/search/views/default/search/header.php | 6 | ||||
| -rw-r--r-- | mod/search/views/default/search/list.php | 23 | ||||
| -rw-r--r-- | mod/search/views/default/search/no_results.php | 2 | ||||
| -rw-r--r-- | mod/search/views/default/search/search_box.php | 23 | ||||
| -rw-r--r-- | mod/search/views/rss/search/comments/entity.php | 11 | ||||
| -rw-r--r-- | mod/search/views/rss/search/list.php (renamed from mod/search/views/rss/search/listing.php) | 0 |
14 files changed, 453 insertions, 350 deletions
diff --git a/mod/search/README.txt b/mod/search/README.txt index fe87f3334..ac5930e5f 100644 --- a/mod/search/README.txt +++ b/mod/search/README.txt @@ -1,262 +1,276 @@ += Elgg Search = + Full text search developer's reference. -CONTENTS: - 1. Overview - 2. Search and Custom Plugins - 3. Controlling Search Results - 3.1 Entities Returned - 3.2 Custom Search - 4. Controlling Search Views - 4.1 Entities - 4.2 Listing - 4.3 Layout - 5. Hints and Quirks +== Contents == +1. Overview +2. Search and Custom Plugins +3. Controlling Search Results + 1. Entities Returned + 2. Custom Search +4. Controlling Search Views + 1. Entities + 2. Listing + 3. Layout +5. Hints and Quirks -1. OVERVIEW - * All entities are searched through title and description using - MySQL's native fulltext search when possible, and LIKE %...% when not. - This can be overridden on a type/subtype basis. - - * Entities are displayed in a standard list view consisting of a - title, blurb, and icon of the owning entity. This can be overridden - on a type/subtype basis. - - * Search is separated based upon types/subtypes pairs and any - registered custom search. - - * METADATA, ANNOTATIONS, AND PRIVATE DATA ARE NOT SEARCHED. - These are used in a variety of ways by plugin authors and generally - should not be displayed. There are exceptions (profile fields and - comments) but if a plugin needs to match against metadata, - annotations, or private data it must register a search hook itself. +== 1. Overview == +All entities are searched through title and description using +MySQL's native fulltext search when possible, and `LIKE %...%` when not. +This can be overridden on a type/subtype basis. -2. SEARCH AND CUSTOM PLUGINS +Entities are displayed in a standard list view consisting of a +title, blurb, and icon of the owning entity. This can be overridden +on a type/subtype basis. - * To appear in search you must register your entity type and subtype - by saying in your plugin's init function: - - register_entity_type($type, $subtype); - - If you are extending ElggObject with your own class, it is also advised - to add a subtype in your plugin's run_once function by saying: - - add_subtype($type, $subtype, $class); +Search is separated based upon types/subtypes pairs and any +registered custom search. - * If your plugin uses ElggEntity's standard title and description, - and you don't need a custom display, there is nothing else you need - to do for your results to appear in search. If you would like more - granular control of search, continue below. +**METADATA, ANNOTATIONS, AND PRIVATE DATA ARE NOT SEARCHED BY DEFAULT!** -3.0 CONTROLLING SEARCH RESULTS +These are used in a variety of ways by plugin authors and generally +should not be displayed. There are exceptions (profile fields and +comments) but if a plugin needs to match against metadata, +annotations, or private data it must register a search hook itself. - * Search results can be controlled at a object:subtype level. - - * You can specify your own search types by responding to a hook. - -3.1 CONTROLLING SEARCH RESULTS - ENTITIES RETURNED +== 2. Search and Custom Plugins == - * You can override the default search by responding to the search/type - or search/type:subtype hook. Generally, you will be replying to - search/object:subtype. +To appear in search you must register your entity type and subtype +by saying in your plugin's init function: - * Search will first trigger a hook for search/type:subtype. If no - results are returned (but not FALSE, see below) a hook for search/type - will be triggered. - - * FALSE returned for any search hook will halt results for that - type/subtype. - - * Register plugin hooks like this: - - register_plugin_hook('search', 'object:my_subtype', - 'my_subtype_search_hook'); - - * The hooked function is provided with details about the search query - in $param. These include: - query - offset - limit - search_type - type - Entity type. (Not applicable for custom searches) - subtype - Entity subtype. (Not applicable for custom searches) - owner_guid - friends - Should only entities by friends of the logged in - user be searched? (@todo) - pagination - Show pagination? - - * The hooked function should respond to search triggers with the - following: - array( - 'count' => A count of ALL entities found, - 'entities' => An array of entities. - ) - - This information is passed directly to the search view, so if you are - registering your own custom hook, you can provide more - information to display in your custom view. - - * For each entity in the returned array, search expects two pieces of - volatile data: search_matched_title and search_matched_description. - Set these by saying: - - $entity->setVolatileData('data_name', 'data_value'); - - Again, if you are customizing your search views, you can add anything - you need. + register_entity_type($type, $subtype); +If your plugin uses ElggEntity's standard title and description, +and you don't need a custom display, there is nothing else you need +to do for your results to appear in search. If you would like more +granular control of search, continue below. -3.2 CONTROLLING SEARCH RESULTS - CUSTOM SEARCH - - * Non-entities, including information from 3rd party applications, - can easily be included in search by registering a custom search hook - that responds to the search_types/get_types trigger: - - register_plugin_hook('search_types', 'get_types', - 'my_custom_search_hook_function'); + +== 3. Controlling Search Results == + +Search results can be controlled at a object:subtype level. - In this function, append to the array sent in $value with the name of - your custom search: +You can specify your own search types by responding to a hook. + + +=== 3.1 Controlling Search Results - Entities Returned === + +You can override the default search by responding to the search/type +or search/type:subtype hook. Generally, you will be replying to +search/object:subtype. + +Search will first trigger a hook for search/type:subtype. If no +results are returned (but not FALSE, see below) a hook for search/type +will be triggered. + +FALSE returned for any search hook will halt results for that +type/subtype. + +Register plugin hooks like this: + + register_plugin_hook('search', 'object:my_subtype', + 'my_subtype_search_hook'); + +The hooked function is provided with details about the search query +in $param. These include: + +* query +* offset +* limit +* search_type +* type - Entity type. (Not applicable for custom searches) +* subtype - Entity subtype. (Not applicable for custom searches) +* owner_guid +* friends - Should only entities by friends of the logged in + user be searched? (@todo) +* pagination - Show pagination? + +The hooked function should respond to search triggers with the +following: + + array( + 'count' => A count of ALL entities found, + 'entities' => An array of entities. + ) + +This information is passed directly to the search view, so if you are +registering your own custom hook, you can provide more +information to display in your custom view. + +For each entity in the returned array, search expects two pieces of +volatile data: search_matched_title and search_matched_description. +Set these by saying: + + $entity->setVolatileData('data_name', 'data_value'); + +Again, if you are customizing your search views, you can add anything +you need. + + +=== 3.2 Controlling Search Results - Custom Search === - function my_custom_search_hook_function($hook, $type, +Non-entities, including information from 3rd party applications, +can easily be included in search by registering a custom search hook +that responds to the search_types/get_types trigger: + + register_plugin_hook('search_types', 'get_types', + 'my_custom_search_hook_function'); + +In this function, append to the array sent in $value with the name of +your custom search: + + function my_custom_search_hook_function($hook, $type, $value, $params) { - $value[] = 'my_custom_search'; - return $value; - } - - Search will trigger a hook for search/my_custom_search, which your - plugin should respond to as detailed in section 3.1 above. + $value[] = 'my_custom_search'; + return $value; + } -4.0 CONTROLLING SEARCH VIEWS - * Three types views are used for displaying search: entity, listing, - and layout. +Search will trigger a hook for search/my_custom_search, which your +plugin should respond to as detailed in section 3.1 above. - * Each view has a default that standardizes the display of entities - regardless of type, subtype, or search type. - * The entity and listing views can be customized based upon a type, - subtype, or custom search type of the results. +== 4.0 Controlling Search Views == +Three types views are used for displaying search: entity, listing, +and layout. - * The layout view can be customized based upon the original search - type. NB: This can be different to the types for the results. +Each view has a default that standardizes the display of entities +regardless of type, subtype, or search type. - * The entity view controls how each individual result is formatted. +The entity and listing views can be customized based upon a type, +subtype, or custom search type of the results. - * The listing view control how each group of listings is formatted. +The layout view can be customized based upon the original search +type. NB: This can be different to the types for the results. - * The listing layout controls how each full result set is formatted. +The entity view controls how each individual result is formatted. +The listing view controls how each group of listings is formatted. -4.1 CONTROLLING SEARCH VIEWS - ENTITIES +The listing layout controls how each full result set is formatted. - * The default view for entities is search/entity. - * Search views are separate from the object/entity views because - view types might not match entity types. - - * The default search listing view iterates through each entity - found and passes to the entity view. See 3.3 for more information - about listing views. - - * Entity views are discovered in the following order. The first search - view found is used. - search/type/subtype/entity (For entity-based searches only) - search/type/entity - search/entity - - * The following parameters are passed in $vars to the entity view by - the default listing view: - entity => The current returned entity - results => The results from the search/type:subtype hook - params => The params passed to the search/type:subtype hook - - * Example: To create an entity view for an ElggObject of subtype blog, - create a file called: - views/default/search/object/blog/entity.php - - To create an entity view for a custom search mysearch, create a file - called: - views/default/search/mysearch/entity.php - - -4.2 CONTROLLING SEARCH VIEWS - LISTING +=== 4.1 Controlling Search Views - Entities === + +The default view for entities is search/entity. + +Search views are separate from the object/entity views because +view types might not match entity types. + +The default search listing view iterates through each entity +found and passes to the entity view. See 3.3 for more information +about listing views. + +Entity views are discovered in the following order. The first search +view found is used. + + search/type/subtype/entity (For entity-based searches only) + search/type/entity + search/entity - * The default search view is search/listing. +The following parameters are passed in $vars to the entity view by +the default listing view: + + entity => The current returned entity + results => The results from the search/type:subtype hook + params => The params passed to the search/type:subtype hook + +Example: To create an entity view for an ElggObject of subtype blog, +create a file called: + + views/default/search/object/blog/entity.php + +To create an entity view for a custom search mysearch, create a file +called: + + views/default/search/mysearch/entity.php - * For each entity in the returned array, search expects two pieces of - volatile data: search_matched_title and search_matched_description. - * Listing views are discovered in the following order. The first - search view found is used. - search/type/subtype/listing (For entity-based searches only) - search/type/listing - search/listing - - * The view is called with the following in $vars: - results => The results from the search/type:subtype hook - params => The params passed to the search/type:subtype hook - - * Example: To create a listing view for ElggObjects with the subtype - of blog, create a file called: - views/default/search/object/blog/listing.php - - To create a listing view for the custom search mysearch, create a file - called: - views/default/search/mysearch/listing.php +=== 4.2 Controlling Search Views - Listing + +The default search view is search/listing. + +For each entity in the returned array, search expects two pieces of +volatile data: search_matched_title and search_matched_description. + +Listing views are discovered in the following order. The first +search view found is used. + search/type/subtype/listing (For entity-based searches only) + search/type/listing + search/listing + +The view is called with the following in $vars: + results => The results from the search/type:subtype hook + params => The params passed to the search/type:subtype hook + +Example: To create a listing view for ElggObjects with the subtype +of blog, create a file called: + + views/default/search/object/blog/listing.php + +To create a listing view for the custom search mysearch, create a file +called: + + views/default/search/mysearch/listing.php -4.3 CONTROLLING SEARCH VIEWS - LAYOUT - - * The default layout view for search is search/layout, which calls - to elgg_view_layout(two_column_left_sidebar', '', $entity_results); +=== 4.3 Controlling Search Views - Layout === - * Layouts can be overridden only when not searching all entities. - - * Layout views are discovered in the following order. The first search - view found is used. - search/type/subtype/layout (For entity-based searches only) - search/type/layout - search/layout - - * The following parameters are passed in $vars to the layout view: - body => The HTML formatted list of results. - params => The original params for the search. - - * Example: To create a layout view for ElggObjects with the subtype - of blog, create a file called: - views/default/search/object/blog/layout.php - - To create a layout view for the custom search mysearch, create a file - called: - views/default/search/mysearch/layout.php +The default layout view for search is search/layout, which calls +to `elgg_view_layout(two_column_left_sidebar', '', $entity_results);` +Layouts can be overridden only when not searching all entities. -5. HINTS AND QUIRKS +Layout views are discovered in the following order. The first search +view found is used. - * Use search_get_relevant_substring() to extract and highlight - relevant substrings for the search_match_title and description. - - * If searching in 3rd party applications, create a temporary - ElggObject to hold the results. No need to save it since search - uses volatile data. - $entity = new ElggObject(); - $entity->owner_guid = use_magic_to_match_to_a_real_user(); - $entity->setVolatileData('search_matched_title', - '3rd Party Integration'); - $entity->setVolatileData('search_matched_description', - 'Searching is fun!'); - - return array( - 'count' => $count, - 'entities' => array($entity) - ); + search/type/subtype/layout (For entity-based searches only) + search/type/layout + search/layout + +The following parameters are passed in $vars to the layout view: + + body => The HTML formatted list of results. + params => The original params for the search. + +Example: To create a layout view for ElggObjects with the subtype +of blog, create a file called: + + views/default/search/object/blog/layout.php + +To create a layout view for the custom search mysearch, create a file +called: + + views/default/search/mysearch/layout.php + + +== 5. Hints and Quirks == + +Use search_get_relevant_substring() to extract and highlight +relevant substrings for the search_match_title and description. + +If searching in 3rd party applications, create a temporary +ElggObject to hold the results. No need to save it since search +uses volatile data. + + $entity = new ElggObject(); + $entity->owner_guid = use_magic_to_match_to_a_real_user(); + $entity->setVolatileData('search_matched_title', + '3rd Party Integration'); + $entity->setVolatileData('search_matched_description', + 'Searching is fun!'); + + return array( + 'count' => $count, + 'entities' => array($entity) + ); - * MySQL's fulltext engine returns *ZERO* rows if more than 50% of - the rows searched match. +MySQL's fulltext engine returns *ZERO* rows if more than 50% of +the rows searched match. - * The default search hooks for users and groups ignore subtypes. - See http://trac.elgg.org/elgg/ticket/1499 +The default search hooks for users and groups ignore subtypes. +See [GitHub issue 1499](https://github.com/elgg/elgg/issues/1499) diff --git a/mod/search/manifest.xml b/mod/search/manifest.xml index 053e22178..513d3a6b9 100644 --- a/mod/search/manifest.xml +++ b/mod/search/manifest.xml @@ -7,11 +7,10 @@ <description>Allow search across entities of the site</description> <website>http://www.elgg.org/</website> <copyright>See COPYRIGHT.txt</copyright> - <license>GNU Public License version 2</license> + <license>GNU General Public License version 2</license> <requires> - <type>elgg_version</type> - <version>2009030702</version> + <type>elgg_release</type> + <version>1.8</version> </requires> <activate_on_install>true</activate_on_install> - <admin_interface>advanced</admin_interface> </plugin_manifest> diff --git a/mod/search/pages/search/index.php b/mod/search/pages/search/index.php index c4e8d2219..9542e0751 100644 --- a/mod/search/pages/search/index.php +++ b/mod/search/pages/search/index.php @@ -17,10 +17,7 @@ $search_type = get_input('search_type', 'all'); // XSS protection is more important that searching for HTML. $query = stripslashes(get_input('q', get_input('tag', ''))); -// @todo - create function for sanitization of strings for display in 1.8 -// encode <,>,&, quotes and characters above 127 -$display_query = mb_convert_encoding($query, 'HTML-ENTITIES', 'UTF-8'); -$display_query = htmlspecialchars($display_query, ENT_QUOTES, 'UTF-8', false); +$display_query = _elgg_get_display_query($query); // check that we have an actual query if (!$query) { @@ -58,7 +55,7 @@ switch ($sort) { break; } -$order = get_input('sort', 'desc'); +$order = get_input('order', 'desc'); if ($order != 'asc' && $order != 'desc') { $order = 'desc'; } @@ -140,11 +137,7 @@ foreach ($custom_types as $type) { $data = htmlspecialchars(http_build_query(array( 'q' => $query, - 'entity_subtype' => $entity_subtype, - 'entity_type' => $entity_type, - 'owner_guid' => $owner_guid, 'search_type' => $type, - 'friends' => $friends ))); $url = elgg_get_site_url()."search?$data"; @@ -235,8 +228,6 @@ if ($search_type != 'entities' || $search_type == 'all') { $current_params = $params; $current_params['search_type'] = $type; - // custom search types have no subtype. - unset($current_params['subtype']); $results = elgg_trigger_plugin_hook('search', $type, $current_params, array()); @@ -258,7 +249,11 @@ if ($search_type != 'entities' || $search_type == 'all') { } // highlight search terms -$searched_words = search_remove_ignored_words($display_query, 'array'); +if ($search_type == 'tags') { + $searched_words = array($display_query); +} else { + $searched_words = search_remove_ignored_words($display_query, 'array'); +} $highlighted_query = search_highlight_words($searched_words, $display_query); $body = elgg_view_title(elgg_echo('search:results', array("\"$highlighted_query\""))); diff --git a/mod/search/search_hooks.php b/mod/search/search_hooks.php index 428d6f700..923cf0aa8 100644 --- a/mod/search/search_hooks.php +++ b/mod/search/search_hooks.php @@ -3,17 +3,17 @@ * Elgg core search. * * @package Elgg - * @subpackage Core + * @subpackage Search */ /** - * Return default results for searches on objects. + * Get objects that match the search parameters. * - * @param unknown_type $hook - * @param unknown_type $type - * @param unknown_type $value - * @param unknown_type $params - * @return unknown_type + * @param string $hook Hook name + * @param string $type Hook type + * @param array $value Empty array + * @param array $params Search parameters + * @return array */ function search_objects_hook($hook, $type, $value, $params) { @@ -23,7 +23,7 @@ function search_objects_hook($hook, $type, $value, $params) { $params['joins'] = array($join); $fields = array('title', 'description'); - $where = search_get_where_sql('oe', $fields, $params, FALSE); + $where = search_get_where_sql('oe', $fields, $params); $params['wheres'] = array($where); $params['count'] = TRUE; @@ -35,6 +35,7 @@ function search_objects_hook($hook, $type, $value, $params) { } $params['count'] = FALSE; + $params['order_by'] = search_get_order_by_sql('e', 'oe', $params['sort'], $params['order']); $entities = elgg_get_entities($params); // add the volatile data for why these entities have been returned. @@ -53,13 +54,13 @@ function search_objects_hook($hook, $type, $value, $params) { } /** - * Return default results for searches on groups. + * Get groups that match the search parameters. * - * @param unknown_type $hook - * @param unknown_type $type - * @param unknown_type $value - * @param unknown_type $params - * @return unknown_type + * @param string $hook Hook name + * @param string $type Hook type + * @param array $value Empty array + * @param array $params Search parameters + * @return array */ function search_groups_hook($hook, $type, $value, $params) { $db_prefix = elgg_get_config('dbprefix'); @@ -68,12 +69,9 @@ function search_groups_hook($hook, $type, $value, $params) { $join = "JOIN {$db_prefix}groups_entity ge ON e.guid = ge.guid"; $params['joins'] = array($join); - $fields = array('name', 'description'); - // force into boolean mode because we've having problems with the - // "if > 50% match 0 sets are returns" problem. - $where = search_get_where_sql('ge', $fields, $params, FALSE); + $where = search_get_where_sql('ge', $fields, $params); $params['wheres'] = array($where); @@ -89,6 +87,7 @@ function search_groups_hook($hook, $type, $value, $params) { } $params['count'] = FALSE; + $params['order_by'] = search_get_order_by_sql('e', 'ge', $params['sort'], $params['order']); $entities = elgg_get_entities($params); // add the volatile data for why these entities have been returned. @@ -107,39 +106,50 @@ function search_groups_hook($hook, $type, $value, $params) { } /** - * Return default results for searches on users. - * - * @todo add profile field MD searching + * Get users that match the search parameters. * - * @param unknown_type $hook - * @param unknown_type $type - * @param unknown_type $value - * @param unknown_type $params - * @return unknown_type + * Searches on username, display name, and profile fields + * + * @param string $hook Hook name + * @param string $type Hook type + * @param array $value Empty array + * @param array $params Search parameters + * @return array */ function search_users_hook($hook, $type, $value, $params) { $db_prefix = elgg_get_config('dbprefix'); $query = sanitise_string($params['query']); - $join = "JOIN {$db_prefix}users_entity ue ON e.guid = ue.guid"; - $params['joins'] = array($join); - -// $where = "(ue.guid = e.guid -// AND (ue.username LIKE '%$query%' -// OR ue.name LIKE '%$query%' -// ) -// )"; - + $params['joins'] = array( + "JOIN {$db_prefix}users_entity ue ON e.guid = ue.guid", + "JOIN {$db_prefix}metadata md on e.guid = md.entity_guid", + "JOIN {$db_prefix}metastrings msv ON n_table.value_id = msv.id" + ); + + // username and display name $fields = array('username', 'name'); $where = search_get_where_sql('ue', $fields, $params, FALSE); + + // profile fields + $profile_fields = array_keys(elgg_get_config('profile_fields')); - $params['wheres'] = array($where); + // get the where clauses for the md names + // can't use egef_metadata() because the n_table join comes too late. + $clauses = elgg_entities_get_metastrings_options('metadata', array( + 'metadata_names' => $profile_fields, + )); + + $params['joins'] = array_merge($clauses['joins'], $params['joins']); + // no fulltext index, can't disable fulltext search in this function. + // $md_where .= " AND " . search_get_where_sql('msv', array('string'), $params, FALSE); + $md_where = "(({$clauses['wheres'][0]}) AND msv.string LIKE '%$query%')"; + + $params['wheres'] = array("(($where) OR ($md_where))"); // override subtype -- All users should be returned regardless of subtype. $params['subtype'] = ELGG_ENTITIES_ANY_VALUE; - - $params['count'] = TRUE; + $params['count'] = true; $count = elgg_get_entities($params); // no need to continue if nothing here. @@ -148,15 +158,41 @@ function search_users_hook($hook, $type, $value, $params) { } $params['count'] = FALSE; + $params['order_by'] = search_get_order_by_sql('e', 'ue', $params['sort'], $params['order']); $entities = elgg_get_entities($params); // add the volatile data for why these entities have been returned. foreach ($entities as $entity) { - $username = search_get_highlighted_relevant_substrings($entity->username, $query); - $entity->setVolatileData('search_matched_title', $username); + + $title = search_get_highlighted_relevant_substrings($entity->name, $query); - $name = search_get_highlighted_relevant_substrings($entity->name, $query); - $entity->setVolatileData('search_matched_description', $name); + // include the username if it matches but the display name doesn't. + if (false !== strpos($entity->username, $query)) { + $username = search_get_highlighted_relevant_substrings($entity->username, $query); + $title .= " ($username)"; + } + + $entity->setVolatileData('search_matched_title', $title); + + $matched = ''; + foreach ($profile_fields as $md_name) { + $metadata = $entity->$md_name; + if (is_array($metadata)) { + foreach ($metadata as $text) { + if (stristr($text, $query)) { + $matched .= elgg_echo("profile:{$md_name}") . ': ' + . search_get_highlighted_relevant_substrings($text, $query); + } + } + } else { + if (stristr($metadata, $query)) { + $matched .= elgg_echo("profile:{$md_name}") . ': ' + . search_get_highlighted_relevant_substrings($metadata, $query); + } + } + } + + $entity->setVolatileData('search_matched_description', $matched); } return array( @@ -166,13 +202,13 @@ function search_users_hook($hook, $type, $value, $params) { } /** - * Return default results for searches on tags. + * Get entities with tags that match the search parameters. * - * @param unknown_type $hook - * @param unknown_type $type - * @param unknown_type $value - * @param unknown_type $params - * @return unknown_type + * @param string $hook Hook name + * @param string $type Hook type + * @param array $value Empty array + * @param array $params Search parameters + * @return array */ function search_tags_hook($hook, $type, $value, $params) { $db_prefix = elgg_get_config('dbprefix'); @@ -202,6 +238,10 @@ function search_tags_hook($hook, $type, $value, $params) { $search_tag_names = $valid_tag_names; } + if (!$search_tag_names) { + return array('entities' => array(), 'count' => $count); + } + // don't use elgg_get_entities_from_metadata() here because of // performance issues. since we don't care what matches at this point // use an IN clause to grab everything that matches at once and sort @@ -230,6 +270,7 @@ function search_tags_hook($hook, $type, $value, $params) { } $params['count'] = FALSE; + $params['order_by'] = search_get_order_by_sql('e', null, $params['sort'], $params['order']); $entities = elgg_get_entities($params); // add the volatile data for why these entities have been returned. @@ -280,7 +321,7 @@ function search_tags_hook($hook, $type, $value, $params) { } $tags_str = implode('. ', $matched_tags_strs); - $tags_str = search_get_highlighted_relevant_substrings($tags_str, $params['query']); + $tags_str = search_get_highlighted_relevant_substrings($tags_str, $params['query'], 30, 300, true); $entity->setVolatileData('search_matched_title', $title_str); $entity->setVolatileData('search_matched_description', $desc_str); @@ -296,11 +337,11 @@ function search_tags_hook($hook, $type, $value, $params) { /** * Register tags as a custom search type. * - * @param unknown_type $hook - * @param unknown_type $type - * @param unknown_type $value - * @param unknown_type $params - * @return unknown_type + * @param string $hook Hook name + * @param string $type Hook type + * @param array $value Array of custom search types + * @param array $params Search parameters + * @return array */ function search_custom_types_tags_hook($hook, $type, $value, $params) { $value[] = 'tags'; @@ -309,18 +350,20 @@ function search_custom_types_tags_hook($hook, $type, $value, $params) { /** - * Return default results for searches on comments. + * Get comments that match the search parameters. * - * @param unknown_type $hook - * @param unknown_type $type - * @param unknown_type $value - * @param unknown_type $params - * @return unknown_type + * @param string $hook Hook name + * @param string $type Hook type + * @param array $value Empty array + * @param array $params Search parameters + * @return array */ function search_comments_hook($hook, $type, $value, $params) { $db_prefix = elgg_get_config('dbprefix'); $query = sanitise_string($params['query']); + $limit = sanitise_int($params['limit']); + $offset = sanitise_int($params['offset']); $params['annotation_names'] = array('generic_comment', 'group_topic_post'); $params['joins'] = array( @@ -337,7 +380,7 @@ function search_comments_hook($hook, $type, $value, $params) { $container_and = ''; if ($params['container_guid'] && $params['container_guid'] !== ELGG_ENTITIES_ANY_VALUE) { - $container_and = 'AND e.container_guid = ' . sanitise_string($params['container_guid']); + $container_and = 'AND e.container_guid = ' . sanitise_int($params['container_guid']); } $e_access = get_access_sql_suffix('e'); @@ -362,9 +405,19 @@ function search_comments_hook($hook, $type, $value, $params) { // don't continue if nothing there... if (!$count) { - return array ('entities' => array(), 'count' => 0); + return array('entities' => array(), 'count' => 0); } - + + // no full text index on metastrings table + if ($params['sort'] == 'relevance') { + $params['sort'] = 'created'; + } + + $order_by = search_get_order_by_sql('a', null, $params['sort'], $params['order']); + if ($order_by) { + $order_by = "ORDER BY $order_by"; + } + $q = "SELECT DISTINCT a.*, msv.string as comment FROM {$db_prefix}annotations a JOIN {$db_prefix}metastrings msn ON a.name_id = msn.id JOIN {$db_prefix}metastrings msv ON a.value_id = msv.id @@ -374,8 +427,9 @@ function search_comments_hook($hook, $type, $value, $params) { AND $e_access AND $a_access $container_and - - LIMIT {$params['offset']}, {$params['limit']} + + $order_by + LIMIT $offset, $limit "; $comments = get_data($q); @@ -401,10 +455,17 @@ function search_comments_hook($hook, $type, $value, $params) { } $comment_str = search_get_highlighted_relevant_substrings($comment->comment, $query); - $entity->setVolatileData('search_match_annotation_id', $comment->id); - $entity->setVolatileData('search_matched_comment', $comment_str); - $entity->setVolatileData('search_matched_comment_owner_guid', $comment->owner_guid); - $entity->setVolatileData('search_matched_comment_time_created', $comment->time_created); + $comments_data = $entity->getVolatileData('search_comments_data'); + if (!$comments_data) { + $comments_data = array(); + } + $comments_data[] = array( + 'annotation_id' => $comment->id, + 'text' => $comment_str, + 'owner_guid' => $comment->owner_guid, + 'time_created' => $comment->time_created, + ); + $entity->setVolatileData('search_comments_data', $comments_data); $entities[] = $entity; } @@ -417,11 +478,11 @@ function search_comments_hook($hook, $type, $value, $params) { /** * Register comments as a custom search type. * - * @param unknown_type $hook - * @param unknown_type $type - * @param unknown_type $value - * @param unknown_type $params - * @return unknown_type + * @param string $hook Hook name + * @param string $type Hook type + * @param array $value Array of custom search types + * @param array $params Search parameters + * @return array */ function search_custom_types_comments_hook($hook, $type, $value, $params) { $value[] = 'comments'; diff --git a/mod/search/start.php b/mod/search/start.php index 18a0d59e3..8a112a3a3 100644 --- a/mod/search/start.php +++ b/mod/search/start.php @@ -14,7 +14,7 @@ function search_init() { require_once 'search_hooks.php'; // page handler for search actions and results - elgg_register_page_handler('search','search_page_handler'); + elgg_register_page_handler('search', 'search_page_handler'); // register some default search hooks elgg_register_plugin_hook_handler('search', 'object', 'search_objects_hook'); @@ -49,13 +49,14 @@ function search_init() { elgg_extend_view('css/elgg', 'search/css'); // extend view for elgg topbar search box - elgg_extend_view('page/elements/header', 'search/search_box'); + elgg_extend_view('page/elements/header', 'search/header'); } /** * Page handler for search * - * @param array $page Page elements from pain page handler + * @param array $page Page elements from core page handler + * @return bool */ function search_page_handler($page) { @@ -71,25 +72,31 @@ function search_page_handler($page) { $base_dir = elgg_get_plugins_path() . 'search/pages/search'; include_once("$base_dir/index.php"); + return true; } /** * Return a string with highlighted matched queries and relevant context - * Determins context based upon occurance and distance of words with each other. + * Determines context based upon occurance and distance of words with each other. * * @param string $haystack * @param string $query * @param int $min_match_context = 30 * @param int $max_length = 300 + * @param bool $tag_match Search is for tags. Don't ignore words. * @return string */ -function search_get_highlighted_relevant_substrings($haystack, $query, $min_match_context = 30, $max_length = 300) { +function search_get_highlighted_relevant_substrings($haystack, $query, $min_match_context = 30, $max_length = 300, $tag_match = false) { $haystack = strip_tags($haystack); $haystack_length = elgg_strlen($haystack); $haystack_lc = elgg_strtolower($haystack); - $words = search_remove_ignored_words($query, 'array'); + if (!$tag_match) { + $words = search_remove_ignored_words($query, 'array'); + } else { + $words = array(); + } // if haystack < $max_length return the entire haystack w/formatting immediately if ($haystack_length <= $max_length) { @@ -105,6 +112,7 @@ function search_get_highlighted_relevant_substrings($haystack, $query, $min_matc $word = elgg_strtolower($word); $count = elgg_substr_count($haystack_lc, $word); $word_len = elgg_strlen($word); + $haystack_len = elgg_strlen($haystack_lc); // find the start positions for the words if ($count > 1) { @@ -115,6 +123,10 @@ function search_get_highlighted_relevant_substrings($haystack, $query, $min_matc $stop = $pos + $word_len + $min_match_context; $lengths[] = $stop - $start; $offset += $pos + $word_len; + + if ($offset >= $haystack_len) { + break; + } } } else { $pos = elgg_strpos($haystack_lc, $word); @@ -132,7 +144,7 @@ function search_get_highlighted_relevant_substrings($haystack, $query, $min_matc $total_length = array_sum($offsets); $add_length = 0; - if ($total_length < $max_length) { + if ($total_length < $max_length && $offsets) { $add_length = floor((($max_length - $total_length) / count($offsets)) / 2); $starts = array(); diff --git a/mod/search/views/default/search/comments/entity.php b/mod/search/views/default/search/comments/entity.php index 005bb270c..77e950843 100644 --- a/mod/search/views/default/search/comments/entity.php +++ b/mod/search/views/default/search/comments/entity.php @@ -6,8 +6,11 @@ */ $entity = $vars['entity']; +$comments_data = $entity->getVolatileData('search_comments_data'); +$comment_data = array_shift($comments_data); +$entity->setVolatileData('search_comments_data', $comments_data); -$owner = get_entity($entity->getVolatileData('search_matched_comment_owner_guid')); +$owner = get_entity($comment_data['owner_guid']); if ($owner instanceof ElggUser) { $icon = elgg_view_entity_icon($owner, 'tiny'); @@ -38,12 +41,12 @@ if ($entity->getVolatileData('search_unavailable_entity')) { $title = elgg_echo('search:comment_on', array($title)); // @todo this should use something like $comment->getURL() - $url = $entity->getURL() . '#comment_' . $entity->getVolatileData('search_match_annotation_id'); + $url = $entity->getURL() . '#comment_' . $comment_data['annotation_id']; $title = "<a href=\"$url\">$title</a>"; } -$description = $entity->getVolatileData('search_matched_comment'); -$tc = $entity->getVolatileData('search_matched_comment_time_created');; +$description = $comment_data['text']; +$tc = $comment_data['time_created']; $time = elgg_view_friendly_time($tc); $body = "<p class=\"mbn\">$title</p>$description"; diff --git a/mod/search/views/default/search/css.php b/mod/search/views/default/search/css.php index 601536c81..30ff45172 100644 --- a/mod/search/views/default/search/css.php +++ b/mod/search/views/default/search/css.php @@ -8,16 +8,16 @@ /********************************** Search plugin ***********************************/ -.elgg-page-header .elgg-search { +.elgg-search-header { bottom: 5px; height: 23px; position: absolute; right: 0; } -.elgg-page-header .elgg-search input[type=text] { +.elgg-search input[type=text] { width: 230px; } -.elgg-page-header .elgg-search input[type=submit] { +.elgg-search input[type=submit] { display: none; } .elgg-search input[type=text] { @@ -30,11 +30,11 @@ Search plugin font-size: 12px; font-weight: bold; padding: 2px 4px 2px 26px; - background: transparent url(<?php echo elgg_get_site_url(); ?>_graphics/elgg_sprites.png) no-repeat 2px -718px; + background: transparent url(<?php echo elgg_get_site_url(); ?>_graphics/elgg_sprites.png) no-repeat 2px -934px; } .elgg-search input[type=text]:focus, .elgg-search input[type=text]:active { background-color: white; - background-position: 2px -700px; + background-position: 2px -916px; border: 1px solid white; color: #0054A7; } diff --git a/mod/search/views/default/search/entity.php b/mod/search/views/default/search/entity.php index 2193a70d8..e04d08836 100644 --- a/mod/search/views/default/search/entity.php +++ b/mod/search/views/default/search/entity.php @@ -27,7 +27,7 @@ if (!$icon) { } else { // display a generic icon if no owner, though there will probably be // other problems if the owner can't be found. - $icon = elgg_view_entity($entity, 'tiny'); + $icon = elgg_view_entity_icon($entity, 'tiny'); } } diff --git a/mod/search/views/default/search/header.php b/mod/search/views/default/search/header.php new file mode 100644 index 000000000..6f8654c13 --- /dev/null +++ b/mod/search/views/default/search/header.php @@ -0,0 +1,6 @@ +<?php +/** + * Search box in page header + */ + +echo elgg_view('search/search_box', array('class' => 'elgg-search-header'));
\ No newline at end of file diff --git a/mod/search/views/default/search/list.php b/mod/search/views/default/search/list.php index c5afaaa86..90aa28989 100644 --- a/mod/search/views/default/search/list.php +++ b/mod/search/views/default/search/list.php @@ -36,16 +36,21 @@ $query = http_build_query( $url = elgg_get_site_url() . "search?$query"; +$more_items = $vars['results']['count'] - ($vars['params']['offset'] + $vars['params']['limit']); + // get pagination -if (array_key_exists('pagination', $vars) && $vars['pagination']) { - $nav .= elgg_view('navigation/pagination',array( - 'baseurl' => $url, +if (array_key_exists('pagination', $vars['params']) && $vars['params']['pagination']) { + $nav = elgg_view('navigation/pagination', array( + 'base_url' => $url, 'offset' => $vars['params']['offset'], 'count' => $vars['results']['count'], 'limit' => $vars['params']['limit'], )); + $show_more = false; } else { + // faceted search page so no pagination $nav = ''; + $show_more = $more_items > 0; } // figure out what we're dealing with. @@ -75,14 +80,10 @@ if (array_key_exists('search_type', $vars['params']) $type_str = $search_type_str; } -// get any more links. -$more_check = $vars['results']['count'] - ($vars['params']['offset'] + $vars['params']['limit']); -$more = ($more_check > 0) ? $more_check : 0; - -if ($more) { - $title_key = ($more == 1) ? 'comment' : 'comments'; +if ($show_more) { $more_str = elgg_echo('search:more', array($count, $type_str)); - $more_link = "<li class='elgg-list-item'><a href=\"$url\">$more_str</a></li>"; + $more_url = elgg_http_remove_url_query_element($url, 'limit'); + $more_link = "<li class='elgg-item'><a href=\"$more_url\">$more_str</a></li>"; } else { $more_link = ''; } @@ -98,7 +99,7 @@ if ($view) { $body .= '<ul class="elgg-list search-list">'; foreach ($entities as $entity) { $id = "elgg-{$entity->getType()}-{$entity->getGUID()}"; - $body .= "<li id=\"$id\" class=\"elgg-list-item\">"; + $body .= "<li id=\"$id\" class=\"elgg-item\">"; $body .= elgg_view($view, array( 'entity' => $entity, 'params' => $vars['params'], diff --git a/mod/search/views/default/search/no_results.php b/mod/search/views/default/search/no_results.php index 74b5b2cfa..0e9a5e295 100644 --- a/mod/search/views/default/search/no_results.php +++ b/mod/search/views/default/search/no_results.php @@ -3,4 +3,4 @@ * No results from search */ -echo autop(elgg_echo('search:no_results')); +echo elgg_autop(elgg_echo('search:no_results')); diff --git a/mod/search/views/default/search/search_box.php b/mod/search/views/default/search/search_box.php index ff5910937..7474a280c 100644 --- a/mod/search/views/default/search/search_box.php +++ b/mod/search/views/default/search/search_box.php @@ -3,8 +3,7 @@ * Search box * * @uses $vars['value'] Current search query - * - * @todo Move javascript into something that extends elgg.js + * @uses $vars['class'] Additional class */ if (array_key_exists('value', $vars)) { @@ -15,20 +14,30 @@ if (array_key_exists('value', $vars)) { $value = elgg_echo('search'); } +$class = "elgg-search"; +if (isset($vars['class'])) { + $class = "$class {$vars['class']}"; +} + // @todo - why the strip slashes? $value = stripslashes($value); // @todo - create function for sanitization of strings for display in 1.8 // encode <,>,&, quotes and characters above 127 -$display_query = mb_convert_encoding($value, 'HTML-ENTITIES', 'UTF-8'); +if (function_exists('mb_convert_encoding')) { + $display_query = mb_convert_encoding($value, 'HTML-ENTITIES', 'UTF-8'); +} else { + // if no mbstring extension, we just strip characters + $display_query = preg_replace("/[^\x01-\x7F]/", "", $value); +} $display_query = htmlspecialchars($display_query, ENT_QUOTES, 'UTF-8', false); - ?> -<form class="elgg-search" action="<?php echo elgg_get_site_url(); ?>search" method="get"> +<form class="<?php echo $class; ?>" action="<?php echo elgg_get_site_url(); ?>search" method="get"> <fieldset> - <input type="text" size="21" name="q" value="<?php echo elgg_echo('search'); ?>" onblur="if (this.value=='') { this.value='<?php echo elgg_echo('search'); ?>' }" onfocus="if (this.value=='<?php echo elgg_echo('search'); ?>') { this.value='' };" class="search-input" /> + <input type="text" class="search-input" size="21" name="q" value="<?php echo $display_query; ?>" onblur="if (this.value=='') { this.value='<?php echo elgg_echo('search'); ?>' }" onfocus="if (this.value=='<?php echo elgg_echo('search'); ?>') { this.value='' };" /> + <input type="hidden" name="search_type" value="all" /> <input type="submit" value="<?php echo elgg_echo('search:go'); ?>" class="search-submit-button" /> </fieldset> -</form>
\ No newline at end of file +</form> diff --git a/mod/search/views/rss/search/comments/entity.php b/mod/search/views/rss/search/comments/entity.php index 869779f35..e47afec4a 100644 --- a/mod/search/views/rss/search/comments/entity.php +++ b/mod/search/views/rss/search/comments/entity.php @@ -6,9 +6,12 @@ */ $entity = $vars['entity']; +$comments_data = $entity->getVolatileData('search_comments_data'); +$comment_data = array_shift($comments_data); +$entity->setVolatileData('search_comments_data', $comments_data); $author_name = ''; -$comment_author_guid = $entity->getVolatileData('search_matched_comment_owner_guid'); +$comment_author_guid = $comment_data['owner_guid']; $author = get_user($comment_author_guid); if ($author) { $author_name = $author->name; @@ -34,11 +37,11 @@ if ($entity->getVolatileData('search_unavailable_entity')) { $title = elgg_echo('search:comment_on', array($title)); $title .= ' ' . elgg_echo('search:comment_by') . ' ' . $author_name; - $url = $entity->getURL() . '#annotation-' . $entity->getVolatileData('search_match_annotation_id'); + $url = $entity->getURL() . '#annotation-' . $comment_data['annotation_id']; } -$description = $entity->getVolatileData('search_matched_comment'); -$tc = $entity->getVolatileData('search_matched_comment_time_created');; +$description = $comment_data['text']; +$tc = $comment_data['time_created']; ?> diff --git a/mod/search/views/rss/search/listing.php b/mod/search/views/rss/search/list.php index 32082fd31..32082fd31 100644 --- a/mod/search/views/rss/search/listing.php +++ b/mod/search/views/rss/search/list.php |
