diff options
Diffstat (limited to 'mod/search')
21 files changed, 703 insertions, 721 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 0041325ad..513d3a6b9 100644 --- a/mod/search/manifest.xml +++ b/mod/search/manifest.xml @@ -1,12 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> -<plugin_manifest> - <field key="author" value="Curverider Ltd, The MITRE Corporation" /> - <field key="version" value="1.7" /> - <field key="description" value="Allow search across entities of the site" /> - <field key="website" value="http://www.elgg.org/" /> - <field key="copyright" value="(C) Curverider 2008-2010, MITRE 2009" /> - <field key="licence" value="GNU Public License version 2" /> - <field key="elgg_version" value="2009030702" /> - <field key="elgg_install_state" value="enabled" /> - <field key="admin_interface" value="advanced" /> +<plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8"> + <name>Search</name> + <author>The MITRE Corporation, Core developers</author> + <version>1.8</version> + <category>bundled</category> + <description>Allow search across entities of the site</description> + <website>http://www.elgg.org/</website> + <copyright>See COPYRIGHT.txt</copyright> + <license>GNU General Public License version 2</license> + <requires> + <type>elgg_release</type> + <version>1.8</version> + </requires> + <activate_on_install>true</activate_on_install> </plugin_manifest> diff --git a/mod/search/index.php b/mod/search/pages/search/index.php index 705f6e0a8..9542e0751 100644 --- a/mod/search/index.php +++ b/mod/search/pages/search/index.php @@ -1,9 +1,8 @@ <?php /** - * Elgg core search. + * Elgg search page * - * @package Elgg - * @subpackage Core + * @todo much of this code should be pulled out into a library of functions */ // Search supports RSS @@ -14,9 +13,24 @@ $autofeed = true; $search_type = get_input('search_type', 'all'); // @todo there is a bug in get_input that makes variables have slashes sometimes. +// @todo is there an example query to demonstrate ^ // XSS protection is more important that searching for HTML. $query = stripslashes(get_input('q', get_input('tag', ''))); +$display_query = _elgg_get_display_query($query); + +// check that we have an actual query +if (!$query) { + $title = sprintf(elgg_echo('search:results'), "\"$display_query\""); + + $body = elgg_view_title(elgg_echo('search:search_error')); + $body .= elgg_echo('search:no_query'); + $layout = elgg_view_layout('one_sidebar', array('content' => $body)); + echo elgg_view_page($title, $layout); + + return; +} + // get limit and offset. override if on search dashboard, where only 2 // of each most recent entity types will be shown. $limit = ($search_type == 'all') ? 2 : get_input('limit', 10); @@ -41,7 +55,7 @@ switch ($sort) { break; } -$order = get_input('sort', 'desc'); +$order = get_input('order', 'desc'); if ($order != 'asc' && $order != 'desc') { $order = 'desc'; } @@ -64,9 +78,9 @@ $params = array( ); $types = get_registered_entity_types(); -$custom_types = trigger_plugin_hook('search_types', 'get_types', $params, array()); +$custom_types = elgg_trigger_plugin_hook('search_types', 'get_types', $params, array()); -// add submenu items for all and native types +// add sidebar items for all and native types // @todo should these maintain any existing type / subtype filters or reset? $data = htmlspecialchars(http_build_query(array( 'q' => $query, @@ -76,8 +90,9 @@ $data = htmlspecialchars(http_build_query(array( 'search_type' => 'all', //'friends' => $friends ))); -$url = elgg_get_site_url()."pg/search/?$data"; -add_submenu_item(elgg_echo('all'), $url); +$url = elgg_get_site_url() . "search?$data"; +$menu_item = new ElggMenuItem('all', elgg_echo('all'), $url); +elgg_register_menu_item('page', $menu_item); foreach ($types as $type => $subtypes) { // @todo when using index table, can include result counts on each of these. @@ -94,9 +109,9 @@ foreach ($types as $type => $subtypes) { 'friends' => $friends ))); - $url = elgg_get_site_url()."pg/search/?$data"; - - add_submenu_item(elgg_echo($label), $url); + $url = elgg_get_site_url()."search?$data"; + $menu_item = new ElggMenuItem($label, elgg_echo($label), $url); + elgg_register_menu_item('page', $menu_item); } } else { $label = "item:$type"; @@ -109,40 +124,26 @@ foreach ($types as $type => $subtypes) { 'friends' => $friends ))); - $url = elgg_get_site_url()."pg/search/?$data"; + $url = elgg_get_site_url() . "search?$data"; - add_submenu_item(elgg_echo($label), $url); + $menu_item = new ElggMenuItem($label, elgg_echo($label), $url); + elgg_register_menu_item('page', $menu_item); } } -// add submenu for custom searches +// add sidebar for custom searches foreach ($custom_types as $type) { $label = "search_types:$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()."pg/search/?$data"; - - add_submenu_item(elgg_echo($label), $url); -} - - -// check that we have an actual query -if (!$query) { - $body = elgg_view_title(elgg_echo('search:search_error')); - $body .= elgg_view('page_elements/elgg_content', array('body' => elgg_echo('search:no_query'))); - - $layout = elgg_view_layout('one_column_with_sidebar', $body); - page_draw($title, $layout); + $url = elgg_get_site_url()."search?$data"; - return; + $menu_item = new ElggMenuItem($label, elgg_echo($label), $url); + elgg_register_menu_item('page', $menu_item); } // start the actual search @@ -172,7 +173,7 @@ if ($search_type == 'all' || $search_type == 'entities') { $current_params['subtype'] = $subtype; $current_params['type'] = $type; - $results = trigger_plugin_hook('search', "$type:$subtype", $current_params, NULL); + $results = elgg_trigger_plugin_hook('search', "$type:$subtype", $current_params, NULL); if ($results === FALSE) { // someone is saying not to display these types in searches. continue; @@ -182,12 +183,15 @@ if ($search_type == 'all' || $search_type == 'entities') { // no results and not hooked. use default type search. // don't change the params here, since it's really a different subtype. // Will be passed to elgg_get_entities(). - $results = trigger_plugin_hook('search', $type, $current_params, array()); + $results = elgg_trigger_plugin_hook('search', $type, $current_params, array()); } if (is_array($results['entities']) && $results['count']) { - if ($view = search_get_search_view($current_params, 'listing')) { - $results_html .= elgg_view($view, array('results' => $results, 'params' => $current_params)); + if ($view = search_get_search_view($current_params, 'list')) { + $results_html .= elgg_view($view, array( + 'results' => $results, + 'params' => $current_params, + )); } } } @@ -197,15 +201,18 @@ if ($search_type == 'all' || $search_type == 'entities') { $current_params['type'] = $type; $current_params['subtype'] = ELGG_ENTITIES_NO_VALUE; - $results = trigger_plugin_hook('search', $type, $current_params, array()); + $results = elgg_trigger_plugin_hook('search', $type, $current_params, array()); if ($results === FALSE) { // someone is saying not to display these types in searches. continue; } if (is_array($results['entities']) && $results['count']) { - if ($view = search_get_search_view($current_params, 'listing')) { - $results_html .= elgg_view($view, array('results' => $results, 'params' => $current_params)); + if ($view = search_get_search_view($current_params, 'list')) { + $results_html .= elgg_view($view, array( + 'results' => $results, + 'params' => $current_params, + )); } } } @@ -221,10 +228,8 @@ 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 = trigger_plugin_hook('search', $type, $current_params, array()); + $results = elgg_trigger_plugin_hook('search', $type, $current_params, array()); if ($results === FALSE) { // someone is saying not to display these types in searches. @@ -232,8 +237,11 @@ if ($search_type != 'entities' || $search_type == 'all') { } if (is_array($results['entities']) && $results['count']) { - if ($view = search_get_search_view($current_params, 'listing')) { - $results_html .= elgg_view($view, array('results' => $results, 'params' => $current_params)); + if ($view = search_get_search_view($current_params, 'list')) { + $results_html .= elgg_view($view, array( + 'results' => $results, + 'params' => $current_params, + )); } } } @@ -241,13 +249,17 @@ if ($search_type != 'entities' || $search_type == 'all') { } // highlight search terms -$searched_words = search_remove_ignored_words($query, 'array'); -$highlighted_query = search_highlight_words($searched_words, $query); +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(sprintf(elgg_echo('search:results'), "\"$highlighted_query\"")); +$body = elgg_view_title(elgg_echo('search:results', array("\"$highlighted_query\""))); if (!$results_html) { - $body .= elgg_view('page_elements/contentwrapper', array('body' => elgg_echo('search:no_results'))); + $body .= elgg_view('search/no_results'); } else { $body .= $results_html; } @@ -258,6 +270,6 @@ if (!$results_html) { $layout_view = search_get_search_view($params, 'layout'); $layout = elgg_view($layout_view, array('params' => $params, 'body' => $body)); -$title = sprintf(elgg_echo('search:results'), "\"{$params['query']}\""); +$title = elgg_echo('search:results', array("\"$display_query\"")); -page_draw($title, $layout); +echo elgg_view_page($title, $layout); diff --git a/mod/search/search_hooks.php b/mod/search/search_hooks.php index 858c4577c..923cf0aa8 100644 --- a/mod/search/search_hooks.php +++ b/mod/search/search_hooks.php @@ -3,26 +3,27 @@ * 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) { - global $CONFIG; - $join = "JOIN {$CONFIG->dbprefix}objects_entity oe ON e.guid = oe.guid"; + $db_prefix = elgg_get_config('dbprefix'); + + $join = "JOIN {$db_prefix}objects_entity oe ON e.guid = oe.guid"; $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; @@ -34,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. @@ -52,27 +54,24 @@ 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) { - global $CONFIG; + $db_prefix = elgg_get_config('dbprefix'); $query = sanitise_string($params['query']); - $join = "JOIN {$CONFIG->dbprefix}groups_entity ge ON e.guid = ge.guid"; + $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); @@ -88,15 +87,16 @@ 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. foreach ($entities as $entity) { - $description = search_get_highlighted_relevant_substrings($entity->description, $query); - $entity->setVolatileData('search_matched_title', $description); - $name = search_get_highlighted_relevant_substrings($entity->name, $query); - $entity->setVolatileData('search_matched_description', $name); + $entity->setVolatileData('search_matched_title', $name); + + $description = search_get_highlighted_relevant_substrings($entity->description, $query); + $entity->setVolatileData('search_matched_description', $description); } return array( @@ -106,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) { - global $CONFIG; + $db_prefix = elgg_get_config('dbprefix'); $query = sanitise_string($params['query']); - $join = "JOIN {$CONFIG->dbprefix}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. @@ -147,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( @@ -165,16 +202,16 @@ 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) { - global $CONFIG; + $db_prefix = elgg_get_config('dbprefix'); $valid_tag_names = elgg_get_registered_tag_metadata_names(); @@ -201,13 +238,17 @@ 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 // out the matches later. - $params['joins'][] = "JOIN {$CONFIG->dbprefix}metadata md on e.guid = md.entity_guid"; - $params['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msn on md.name_id = msn.id"; - $params['joins'][] = "JOIN {$CONFIG->dbprefix}metastrings msv on md.value_id = msv.id"; + $params['joins'][] = "JOIN {$db_prefix}metadata md on e.guid = md.entity_guid"; + $params['joins'][] = "JOIN {$db_prefix}metastrings msn on md.name_id = msn.id"; + $params['joins'][] = "JOIN {$db_prefix}metastrings msv on md.value_id = msv.id"; $access = get_access_sql_suffix('md'); $sanitised_tags = array(); @@ -229,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. @@ -279,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); @@ -295,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'; @@ -308,24 +350,26 @@ 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) { - global $CONFIG; + $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( - "JOIN {$CONFIG->dbprefix}annotations a on e.guid = a.entity_guid", - "JOIN {$CONFIG->dbprefix}metastrings msn on a.name_id = msn.id", - "JOIN {$CONFIG->dbprefix}metastrings msv on a.value_id = msv.id" + "JOIN {$db_prefix}annotations a on e.guid = a.entity_guid", + "JOIN {$db_prefix}metastrings msn on a.name_id = msn.id", + "JOIN {$db_prefix}metastrings msv on a.value_id = msv.id" ); $fields = array('string'); @@ -336,16 +380,16 @@ 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'); $a_access = get_access_sql_suffix('a'); // @todo this can probably be done through the api.. - $q = "SELECT count(DISTINCT a.id) as total FROM {$CONFIG->dbprefix}annotations a - JOIN {$CONFIG->dbprefix}metastrings msn ON a.name_id = msn.id - JOIN {$CONFIG->dbprefix}metastrings msv ON a.value_id = msv.id - JOIN {$CONFIG->dbprefix}entities e ON a.entity_guid = e.guid + $q = "SELECT count(DISTINCT a.id) as total 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 + JOIN {$db_prefix}entities e ON a.entity_guid = e.guid WHERE msn.string IN ('generic_comment', 'group_topic_post') AND ($search_where) AND $e_access @@ -361,20 +405,31 @@ 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); } - - $q = "SELECT DISTINCT a.*, msv.string as comment FROM {$CONFIG->dbprefix}annotations a - JOIN {$CONFIG->dbprefix}metastrings msn ON a.name_id = msn.id - JOIN {$CONFIG->dbprefix}metastrings msv ON a.value_id = msv.id - JOIN {$CONFIG->dbprefix}entities e ON a.entity_guid = e.guid + + // 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 + JOIN {$db_prefix}entities e ON a.entity_guid = e.guid WHERE msn.string IN ('generic_comment', 'group_topic_post') AND ($search_where) AND $e_access AND $a_access $container_and - - LIMIT {$params['offset']}, {$params['limit']} + + $order_by + LIMIT $offset, $limit "; $comments = get_data($q); @@ -400,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; } @@ -416,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 791555a99..8a112a3a3 100644 --- a/mod/search/start.php +++ b/mod/search/start.php @@ -1,36 +1,33 @@ <?php /** - * Elgg core search. + * Elgg search plugin * - * @package Elgg - * @subpackage Core */ +elgg_register_event_handler('init','system','search_init'); + /** - * Initialise search helper functions. - * + * Initialize search plugin */ function search_init() { global $CONFIG; require_once 'search_hooks.php'; // page handler for search actions and results - register_page_handler('search','search_page_handler'); + elgg_register_page_handler('search', 'search_page_handler'); // register some default search hooks - register_plugin_hook('search', 'object', 'search_objects_hook'); - register_plugin_hook('search', 'user', 'search_users_hook'); - - // @todo pull this out into groups - register_plugin_hook('search', 'group', 'search_groups_hook'); + elgg_register_plugin_hook_handler('search', 'object', 'search_objects_hook'); + elgg_register_plugin_hook_handler('search', 'user', 'search_users_hook'); + elgg_register_plugin_hook_handler('search', 'group', 'search_groups_hook'); // tags and comments are a bit different. // register a search types and a hooks for them. - register_plugin_hook('search_types', 'get_types', 'search_custom_types_tags_hook'); - register_plugin_hook('search', 'tags', 'search_tags_hook'); + elgg_register_plugin_hook_handler('search_types', 'get_types', 'search_custom_types_tags_hook'); + elgg_register_plugin_hook_handler('search', 'tags', 'search_tags_hook'); - register_plugin_hook('search_types', 'get_types', 'search_custom_types_comments_hook'); - register_plugin_hook('search', 'comments', 'search_comments_hook'); + elgg_register_plugin_hook_handler('search_types', 'get_types', 'search_custom_types_comments_hook'); + elgg_register_plugin_hook_handler('search', 'comments', 'search_comments_hook'); // get server min and max allowed chars for ft searching $CONFIG->search_info = array(); @@ -49,19 +46,19 @@ function search_init() { } // add in CSS for search elements - elgg_extend_view('css', 'search/css'); + elgg_extend_view('css/elgg', 'search/css'); // extend view for elgg topbar search box - elgg_extend_view('header/extend', '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) { - global $CONFIG; // if there is no q set, we're being called from a legacy installation // it expects a search by tags. @@ -72,26 +69,34 @@ function search_page_handler($page) { //set_input('search_type', 'tags'); } - include_once('index.php'); + $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) { - global $CONFIG; +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) { @@ -107,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) { @@ -117,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); @@ -134,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(); @@ -224,7 +234,7 @@ function search_consolidate_substrings($offsets, $lengths) { $end_pos = $offset + $length; // find the next entry that doesn't overlap - while(array_key_exists($i+1, $offsets) && $end_pos > $offsets[$i+1]) { + while (array_key_exists($i+1, $offsets) && $end_pos > $offsets[$i+1]) { $i++; if (!array_key_exists($i, $offsets)) { break; @@ -253,8 +263,8 @@ function search_highlight_words($words, $string) { $replace_html = array( 'strong' => rand(10000, 99999), 'class' => rand(10000, 99999), - 'searchMatch' => rand(10000, 99999), - 'searchMatchColor' => rand(10000, 99999) + 'search-highlight' => rand(10000, 99999), + 'search-highlight-color' => rand(10000, 99999) ); foreach ($words as $word) { @@ -266,16 +276,17 @@ function search_highlight_words($words, $string) { $search = "/($word)/i"; + // @todo // must replace with placeholders in case one of the search terms is // in the html string. // later, will replace the placeholders with the actual html. // Yeah this is hacky. I'm tired. $strong = $replace_html['strong']; $class = $replace_html['class']; - $searchMatch = $replace_html['searchMatch']; - $searchMatchColor = $replace_html['searchMatchColor']; + $highlight = $replace_html['search-highlight']; + $color = $replace_html['search-highlight-color']; - $replace = "<$strong $class=\"$searchMatch $searchMatchColor{$i}\">$1</$strong>"; + $replace = "<$strong $class=\"$highlight $color{$i}\">$1</$strong>"; $string = preg_replace($search, $replace, $string); $i++; } @@ -330,12 +341,12 @@ function search_remove_ignored_words($query, $format = 'array') { * * @param array $results * @param array $params - * @param string $view_type = listing, entity or listing + * @param string $view_type = list, entity or layout * @return string */ function search_get_search_view($params, $view_type) { switch ($view_type) { - case 'listing': + case 'list': case 'entity': case 'layout': break; @@ -346,7 +357,7 @@ function search_get_search_view($params, $view_type) { $view_order = array(); - // check if there's a special search listing view for this type:subtype + // check if there's a special search list view for this type:subtype if (isset($params['type']) && $params['type'] && isset($params['subtype']) && $params['subtype']) { $view_order[] = "search/{$params['type']}/{$params['subtype']}/$view_type"; } @@ -361,7 +372,7 @@ function search_get_search_view($params, $view_type) { $view_order[] = "search/{$params['search_type']}/$view_type"; } - // finally default to a search listing default + // finally default to a search list default $view_order[] = "search/$view_type"; foreach ($view_order as $view) { @@ -457,7 +468,7 @@ function search_get_order_by_sql($entities_table, $type_table, $sort, $order) { default: case 'relevance': // default is relevance descending. - // acending relevancy is silly and complicated. + // ascending relevancy is silly and complicated. $on = ''; break; case 'created': @@ -489,6 +500,3 @@ function search_get_order_by_sql($entities_table, $type_table, $sort, $order) { return $order_by; } -/** Register init system event **/ - -register_elgg_event_handler('init','system','search_init'); diff --git a/mod/search/views/default/search/comments/entity.php b/mod/search/views/default/search/comments/entity.php index 279d7a94f..77e950843 100644 --- a/mod/search/views/default/search/comments/entity.php +++ b/mod/search/views/default/search/comments/entity.php @@ -1,23 +1,26 @@ <?php /** - * Elgg search entity + * Default search view for a comment * - * @package Elgg - * @subpackage Core + * @uses $vars['entity'] */ + $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('profile/icon', array('entity' => $owner, 'size' => 'tiny')); + $icon = elgg_view_entity_icon($owner, 'tiny'); } else { $icon = ''; } // @todo Sometimes we find comments on entities we can't display... if ($entity->getVolatileData('search_unavailable_entity')) { - $title = sprintf(elgg_echo('search:comment_on'), elgg_echo('search:unavailable_entity')); + $title = elgg_echo('search:comment_on', array(elgg_echo('search:unavailable_entity'))); // keep anchor for formatting. $title = "<a>$title</a>"; } else { @@ -35,23 +38,18 @@ if ($entity->getVolatileData('search_unavailable_entity')) { $title = elgg_echo('item:' . $entity->getType()); } - $title = sprintf(elgg_echo('search:comment_on'), $title); - $url = $entity->getURL() . '#comment_' . $entity->getVolatileData('search_match_annotation_id'); + $title = elgg_echo('search:comment_on', array($title)); + + // @todo this should use something like $comment->getURL() + $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); -echo <<<___END - <div class="search_listing clearfloat"> - <div class="search_listing_icon">$icon</div> - <div class="search_listing_info"> - <p class="entity_title">$title</p>$description - <p class="entity_subtext">$time</p> - </div> - </div> -___END; - -?> +$body = "<p class=\"mbn\">$title</p>$description"; +$body .= "<p class=\"elgg-subtext\">$time</p>"; + +echo elgg_view_image_block($icon, $body); diff --git a/mod/search/views/default/search/css.php b/mod/search/views/default/search/css.php index 557f94e21..30ff45172 100644 --- a/mod/search/views/default/search/css.php +++ b/mod/search/views/default/search/css.php @@ -2,79 +2,66 @@ /** * Elgg Search css * - * @package search */ ?> -.search_listing { - background:none; - border-bottom:1px dotted #CCCCCC; - clear:both; - display:block; - margin:0; - padding:5px 0 7px; - position:relative; +/********************************** +Search plugin +***********************************/ +.elgg-search-header { + bottom: 5px; + height: 23px; + position: absolute; + right: 0; +} +.elgg-search input[type=text] { + width: 230px; +} +.elgg-search input[type=submit] { + display: none; +} +.elgg-search input[type=text] { + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + + border: 1px solid #71b9f7; + color: white; + 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 -934px; +} +.elgg-search input[type=text]:focus, .elgg-search input[type=text]:active { + background-color: white; + background-position: 2px -916px; + border: 1px solid white; + color: #0054A7; } -.search_listing_icon { - float:left; - margin-left:3px; - margin-top:3px; -} -.search_listing_icon .avatar_menu_button img { - width: 15px; - margin:0; -} -.search_listing_info { - float:left; - margin-left:7px; - min-height:28px; - width:693px; -} -.search_listing_info p { - margin:0; -} -.search_listing_category_title { - margin-top:20px; -} -.search_listing_category_title h2 { - color:#666666; -} -.search_listing.more { - display: block; -} - -/* search matches */ -.searchtype { - background: #FFFACD; - color: black; +.search-list li { + padding: 5px 0 0; } -.searchtypes { - border: 1px #EEEEEE solid; - padding: 4px; - margin: 6px; +.search-heading-category { + margin-top: 20px; + color: #666666; } -.searchMatch { + +.search-highlight { background-color: #bbdaf7; } -.searchMatchColor1 { +.search-highlight-color1 { background-color: #bbdaf7; } -.searchMatchColor2 { +.search-highlight-color2 { background-color: #A0FFFF; } -.searchMatchColor3 { +.search-highlight-color3 { background-color: #FDFFC3; } -.searchMatchColor4 { - background-color: #cccccc; +.search-highlight-color4 { + background-color: #ccc; } -.searchMatchColor5 { +.search-highlight-color5 { background-color: #4690d6; } - -/* formatting for the search results */ - -.search_listing .item_timestamp { - font-style: italic; -} diff --git a/mod/search/views/default/search/entity.php b/mod/search/views/default/search/entity.php index 917ebe9d7..e04d08836 100644 --- a/mod/search/views/default/search/entity.php +++ b/mod/search/views/default/search/entity.php @@ -1,6 +1,6 @@ <?php /** - * Elgg search entity + * Default view for an entity returned in a search * * Display largely controlled by a set of overrideable volatile data: * - search_icon (defaults to entity icon) @@ -10,8 +10,7 @@ * - search_url (defaults to entity->getURL()) * - search_time (defaults to entity->time_updated or entity->time_created) * - * @package Elgg - * @subpackage Core + * @uses $vars['entity'] Entity returned in a search */ $entity = $vars['entity']; @@ -22,17 +21,13 @@ if (!$icon) { // @todo allow an option to switch to displaying the entity's icon instead. $type = $entity->getType(); if ($type == 'user' || $type == 'group') { - $icon = elgg_view('profile/icon', array('entity' => $entity, 'size' => 'tiny')); + $icon = elgg_view_entity_icon($entity, 'tiny'); } elseif ($owner = $entity->getOwnerEntity()) { - $icon = elgg_view('profile/icon', array('entity' => $owner, 'size' => 'tiny')); + $icon = elgg_view_entity_icon($owner, 'tiny'); } 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( - 'graphics/icon', array( - 'entity' => $entity, - 'size' => 'tiny', - )); + $icon = elgg_view_entity_icon($entity, 'tiny'); } } @@ -52,19 +47,11 @@ if (!$time) { $tu = $entity->time_updated; $time = elgg_view_friendly_time(($tu > $tc) ? $tu : $tc); } -?> - <div class="search_listing clearfloat"> - <div class="search_listing_icon"><?php echo $icon; ?></div> - <div class="search_listing_info"> - <p class="entity_title"><?php echo $title; ?></p> - <?php echo $description; ?> -<?php + +$body = "<p class=\"mbn\">$title</p>$description"; if ($extra_info) { -?> - <p class="entity_subtext"><?php echo $extra_info; ?></p> -<?php + $body .= "<p class=\"elgg-subtext\">$extra_info</p>"; } -?> - <p class="entity_subtext"><?php echo $time; ?></p> - </div> - </div> +$body .= "<p class=\"elgg-subtext\">$time</p>"; + +echo elgg_view_image_block($icon, $body); diff --git a/mod/search/views/default/search/entity_list.php b/mod/search/views/default/search/entity_list.php deleted file mode 100644 index 4a04aaf84..000000000 --- a/mod/search/views/default/search/entity_list.php +++ /dev/null @@ -1,64 +0,0 @@ -<?php -$context = $vars['context']; -$offset = $vars['offset']; -$entities = $vars['entities']; -$limit = $vars['limit']; -$count = $vars['count']; -$baseurl = $vars['baseurl']; -$context = $vars['context']; -$viewtype = $vars['viewtype']; -$pagination = $vars['pagination']; -$fullview = $vars['fullview']; - -$html = ""; -$nav = ""; -if (isset($vars['viewtypetoggle'])) { - $viewtypetoggle = $vars['viewtypetoggle']; -} else { - $viewtypetoggle = true; -} - -if ($context == "search" && $count > 0 && $viewtypetoggle) { - $nav .= elgg_view("navigation/viewtype",array( - - 'baseurl' => $baseurl, - 'offset' => $offset, - 'count' => $count, - 'viewtype' => $viewtype, - - )); -} - -if ($pagination) - $nav .= elgg_view('navigation/pagination',array( - - 'baseurl' => $baseurl, - 'offset' => $offset, - 'count' => $count, - 'limit' => $limit, - - )); - -if ($viewtype == "list") { - if (is_array($entities) && sizeof($entities) > 0) { - foreach($entities as $entity) { - // print out the entity - $ev = elgg_view_entity($entity, $fullview); - // then add the search decorations around it - $html .= elgg_view('search/listing', array('entity_view' => $ev, - 'search_types' => $entity->getVolatileData('search'))); - - } - } -} else if ($viewtype == "gallery") { - if (is_array($entities) && sizeof($entities) > 0) { - $html .= elgg_view("search/gallery",array('entities' => $entities)); - } -} - -if ($count) { - $html .= $nav; -} -echo $html; - -?> diff --git a/mod/search/views/default/search/gallery.php b/mod/search/views/default/search/gallery.php deleted file mode 100644 index e2c6875e0..000000000 --- a/mod/search/views/default/search/gallery.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - - /** - * Elgg gallery view - * - * @package Elgg - * @subpackage Core - - - */ - - $entities = $vars['entities']; - if (is_array($entities) && sizeof($entities) > 0) { - -?> - - <table class="entity_gallery"> - -<?php - - $col = 0; - foreach($entities as $entity) { - if ($col == 0) { - - echo "<tr>"; - - } - echo "<td class='entity_gallery_item'>"; - - $ev = elgg_view_entity($entity, $fullview); - - echo elgg_view('search/listing', array('entity_view' => $ev, - 'search_types' => $entity->getVolatileData('search'))); - - - echo "</td>"; - $col++; - if ($col > 3) { - echo "</tr>"; - $col = 0; - } - } - if ($col > 0) echo "</tr>"; - -?> - - </table> - -<?php - - } - -?>
\ No newline at end of file diff --git a/mod/search/views/default/search/gallery_listing.php b/mod/search/views/default/search/gallery_listing.php deleted file mode 100644 index e4b723242..000000000 --- a/mod/search/views/default/search/gallery_listing.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php -/** - * Elgg search listing: gallery view - * - * DEPRECATED VIEW: use entities/gallery_listing instead - * - * @package Elgg - * @subpackage Core - */ - - - echo elgg_view('entities/gallery_listing', $vars); - -?>
\ No newline at end of file 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/layout.php b/mod/search/views/default/search/layout.php index 732415f0e..eb597836e 100644 --- a/mod/search/views/default/search/layout.php +++ b/mod/search/views/default/search/layout.php @@ -1,9 +1,8 @@ <?php /** - * Elgg core search listing. - * - * @package Elgg - * @subpackage Core - */ + * The default search layout + * + * @uses $vars['body'] + */ -echo elgg_view_layout('one_column_with_sidebar', $vars['body']);
\ No newline at end of file +echo elgg_view_layout('one_sidebar', array('content' => $vars['body']));
\ No newline at end of file diff --git a/mod/search/views/default/search/listing.php b/mod/search/views/default/search/list.php index c9dda25f0..90aa28989 100644 --- a/mod/search/views/default/search/listing.php +++ b/mod/search/views/default/search/list.php @@ -1,9 +1,18 @@ <?php /** - * Elgg search listing + * List a section of search results corresponding in a particular type/subtype + * or search type (comments for example) * - * @package Elgg - * @subpackage Core + * @uses $vars['results'] Array of data related to search results including: + * - 'entities' Array of entities to be displayed + * - 'count' Total number of results + * @uses $vars['params'] Array of parameters including: + * - 'type' Entity type + * - 'subtype' Entity subtype + * - 'search_type' Type of search: 'entities', 'comments', 'tags' + * - 'offset' Offset in search results + * - 'limit' Number of results per page + * - 'pagination' Display pagination? */ $entities = $vars['results']['entities']; @@ -18,25 +27,30 @@ $query = http_build_query( 'q' => $vars['params']['query'], 'entity_type' => $vars['params']['type'], 'entity_subtype' => $vars['params']['subtype'], - 'limit' => get_input('limit', 10), - 'offset' => get_input('offset', 0), + 'limit' => $vars['params']['limit'], + 'offset' => $vars['params']['offset'], 'search_type' => $vars['params']['search_type'], //@todo include vars for sorting, order, and friend-only. ) ); -$url = elgg_get_site_url()."pg/search?$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. @@ -66,41 +80,36 @@ if (array_key_exists('search_type', $vars['params']) $type_str = $search_type_str; } -// get pagination -if (array_key_exists('pagination', $vars['params']) && $vars['params']['pagination']) { - $nav .= elgg_view('navigation/pagination',array( - 'baseurl' => $url, - 'offset' => $vars['params']['offset'], - 'count' => $vars['results']['count'], - 'limit' => $vars['params']['limit'], - )); -} else { - $nav = ''; -} - -// 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'; - $more_str = sprintf(elgg_echo('search:more'), $count, $type_str); - $more_link = "<div class='search_listing'><a href=\"$url\">$more_str</a></div>"; +if ($show_more) { + $more_str = elgg_echo('search:more', array($count, $type_str)); + $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 = ''; } -$body = "<div class='search_listing_category_title'>".elgg_view_title($type_str)."</div>"; - -foreach ($entities as $entity) { - if ($view = search_get_search_view($vars['params'], 'entity')) { +// @todo once elgg_view_title() supports passing a $vars array use it +$body = elgg_view('page/elements/title', array( + 'title' => $type_str, + 'class' => 'search-heading-category', +)); + +$view = search_get_search_view($vars['params'], 'entity'); +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-item\">"; $body .= elgg_view($view, array( 'entity' => $entity, 'params' => $vars['params'], 'results' => $vars['results'] )); + $body .= '</li>'; } + $body .= $more_link; + $body .= '</ul>'; } + echo $body; -echo $more_link; echo $nav; diff --git a/mod/search/views/default/search/no_results.php b/mod/search/views/default/search/no_results.php new file mode 100644 index 000000000..0e9a5e295 --- /dev/null +++ b/mod/search/views/default/search/no_results.php @@ -0,0 +1,6 @@ +<?php +/** + * No results from search + */ + +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 df66d11f5..7474a280c 100644 --- a/mod/search/views/default/search/search_box.php +++ b/mod/search/views/default/search/search_box.php @@ -1,4 +1,10 @@ <?php +/** + * Search box + * + * @uses $vars['value'] Current search query + * @uses $vars['class'] Additional class + */ if (array_key_exists('value', $vars)) { $value = $vars['value']; @@ -8,15 +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 +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); + ?> -<div id="elgg_search"> - <form id="searchform" action="<?php echo elgg_get_site_url(); ?>pg/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="submit" value="<?php echo elgg_echo('search:go'); ?>" class="search_submit_button" /> - </fieldset> - </form> -</div>
\ No newline at end of file +<form class="<?php echo $class; ?>" action="<?php echo elgg_get_site_url(); ?>search" method="get"> + <fieldset> + <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> diff --git a/mod/search/views/default/search/startblurb.php b/mod/search/views/default/search/startblurb.php index 2b7230709..d6394da0d 100644 --- a/mod/search/views/default/search/startblurb.php +++ b/mod/search/views/default/search/startblurb.php @@ -1,7 +1,6 @@ <?php /** - * @package Elgg - * @subpackage Core + * @todo This doesn't appear to be called by anything. Look into removing. */ -echo sprintf(elgg_echo("tag:search:startblurb"), $vars['query']); +echo elgg_echo("tag:search:startblurb", array($vars['query'])); diff --git a/mod/search/views/rss/search/comments/entity.php b/mod/search/views/rss/search/comments/entity.php index 9bdc062c7..e47afec4a 100644 --- a/mod/search/views/rss/search/comments/entity.php +++ b/mod/search/views/rss/search/comments/entity.php @@ -1,14 +1,17 @@ <?php /** - * Elgg search comment view + * Search comment view for RSS feeds. * - * @package Elgg - * @subpackage Core + * @uses $vars['entity'] */ + $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; @@ -16,7 +19,7 @@ if ($author) { // @todo Sometimes we find comments on entities we can't display... if ($entity->getVolatileData('search_unavailable_entity')) { - $title = sprintf(elgg_echo('search:comment_on'), elgg_echo('search:unavailable_entity')); + $title = elgg_echo('search:comment_on', array(elgg_echo('search:unavailable_entity'))); } else { if ($entity->getType() == 'object') { $title = $entity->title; @@ -32,13 +35,13 @@ if ($entity->getVolatileData('search_unavailable_entity')) { $title = elgg_echo('item:' . $entity->getType()); } - $title = sprintf(elgg_echo('search:comment_on'), $title); + $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/entity.php b/mod/search/views/rss/search/entity.php index 54f241982..10d28e8e1 100644 --- a/mod/search/views/rss/search/entity.php +++ b/mod/search/views/rss/search/entity.php @@ -1,10 +1,8 @@ <?php /** - * Elgg core search. * Search entity view for RSS feeds. * - * @package Elgg - * @subpackage Core + * @uses $vars['entity'] */ if (!array_key_exists('entity', $vars) || !($vars['entity'] instanceof ElggEntity)) { diff --git a/mod/search/views/rss/search/layout.php b/mod/search/views/rss/search/layout.php index d722ccaeb..2c255a9cc 100644 --- a/mod/search/views/rss/search/layout.php +++ b/mod/search/views/rss/search/layout.php @@ -1,5 +1,6 @@ <?php +/** + * Search layout for RSS + */ echo $vars['body']; - -?>
\ No newline at end of file diff --git a/mod/search/views/rss/search/listing.php b/mod/search/views/rss/search/list.php index 92f6553b9..32082fd31 100644 --- a/mod/search/views/rss/search/listing.php +++ b/mod/search/views/rss/search/list.php @@ -1,10 +1,9 @@ <?php /** - * Elgg core search. - * Search listing view for RSS feeds. + * List a section of search results for RSS feeds. * - * @package Elgg - * @subpackage Core + * @uses $vars['results'] + * @uses $vars['params'] */ $entities = $vars['results']['entities']; |
