diff options
Diffstat (limited to 'mod/search')
27 files changed, 1668 insertions, 1289 deletions
diff --git a/mod/search/README.txt b/mod/search/README.txt new file mode 100644 index 000000000..ac5930e5f --- /dev/null +++ b/mod/search/README.txt @@ -0,0 +1,276 @@ += Elgg Search = + +Full text search developer's reference. + +== 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 BY DEFAULT!** + +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. + + +== 2. Search and Custom Plugins == + +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 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. Controlling Search Results == + +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 === + +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 === + +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. + + +== 4.0 Controlling Search Views == +Three types views are used for displaying search: entity, listing, +and layout. + +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. + +The layout view can be customized based upon the original search +type. NB: This can be different to the types for the results. + +The entity view controls how each individual result is formatted. + +The listing view controls how each group of listings is formatted. + +The listing layout controls how each full result set is formatted. + + +=== 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 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 + +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);` + +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 + + +== 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. + +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/index.php b/mod/search/index.php deleted file mode 100644 index ee6da32a8..000000000 --- a/mod/search/index.php +++ /dev/null @@ -1,375 +0,0 @@ -<?php - -// $search_type == all || entities || trigger plugin hook -$search_type = get_input('search_type', 'all'); - -// @todo there is a bug in get_input that makes variables have slashes sometimes. -$query = stripslashes(get_input('q', get_input('tag', '', FALSE), FALSE)); - -// 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); -$offset = ($search_type == 'all') ? 0 : get_input('offset', 0); - -$entity_type = get_input('entity_type', NULL); -$entity_subtype = get_input('entity_subtype', NULL); -$owner_guid = get_input('owner_guid', NULL); -$friends = (int)get_input('friends', 0); - -// set up search params -$params = array( - 'query' => $query, - 'offset' => $offset, - 'limit' => $limit, - 'search_type' => $search_type, - 'type' => $entity_type, - 'subtype' => $entity_subtype, -// 'tag_type' => $tag_type, - 'owner_guid' => $owner_guid, -// 'friends' => $friends -); - -$results_html = ''; -//$results_html .= elgg_view_title(elgg_echo('search:results')) . "<input type=\"text\" value=\"$query\" />"; -$results_html .= elgg_view_title(elgg_echo('search:results')); -$types = get_registered_entity_types(); -$custom_types = trigger_plugin_hook('search_types', 'get_types', $params, array()); - -// add submenu 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, - 'entity_subtype' => $subtype, - 'entity_type' => $type, - 'owner_guid' => $owner_guid, - 'search_type' => 'all', - 'friends' => $friends -))); -$url = "{$CONFIG->wwwroot}pg/search/?$data"; -add_submenu_item(elgg_echo('all'), $url); - -foreach ($types as $type => $subtypes) { - // @todo when using index table, can include result counts on each of these. - if (is_array($subtypes) && count($subtypes)) { - foreach ($subtypes as $subtype) { - $label = "item:$type:$subtype"; - - $data = htmlspecialchars(http_build_query(array( - 'q' => $query, - 'entity_subtype' => $subtype, - 'entity_type' => $type, - 'owner_guid' => $owner_guid, - 'search_type' => 'entities', - 'friends' => $friends - ))); - - $url = "{$CONFIG->wwwroot}pg/search/?$data"; - - add_submenu_item(elgg_echo($label), $url); - } - } else { - $label = "item:$type"; - - $data = htmlspecialchars(http_build_query(array( - 'q' => $query, - 'entity_type' => $type, - 'owner_guid' => $owner_guid, - 'search_type' => 'entities', - 'friends' => $friends - ))); - - $url = "{$CONFIG->wwwroot}pg/search/?$data"; - - add_submenu_item(elgg_echo($label), $url); - } -} - -// add submenu 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 = "{$CONFIG->wwwroot}pg/search/?$data"; - - add_submenu_item(elgg_echo($label), $url); -} - - -// check that we have an actual query -if (!$query) { - $body .= "No query."; - $layout = elgg_view_layout('two_column_left_sidebar', '', $body); - page_draw($title, $layout); - - return; -} - -if ($search_type == 'all' || $search_type == 'entities') { - // to pass the correct search type to the views - $params['search_type'] = 'entities'; - - // foreach through types. - // if a plugin returns FALSE for subtype ignore it. - // if a plugin returns NULL or '' for subtype, pass to generic type search function. - // if still NULL or '' or empty(array()) no results found. (== don't show??) - foreach ($types as $type => $subtypes) { - if ($search_type != 'all' && $entity_type != $type) { - continue; - } - - if (is_array($subtypes) && count($subtypes)) { - foreach ($subtypes as $subtype) { - // no need to search if we're not interested in these results - // @todo when using index table, allow search to get full count. - if ($search_type != 'all' && $entity_subtype != $subtype) { - continue; - } - $params['subtype'] = $subtype; - $params['type'] = $type; - - $entities = trigger_plugin_hook('search', "$type:$subtype", $params, NULL); - if ($entities === FALSE) { - // someone is saying not to display these types in searches. - continue; - } elseif (is_array($entities) && !count($entities)) { - // no results, but results searched in hook. - } elseif (!$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, $params, array()); - } - - if (is_array($results['entities']) && $results['count']) { - $results_html .= search_get_listing_html($results['entities'], $results['count'], $params); - } - } - } - - // pull in default type entities with no subtypes - // @todo this might currently means "all entities regardless of subtype" - $params['type'] = $type; - $params['subtype'] = 0; - - $results = trigger_plugin_hook('search', $type, $params, array()); - if ($results === FALSE) { - // someone is saying not to display these types in searches. - continue; - } - - if (is_array($results['entities']) && $results['count']) { - $results_html .= search_get_listing_html($results['entities'], $results['count'], $params); - } - } -} - -// call custom searches -if ($search_type != 'entities' || $search_type == 'all') { - // get custom search types - $types = trigger_plugin_hook('search_types', 'get_types', $params, array()); - - if (is_array($types)) { - foreach ($types as $type) { - if ($search_type != 'all' && $search_type != $type) { - continue; - } - - $params['search_type'] = $type; - unset($params['subtype']); - - $results = trigger_plugin_hook('search', $type, $params, array()); - - if ($results === FALSE) { - // someone is saying not to display these types in searches. - continue; - } - - if (is_array($results['entities']) && $results['count']) { - $results_html .= search_get_listing_html($results['entities'], $results['count'], $params); - } - } - } -} - -//if ($search_type !== 'all') { -// var_dump('here'); -// $entities = trigger_plugin_hook('search', $search_type, '', $return); -//} -/* - -call search_section_start to display long bar with types and titles -call search - -*/ - -if (!$results_html) { - $body = elgg_echo('search:no_results'); -} else { - $body = $results_html; -} - -$layout = elgg_view_layout('two_column_left_sidebar', '', $body); - -page_draw($title, $layout); - - - - - - - -return; - - -/** Main search page */ - -global $CONFIG; - -$tag = get_input('tag'); -$offset = get_input('offset', 0); -$viewtype = get_input('search_viewtype','list'); -if ($viewtype == 'gallery') { - $limit = get_input('limit', 12); // 10 items in list view -} else { - $limit = get_input('limit', 10); // 12 items in gallery view -} -$searchtype = get_input('searchtype', 'all'); -$type = get_input('type', ''); -$subtype = get_input('subtype', ''); -$owner_guid = get_input('owner_guid', ''); -$tagtype = get_input('tagtype', ''); -$friends = (int)get_input('friends', 0); -$title = sprintf(elgg_echo('searchtitle'), $tag); - -if (substr_count($owner_guid, ',')) { - $owner_guid_array = explode(',', $owner_guid); -} else { - $owner_guid_array = $owner_guid; -} -if ($friends > 0) { - if ($friends = get_user_friends($friends, '', 9999)) { - $owner_guid_array = array(); - foreach($friends as $friend) { - $owner_guid_array[] = $friend->guid; - } - } else { - $owner_guid = -1; - } -} - -// Set up submenus -if ($types = get_registered_entity_types()) { - foreach($types as $ot => $subtype_array) { - if (is_array($subtype_array) && count($subtype_array)) { - foreach($subtype_array as $object_subtype) { - $label = 'item:' . $ot; - if (!empty($object_subtype)) { - $label .= ':' . $object_subtype; - } - - $data = http_build_query(array( - 'tag' => urlencode($tag), - 'subtype' => $object_subtype, - 'type' => urlencode($ot), - //'tagtype' => urlencode($md_type), - 'owner_guid' => urlencode($owner_guid) - )); - - $url = "{$CONFIG->wwwroot}pg/search/?$data"; - - add_submenu_item(elgg_echo($label), $url); - } - } - } - - $data = http_build_query(array( - 'tag' => urlencode($tag), - 'owner_guid' => urlencode($owner_guid) - )); - - add_submenu_item(elgg_echo('all'), "{$CONFIG->wwwroot}pg/search/?$data"); -} - -// pull in search types for external or aggregated searches. -if ($search_types = trigger_plugin_hook('search', 'types', '', NULL, array())) { - -} - -$body = ''; -if (!empty($tag)) { - // start with blank results. - $results = array( - 'entities' => array(), - 'total' => 0 - ); - - // do the actual searchts - $params = array( - 'tag' => $tag, - 'offset' => $offset, - 'limit' => $limit, - 'searchtype' => $searchtype, - 'type' => $type, - 'subtype' => $subtype, - 'tagtype' => $tagtype, - 'owner_guid' => $owner_guid_array - ); - - $results = trigger_plugin_hook('search', 'entities', $params, $results); - - if (empty($type) && empty($subtype)) { - $title = sprintf(elgg_echo('searchtitle'),$tag); - } else { - if (empty($type)) { - $type = 'object'; - } - $itemtitle = 'item:' . $type; - if (!empty($subtype)) { - $itemtitle .= ':' . $subtype; - } - $itemtitle = elgg_echo($itemtitle); - $title = sprintf(elgg_echo('advancedsearchtitle'),$itemtitle,$tag); - } - - $body .= elgg_view_title($title); // elgg_view_title(sprintf(elgg_echo('searchtitle'),$tag)); - - // call the old (now-deprecated) search hook here - $body .= trigger_plugin_hook('search','',$tag, ''); - - $body .= elgg_view('search/startblurb', array('query' => $query)); - - if ($results->total > 0) { - $body .= elgg_view('search/entity_list', array( - 'entities' => $results->entities, - 'count' => $results->total, - 'offset' => $offset, - 'limit' => $limit, - 'baseurl' => $_SERVER['REQUEST_URI'], - 'fullview' => false, - 'context' => 'search', - 'viewtypetoggle' => true, - 'viewtype' => $viewtype, - 'pagination' => true - )); - } else { - $body .= elgg_view('page_elements/contentwrapper', array('body' => elgg_echo('search:noresults'))); - } - - elgg_view_entity_list($results->entities, count($results->entities), 0, count($results->entities), false); -} else { - // if no tag was given, give the user a box to input a search term - $body .= elgg_view_title(elgg_echo('search:enterterm')); - $body .= elgg_view('page_elements/contentwrapper', array('body' => '<div>' . elgg_view('page_elements/searchbox') . '</div>')); -} - -$layout = elgg_view_layout('two_column_left_sidebar','',$body); - -page_draw($title, $layout);
\ No newline at end of file diff --git a/mod/search/languages/en.php b/mod/search/languages/en.php index 52c29c7ed..e3899500f 100644 --- a/mod/search/languages/en.php +++ b/mod/search/languages/en.php @@ -1,10 +1,21 @@ <?php - $language_array = array('search:enterterm' => 'Enter a search term:', - 'search:noresults' => 'No results.', - 'search:matched' => 'Matched: ' - ); +$english = array( + 'search:enter_term' => 'Enter a search term:', + 'search:no_results' => 'No results.', + 'search:matched' => 'Matched: ', + 'search:results' => 'Results for %s', + 'search:no_query' => 'Please enter a query to search.', + 'search:search_error' => 'Error', -add_translation('en', $language_array); + 'search:more' => '+%s more %s', -?> + 'search_types:tags' => 'Tags', + + 'search_types:comments' => 'Comments', + 'search:comment_on' => 'Comment on "%s"', + 'search:comment_by' => 'by', + 'search:unavailable_entity' => 'Unavailable Entity', +); + +add_translation('en', $english); diff --git a/mod/search/manifest.xml b/mod/search/manifest.xml index 1f8415445..513d3a6b9 100644 --- a/mod/search/manifest.xml +++ b/mod/search/manifest.xml @@ -1,10 +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-2009, MITRE 2009" /> - <field key="licence" value="GNU Public License version 2" /> - <field key="elgg_version" value="2009030702" /> -</plugin_manifest>
\ No newline at end of file +<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/pages/search/index.php b/mod/search/pages/search/index.php new file mode 100644 index 000000000..9542e0751 --- /dev/null +++ b/mod/search/pages/search/index.php @@ -0,0 +1,275 @@ +<?php +/** + * Elgg search page + * + * @todo much of this code should be pulled out into a library of functions + */ + +// Search supports RSS +global $autofeed; +$autofeed = true; + +// $search_type == all || entities || trigger plugin hook +$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); +$offset = ($search_type == 'all') ? 0 : get_input('offset', 0); + +$entity_type = get_input('entity_type', ELGG_ENTITIES_ANY_VALUE); +$entity_subtype = get_input('entity_subtype', ELGG_ENTITIES_ANY_VALUE); +$owner_guid = get_input('owner_guid', ELGG_ENTITIES_ANY_VALUE); +$container_guid = get_input('container_guid', ELGG_ENTITIES_ANY_VALUE); +$friends = get_input('friends', ELGG_ENTITIES_ANY_VALUE); +$sort = get_input('sort'); +switch ($sort) { + case 'relevance': + case 'created': + case 'updated': + case 'action_on': + case 'alpha': + break; + + default: + $sort = 'relevance'; + break; +} + +$order = get_input('order', 'desc'); +if ($order != 'asc' && $order != 'desc') { + $order = 'desc'; +} + +// set up search params +$params = array( + 'query' => $query, + 'offset' => $offset, + 'limit' => $limit, + 'sort' => $sort, + 'order' => $order, + 'search_type' => $search_type, + 'type' => $entity_type, + 'subtype' => $entity_subtype, +// 'tag_type' => $tag_type, + 'owner_guid' => $owner_guid, + 'container_guid' => $container_guid, +// 'friends' => $friends + 'pagination' => ($search_type == 'all') ? FALSE : TRUE +); + +$types = get_registered_entity_types(); +$custom_types = elgg_trigger_plugin_hook('search_types', 'get_types', $params, array()); + +// 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, + 'entity_subtype' => $entity_subtype, + 'entity_type' => $entity_type, + 'owner_guid' => $owner_guid, + 'search_type' => 'all', + //'friends' => $friends +))); +$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. + if (is_array($subtypes) && count($subtypes)) { + foreach ($subtypes as $subtype) { + $label = "item:$type:$subtype"; + + $data = htmlspecialchars(http_build_query(array( + 'q' => $query, + 'entity_subtype' => $subtype, + 'entity_type' => $type, + 'owner_guid' => $owner_guid, + 'search_type' => 'entities', + 'friends' => $friends + ))); + + $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"; + + $data = htmlspecialchars(http_build_query(array( + 'q' => $query, + 'entity_type' => $type, + 'owner_guid' => $owner_guid, + 'search_type' => 'entities', + 'friends' => $friends + ))); + + $url = elgg_get_site_url() . "search?$data"; + + $menu_item = new ElggMenuItem($label, elgg_echo($label), $url); + elgg_register_menu_item('page', $menu_item); + } +} + +// add sidebar for custom searches +foreach ($custom_types as $type) { + $label = "search_types:$type"; + + $data = htmlspecialchars(http_build_query(array( + 'q' => $query, + 'search_type' => $type, + ))); + + $url = elgg_get_site_url()."search?$data"; + + $menu_item = new ElggMenuItem($label, elgg_echo($label), $url); + elgg_register_menu_item('page', $menu_item); +} + +// start the actual search +$results_html = ''; + +if ($search_type == 'all' || $search_type == 'entities') { + // to pass the correct current search type to the views + $current_params = $params; + $current_params['search_type'] = 'entities'; + + // foreach through types. + // if a plugin returns FALSE for subtype ignore it. + // if a plugin returns NULL or '' for subtype, pass to generic type search function. + // if still NULL or '' or empty(array()) no results found. (== don't show??) + foreach ($types as $type => $subtypes) { + if ($search_type != 'all' && $entity_type != $type) { + continue; + } + + if (is_array($subtypes) && count($subtypes)) { + foreach ($subtypes as $subtype) { + // no need to search if we're not interested in these results + // @todo when using index table, allow search to get full count. + if ($search_type != 'all' && $entity_subtype != $subtype) { + continue; + } + $current_params['subtype'] = $subtype; + $current_params['type'] = $type; + + $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; + } elseif (is_array($results) && !count($results)) { + // no results, but results searched in hook. + } elseif (!$results) { + // 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 = elgg_trigger_plugin_hook('search', $type, $current_params, array()); + } + + if (is_array($results['entities']) && $results['count']) { + if ($view = search_get_search_view($current_params, 'list')) { + $results_html .= elgg_view($view, array( + 'results' => $results, + 'params' => $current_params, + )); + } + } + } + } + + // pull in default type entities with no subtypes + $current_params['type'] = $type; + $current_params['subtype'] = ELGG_ENTITIES_NO_VALUE; + + $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, 'list')) { + $results_html .= elgg_view($view, array( + 'results' => $results, + 'params' => $current_params, + )); + } + } + } +} + +// call custom searches +if ($search_type != 'entities' || $search_type == 'all') { + if (is_array($custom_types)) { + foreach ($custom_types as $type) { + if ($search_type != 'all' && $search_type != $type) { + continue; + } + + $current_params = $params; + $current_params['search_type'] = $type; + + $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, 'list')) { + $results_html .= elgg_view($view, array( + 'results' => $results, + 'params' => $current_params, + )); + } + } + } + } +} + +// highlight search terms +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\""))); + +if (!$results_html) { + $body .= elgg_view('search/no_results'); +} else { + $body .= $results_html; +} + +// this is passed the original params because we don't care what actually +// matched (which is out of date now anyway). +// we want to know what search type it is. +$layout_view = search_get_search_view($params, 'layout'); +$layout = elgg_view($layout_view, array('params' => $params, 'body' => $body)); + +$title = elgg_echo('search:results', array("\"$display_query\"")); + +echo elgg_view_page($title, $layout); diff --git a/mod/search/search_hooks.php b/mod/search/search_hooks.php index f7a49400f..923cf0aa8 100644 --- a/mod/search/search_hooks.php +++ b/mod/search/search_hooks.php @@ -3,51 +3,46 @@ * Elgg core search. * * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com>, The MITRE Corporation <http://www.mitre.org> - * @link http://elgg.org/ + * @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); $params['wheres'] = array($where); - - //@todo allow sorting by recent time - $params['order_by'] = NULL; - - $entities = elgg_get_entities($params); $params['count'] = TRUE; $count = elgg_get_entities($params); - + // no need to continue if nothing here. if (!$count) { return array('entities' => array(), 'count' => $count); } + + $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. foreach ($entities as $entity) { - //$title = search_get_highlighted_relevant_substrings($entity->title, $params['query']); - //$title = search_get_relevant_substring($entity->title, $params['query'], '<strong class="searchMatch">', '</strong>'); $title = search_get_highlighted_relevant_substrings($entity->title, $params['query']); $entity->setVolatileData('search_matched_title', $title); - //$desc = search_get_relevant_substring($entity->description, $params['query'], '<strong class="searchMatch">', '</strong>'); $desc = search_get_highlighted_relevant_substrings($entity->description, $params['query']); $entity->setVolatileData('search_matched_description', $desc); } @@ -59,45 +54,49 @@ 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'); + + $where = search_get_where_sql('ge', $fields, $params); - $where = "(ge.guid = e.guid - AND (ge.name LIKE '%$query%' - OR ge.description LIKE '%$query%' - ) - )"; $params['wheres'] = array($where); - $entities = elgg_get_entities($params); + // override subtype -- All groups should be returned regardless of subtype. + $params['subtype'] = ELGG_ENTITIES_ANY_VALUE; + $params['count'] = TRUE; $count = elgg_get_entities($params); - + // no need to continue if nothing here. if (!$count) { return array('entities' => array(), 'count' => $count); } + + $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( @@ -107,45 +106,93 @@ function search_groups_hook($hook, $type, $value, $params) { } /** - * Return default results for searches on users. + * 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['wheres'] = array($where); - - $entities = elgg_get_entities($params); - $params['count'] = TRUE; + $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')); + + // 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; $count = elgg_get_entities($params); // no need to continue if nothing here. if (!$count) { return array('entities' => array(), 'count' => $count); } + + $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( @@ -155,35 +202,130 @@ 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(); // @todo will need to split this up to support searching multiple tags at once. $query = sanitise_string($params['query']); - $params['metadata_name_value_pair'] = array ('name' => 'tags', 'value' => $query, 'case_sensitive' => FALSE); - $entities = elgg_get_entities_from_metadata($params); + // if passed a tag metadata name, only search on that tag name. + // tag_name isn't included in the params because it's specific to + // tag searches. + if ($tag_names = get_input('tag_names')) { + if (is_array($tag_names)) { + $search_tag_names = $tag_names; + } else { + $search_tag_names = array($tag_names); + } + + // check these are valid to avoid arbitrary metadata searches. + foreach ($search_tag_names as $i => $tag_name) { + if (!in_array($tag_name, $valid_tag_names)) { + unset($search_tag_names[$i]); + } + } + } else { + $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 {$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(); + + foreach ($search_tag_names as $tag) { + $sanitised_tags[] = '"' . sanitise_string($tag) . '"'; + } + + $tags_in = implode(',', $sanitised_tags); + + $params['wheres'][] = "(msn.string IN ($tags_in) AND msv.string = '$query' AND $access)"; + $params['count'] = TRUE; - $count = elgg_get_entities_from_metadata($params); + $count = elgg_get_entities($params); // no need to continue if nothing here. if (!$count) { return array('entities' => array(), 'count' => $count); } + + $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. foreach ($entities as $entity) { - $tags = implode(',', $entity->tags); - $tags_str = search_get_highlighted_relevant_substrings($tags, $params['query']); - $entity->setVolatileData('search_matched_tags', $tags_str); + $matched_tags_strs = array(); + + // get tags for each tag name requested to find which ones matched. + foreach ($search_tag_names as $tag_name) { + $tags = $entity->getTags($tag_name); + + // @todo make one long tag string and run this through the highlight + // function. This might be confusing as it could chop off + // the tag labels. + if (in_array(strtolower($query), array_map('strtolower', $tags))) { + if (is_array($tags)) { + $tag_name_str = elgg_echo("tag_names:$tag_name"); + $matched_tags_strs[] = "$tag_name_str: " . implode(', ', $tags); + } + } + } + + // different entities have different titles + switch($entity->type) { + case 'site': + case 'user': + case 'group': + $title_tmp = $entity->name; + break; + + case 'object': + $title_tmp = $entity->title; + break; + } + + // Nick told me my idea was dirty, so I'm hard coding the numbers. + $title_tmp = strip_tags($title_tmp); + if (elgg_strlen($title_tmp) > 297) { + $title_str = elgg_substr($title_tmp, 0, 297) . '...'; + } else { + $title_str = $title_tmp; + } + + $desc_tmp = strip_tags($entity->description); + if (elgg_strlen($desc_tmp) > 297) { + $desc_str = elgg_substr($desc_tmp, 0, 297) . '...'; + } else { + $desc_str = $desc_tmp; + } + + $tags_str = implode('. ', $matched_tags_strs); + $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); + $entity->setVolatileData('search_matched_extra', $tags_str); } return array( @@ -195,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'; @@ -208,76 +350,141 @@ 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'); - $search_where = search_get_where_sql('msv', $fields, $params); + + // force IN BOOLEAN MODE since fulltext isn't + // available on metastrings (and boolean mode doesn't need it) + $search_where = search_get_where_sql('msv', $fields, $params, FALSE); + + $container_and = ''; + if ($params['container_guid'] && $params['container_guid'] !== ELGG_ENTITIES_ANY_VALUE) { + $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 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 + $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 AND $a_access + $container_and + "; - LIMIT {$params['offset']}, {$params['limit']} + if (!$result = get_data($q)) { + return FALSE; + } + + $count = $result[0]->total; + + // don't continue if nothing there... + if (!$count) { + 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 + 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 + + $order_by + LIMIT $offset, $limit "; + $comments = get_data($q); + // @todo if plugins are disabled causing subtypes + // to be invalid and there are comments on entities of those subtypes, + // the counts will be wrong here and results might not show up correctly, + // especially on the search landing page, which only pulls out two results. + + // probably better to check against valid subtypes than to do what I'm doing. + // need to return actual entities // add the volatile data for why these entities have been returned. $entities = array(); foreach ($comments as $comment) { - $tags = implode(',', $entity->tags); - if (!$entity = get_entity($comment->entity_guid)) { - continue; + $entity = get_entity($comment->entity_guid); + + // hic sunt dracones + if (!$entity) { + //continue; + $entity = new ElggObject(); + $entity->setVolatileData('search_unavailable_entity', TRUE); } + $comment_str = search_get_highlighted_relevant_substrings($comment->comment, $query); - $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; } return array( 'entities' => $entities, - 'count' => count($entities), + 'count' => $count, ); } /** * 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'; return $value; -}
\ No newline at end of file +} diff --git a/mod/search/start.php b/mod/search/start.php index 18b743cde..8a112a3a3 100644 --- a/mod/search/start.php +++ b/mod/search/start.php @@ -1,46 +1,42 @@ <?php /** - * Elgg core search. - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd <info@elgg.com>, The MITRE Corporation <http://www.mitre.org> - * @link http://elgg.org/ - */ + * Elgg search plugin + * + */ + +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(); // can't use get_data() here because some servers don't have these globals set, // which throws a db exception. - $r = mysql_query('SELECT @@ft_min_word_len as min, @@ft_max_word_len as max'); - if ($word_lens = mysql_fetch_assoc($r)) { + $dblink = get_db_link('read'); + $r = mysql_query('SELECT @@ft_min_word_len as min, @@ft_max_word_len as max', $dblink); + if ($r && ($word_lens = mysql_fetch_assoc($r))) { $CONFIG->search_info['min_chars'] = $word_lens['min']; $CONFIG->search_info['max_chars'] = $word_lens['max']; } else { @@ -50,16 +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('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. @@ -70,420 +69,315 @@ 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 elements. - * Checks for "s - * Provides context for matched elements. - * Will not return more than $max_length of full context. - * Only highlights words + * Return a string with highlighted matched queries and relevant context + * Determines context based upon occurance and distance of words with each other. * - * @param unknown_type $haystack - * @param unknown_type $need - * @param unknown_type $context - * @param unknown_type $max_length - * @return unknown_type + * @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, $needle, $min_match_context = 15, $max_length = 500) { - 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_lc = strtolower($haystack); -// -// $haystack = "Like merge sort, quicksort can also be easily parallelized due to its " -// . "divide-and-conquer nature. Individual in-place partition operations are difficult " -// . "to parallelize, but once divided, different sections of the list can be sorted in parallel. " -// . "If we have p processors, we can divide a list of n ele"; -// -// $needle = 'difficult to sort in parallel'; - - // for now don't worry about "s or boolean operators - $needle = str_replace(array('"', '-', '+', '~'), '', stripslashes(strip_tags($needle))); - $words = explode(' ', $needle); + $haystack_length = elgg_strlen($haystack); + $haystack_lc = elgg_strtolower($haystack); - $min_chars = $CONFIG->search_info['min_chars']; - // if > ft_min_word == not running in literal mode. - if ($needle >= $min_chars) { - // clean out any words that are ignored by mysql - foreach ($words as $i => $word) { - if (strlen($word) < $min_chars) { - unset ($words[$i]); - } - } + if (!$tag_match) { + $words = search_remove_ignored_words($query, 'array'); + } else { + $words = array(); } - /* - - $body_len = 250 - - $context = 5-30, 20-45, 75-100, 150 - - can pull out context either on: - one of each matching term - X # of highest matching terms + // if haystack < $max_length return the entire haystack w/formatting immediately + if ($haystack_length <= $max_length) { + $return = search_highlight_words($words, $haystack); + return $return; + } - */ - $substr_counts = array(); - $str_pos = array(); - // matrices for being and end context lengths. - // defaults to min context. will add additional context later if needed + // get the starting positions and lengths for all matching words $starts = array(); - $stops = array(); - - // map the words to the starts and stops - $words_arg = array(); - $context_count = 0; - - - // get the full count of matches. + $lengths = array(); foreach ($words as $word) { - $word = strtolower($word); - $count = substr_count($haystack, $word); - $word_len = strlen($word); + $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) { - $str_pos[$word] = array(); $offset = 0; - while (FALSE !== $pos = strpos($haystack, $word, $offset)) { - $str_pos[$word][] = $pos; - $starts[] = ($pos - $min_match_context > 0) ? $pos - $min_match_context : 0; - $stops[] = $pos + $word_len + $min_match_context; - $words_arg[] = $word; - $context_count += $min_match_context + $word_len; + while (FALSE !== $pos = elgg_strpos($haystack_lc, $word, $offset)) { + $start = ($pos - $min_match_context > 0) ? $pos - $min_match_context : 0; + $starts[] = $start; + $stop = $pos + $word_len + $min_match_context; + $lengths[] = $stop - $start; $offset += $pos + $word_len; + + if ($offset >= $haystack_len) { + break; + } } } else { - $pos = strpos($haystack, $word); - $str_pos[$word] = array($pos); - $starts[] = ($pos - $min_match_context > 0) ? $pos - $min_match_context : 0; - $stops[] = $pos + $word_len + $min_match_context; - $context_count += $min_match_context + $word_len; - $words_arg[] = $word; - } - $substr_counts[$word] = $count; - } - - // sort by order of occurence - //krsort($substr_counts); - $full_count = array_sum($substr_counts); - - // figure out what the context needs to be. - // take one of each matched phrase - // if there are any - -// -// var_dump($str_pos); -// var_dump($substr_counts); -// var_dump($context_count); - - - // sort to put them in order of occurence - asort($starts, SORT_NUMERIC); - asort($stops, SORT_NUMERIC); - - // offset them correctly - $starts[] = 0; - $new_stops = array(0); - foreach ($stops as $i => $pos) { - $new_stops[$i+1] = $pos; - } - $stops = $new_stops; - - $substrings = array(); - $len = count($starts); - - $starts = array_merge($starts); - $stops = array_merge($stops); - - $offsets = array(); - $limits = array(); - $c = 0; - foreach ($starts as $i => $start) { - $stop = $stops[$i]; - $offsets[$c] = $start; - $limits[$c] = $stop; - - // never need the last one as it's just a displacing entry - if ($c+1 == count($starts)) { - break; - } - - if ($start - $stop < 0) { - //var_dump("Looking at c=$c & $start - $stop and going to unset {$limits[$c]}"); - unset($offsets[$c]); - unset($limits[$c]); + $pos = elgg_strpos($haystack_lc, $word); + $start = ($pos - $min_match_context > 0) ? $pos - $min_match_context : 0; + $starts[] = $start; + $stop = $pos + $word_len + $min_match_context; + $lengths[] = $stop - $start; } - $c++; } - // reset indexes and remove placeholder elements. - $limits = array_merge($limits); - array_shift($limits); - $offsets = array_merge($offsets); - array_pop($offsets); - - // figure out if we need to adjust the offsets from the base - // this could result in overlapping summaries. - // might be nicer to just remove it. + $offsets = search_consolidate_substrings($starts, $lengths); - $total_len = 0; - foreach ($offsets as $i => $offset) { - $total_len += $limits[$i] - $offset; - } + // figure out if we can adjust the offsets and lengths + // in order to return more context + $total_length = array_sum($offsets); $add_length = 0; - if ($total_length < $max_length) { - $add_length = floor((($max_length - $total_len) / count($offsets)) / 2); - } - - $lengths = array(); - foreach ($offsets as $i => $offset) { - $limit = $limits[$i]; - if ($offset == 0 && $add_length) { - $limit += $add_length; - } else { - $offset = $offset - $add_length; - } - $string = substr($haystack, $offset, $limit - $offset); - - if ($offset != 0) { - $string = "...$string"; - } - - if ($limit + $offset >= strlen($haystack)) { - $string .= '...'; + if ($total_length < $max_length && $offsets) { + $add_length = floor((($max_length - $total_length) / count($offsets)) / 2); + + $starts = array(); + $lengths = array(); + foreach ($offsets as $offset => $length) { + $start = ($offset - $add_length > 0) ? $offset - $add_length : 0; + $length = $length + $add_length; + $starts[] = $start; + $lengths[] = $length; } - $substrings[] = $string; - $lengths[] = strlen($string); + $offsets = search_consolidate_substrings($starts, $lengths); } - // sort by length of context. - asort($lengths); + // sort by order of string size descending (which is roughly + // the proximity of matched terms) so we can keep the + // substrings with terms closest together and discard + // the others as needed to fit within $max_length. + arsort($offsets); - $matched = ''; - foreach ($lengths as $i => $len) { - $string = $substrings[$i]; + $return_strs = array(); + $total_length = 0; + foreach ($offsets as $start => $length) { + $string = trim(elgg_substr($haystack, $start, $length)); - if (strlen($matched) + strlen($string) < $max_length) { - $matched .= $string; + // continue past if adding this substring exceeds max length + if ($total_length + $length > $max_length) { + continue; } - } - $i = 1; - foreach ($words as $word) { - $search = "/($word)/i"; - $replace = "<strong class=\"searchMatch searchMatchColor$i\">$1</strong>"; - $matched = preg_replace($search, $replace, $matched); - $i++; + $total_length += $length; + $return_strs[$start] = $string; } - return $matched; - + // put the strings in order of occurence + ksort($return_strs); - // crap below.. - - - - for ($i=0; $i<$len; $i++) { - $start = $starts[$i]; - $stop = $stops[$i]; - var_dump("Looking at $i = $start - $stop"); - - while ($start - $stop <= 0) { - $stop = $stops[$i++]; - var_dump("New start is $stop"); - } - - var_dump("$start-$stop"); - } - - // find the intersecting contexts - foreach ($starts as $i => $start_pos) { - $words .= "{$words_arg[$i]}\t\t\t"; - echo "$start_pos\t\t\t"; + // add ...s where needed + $return = implode('...', $return_strs); + if (!array_key_exists(0, $return_strs)) { + $return = "...$return"; } - echo "\n"; - - foreach ($stops as $i => $stop_pos) { - echo "$stop_pos\t\t\t"; + // add to end of string if last substring doesn't hit the end. + $starts = array_keys($return_strs); + $last_pos = $starts[count($starts)-1]; + if ($last_pos + elgg_strlen($return_strs[$last_pos]) < $haystack_length) { + $return .= '...'; } -echo "\n$words\n"; - // get full number of matches against all words to see how many we actually want to look at. + $return = search_highlight_words($words, $return); + return $return; +} +/** + * Takes an array of offsets and lengths and consolidates any + * overlapping entries, returning an array of new offsets and lengths + * + * Offsets and lengths are specified in separate arrays because of possible + * index collisions with the offsets. + * + * @param array $offsets + * @param array $lengths + * @return array + */ +function search_consolidate_substrings($offsets, $lengths) { + // sort offsets by occurence + asort($offsets, SORT_NUMERIC); -// $desc = search_get_relevant_substring($entity->description, $params['query'], '<strong class="searchMatch">', '</strong>'); - - - $params['query']; - // "this is"just a test "silly person" - - // check for "s - $words_quotes = explode('"', $needle); - - $words_orig = explode(' ', $needle); - $words = array(); - - foreach ($words_orig as $i => $word) { - // figure out if we have a special operand - $operand = substr($word, 0, 1); - switch($operand) { - case '"': - // find the matching " if any. else, remove the " - if (substr_count($query, '"') < 2) { - $words[] = substr($word, 1); - } else { - $word = substr($word, 1); - $word_i = $i; - while ('"' != strpos($words_orig[$word_i], '"')) { - $word .= " {$words_orig[$word_i]}"; - unset($words_orig[$word_i]); - } - } + // reset the indexes maintaining association with the original offsets. + $offsets = array_merge($offsets); - break; + $new_lengths = array(); + foreach ($offsets as $i => $offset) { + $new_lengths[] = $lengths[$i]; + } - case '+': - // remove + - $words[] = substr($word, 1); - break; + $lengths = $new_lengths; - case '~': - case '-': - // remove this from highlighted list. + $return = array(); + $count = count($offsets); + for ($i=0; $i<$count; $i++) { + $offset = $offsets[$i]; + $length = $lengths[$i]; + $end_pos = $offset + $length; + // find the next entry that doesn't overlap + while (array_key_exists($i+1, $offsets) && $end_pos > $offsets[$i+1]) { + $i++; + if (!array_key_exists($i, $offsets)) { break; + } + $end_pos = $lengths[$i] + $offsets[$i]; } - } - // pick out " queries - if (substr_count($query, '"') >= 2) { + $length = $end_pos - $offset; + // will never have a colliding offset, so can return as a single array + $return[$offset] = $length; } - // ignore queries starting with - - - - // @todo figure out a way to "center" the matches within the max_length. - // if only one match, its context is $context + $max_length / 2 - // if 2 matches, its context is $context + $max_length / 4 - // if 3 matches, its context is $context + $max_length / 6 - // $context per match = $min_match_context + ($max_length / $num_count_match) - - // if $max_length / ($matched_count * 2) < $context - // only match against the first X matches where $context >= $context + return $return; } /** - * Returns a matching string with $context amount of context, optionally - * surrounded by $before and $after. + * Safely highlights the words in $words found in $string avoiding recursion * - * If no match is found, restricts string to $context*2 starting from strpos 0. - * - * @param str $haystack - * @param str $needle - * @param str $before - * @param str $after - * @param int $context - * @return str + * @param array $words + * @param string $string + * @return string */ -function search_get_relevant_substring($haystack, $needle, $before = '', $after = '', $context = 75) { - $haystack = strip_tags($haystack); - $needle = strip_tags($needle); - - $pos = strpos(strtolower($haystack), strtolower($needle)); +function search_highlight_words($words, $string) { + $i = 1; + $replace_html = array( + 'strong' => rand(10000, 99999), + 'class' => rand(10000, 99999), + 'search-highlight' => rand(10000, 99999), + 'search-highlight-color' => rand(10000, 99999) + ); - if ($pos === FALSE) { - $str = substr($haystack, 0, $context*2); - if (strlen($haystack) > $context*2) { - $str .= '...'; - } + foreach ($words as $word) { + // remove any boolean mode operators + $word = preg_replace("/([\-\+~])([\w]+)/i", '$2', $word); + + // escape the delimiter and any other regexp special chars + $word = preg_quote($word, '/'); + + $search = "/($word)/i"; - return $str; + // @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']; + $highlight = $replace_html['search-highlight']; + $color = $replace_html['search-highlight-color']; + + $replace = "<$strong $class=\"$highlight $color{$i}\">$1</$strong>"; + $string = preg_replace($search, $replace, $string); + $i++; } - $start_pos = $pos - $context; - - if ($start_pos < 0) { - $start_pos = 0; + foreach ($replace_html as $replace => $search) { + $string = str_replace($search, $replace, $string); } - // get string from -context to +context - $matched = substr($haystack, $start_pos, $context*2); + return $string; +} - // add elipses to front. - if ($start_pos > 0) { - $matched = "...$matched"; - } +/** + * Returns a query with stop and too short words removed. + * (Unless the entire query is < ft_min_word_chars, in which case + * it's taken literally.) + * + * @param array $query + * @param str $format Return as an array or a string + * @return mixed + */ +function search_remove_ignored_words($query, $format = 'array') { + global $CONFIG; - // add elipses to end. - if ($pos + strlen($needle) + $context*2 < strlen($haystack)) { - $matched = "$matched..."; + // don't worry about "s or boolean operators + //$query = str_replace(array('"', '-', '+', '~'), '', stripslashes(strip_tags($query))); + $query = stripslashes(strip_tags($query)); + + $words = explode(' ', $query); + + $min_chars = $CONFIG->search_info['min_chars']; + // if > ft_min_word we're not running in literal mode. + if (elgg_strlen($query) >= $min_chars) { + // clean out any words that are ignored by mysql + foreach ($words as $i => $word) { + if (elgg_strlen($word) < $min_chars) { + unset ($words[$i]); + } + } } - // surround if needed - // @todo would getting each position of the match then - // inserting manually based on the position be faster than preg_replace()? - if ($before || $after) { - $matched = str_ireplace($needle, $before . $needle . $after, $matched); - //$matched = mb_ereg_replace("") - // insert before + if ($format == 'string') { + return implode(' ', $words); } - return $matched; + return $words; } /** - * Passes entities, count, and original params to the view functions for + * Passes results, and original params to the view functions for * search type. * - * @param array $entities - * @param int $count + * @param array $results * @param array $params + * @param string $view_type = list, entity or layout * @return string */ -function search_get_listing_html($entities, $count, $params) { - if (!is_array($entities) || !$count) { - return FALSE; +function search_get_search_view($params, $view_type) { + switch ($view_type) { + case 'list': + case 'entity': + case 'layout': + break; + + default: + return FALSE; } $view_order = array(); - // check if there's a special search 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']}/listing"; + $view_order[] = "search/{$params['type']}/{$params['subtype']}/$view_type"; } // also check for the default type if (isset($params['type']) && $params['type']) { - $view_order[] = "search/{$params['type']}/listing"; + $view_order[] = "search/{$params['type']}/$view_type"; } // check search types if (isset($params['search_type']) && $params['search_type']) { - $view_order[] = "search/{$params['search_type']}/listing"; + $view_order[] = "search/{$params['search_type']}/$view_type"; } - // finally default to a search listing default - $view_order[] = "search/listing"; - - $vars = array( - 'entities' => $entities, - 'count' => $count, - 'params' => $params - ); + // finally default to a search list default + $view_order[] = "search/$view_type"; foreach ($view_order as $view) { if (elgg_view_exists($view)) { - return elgg_view($view, $vars); + return $view; } } @@ -498,58 +392,111 @@ function search_get_listing_html($entities, $count, $params) { * @param array $params Original search params * @return str */ -function search_get_where_sql($table, $fields, $params) { +function search_get_where_sql($table, $fields, $params, $use_fulltext = TRUE) { global $CONFIG; $query = $params['query']; // add the table prefix to the fields foreach ($fields as $i => $field) { - $fields[$i] = "$table.$field"; + if ($table) { + $fields[$i] = "$table.$field"; + } } + + $where = ''; // if query is shorter than the min for fts words // it's likely a single acronym or similar // switch to literal mode - if (strlen($query) < $CONFIG->search_info['min_chars']) { + if (elgg_strlen($query) < $CONFIG->search_info['min_chars']) { $likes = array(); $query = sanitise_string($query); foreach ($fields as $field) { $likes[] = "$field LIKE '%$query%'"; } $likes_str = implode(' OR ', $likes); - //$where = "($table.guid = e.guid AND ($likes_str))"; $where = "($likes_str)"; } else { - // if using advanced or paired "s, switch into boolean mode - if ((isset($params['advanced_search']) && $params['advanced_search']) || substr_count($query, '"') >= 2 ) { + // if we're not using full text, rewrite the query for bool mode. + // exploiting a feature(ish) of bool mode where +-word is the same as -word + if (!$use_fulltext) { + $query = '+' . str_replace(' ', ' +', $query); + } + + // if using advanced, boolean operators, or paired "s, switch into boolean mode + $booleans_used = preg_match("/([\-\+~])([\w]+)/i", $query); + $advanced_search = (isset($params['advanced_search']) && $params['advanced_search']); + $quotes_used = (elgg_substr_count($query, '"') >= 2); + + if (!$use_fulltext || $booleans_used || $advanced_search || $quotes_used) { $options = 'IN BOOLEAN MODE'; } else { - // natural language mode is default and this keyword isn't supported - // in < 5.1 + // natural language mode is default and this keyword isn't supported in < 5.1 //$options = 'IN NATURAL LANGUAGE MODE'; $options = ''; } - + // if short query, use query expansion. - if (strlen($query) < 6) { - //$options .= ' WITH QUERY EXPANSION'; - } + // @todo doesn't seem to be working well. +// if (elgg_strlen($query) < 5) { +// $options .= ' WITH QUERY EXPANSION'; +// } $query = sanitise_string($query); - // if query is shorter than the ft_min_word_len switch to literal mode. $fields_str = implode(',', $fields); - //$where = "($table.guid = e.guid AND (MATCH ($fields_str) AGAINST ('$query' $options)))"; $where = "(MATCH ($fields_str) AGAINST ('$query' $options))"; } return $where; } -function search_get_query_where_sql($table, $query) { - // if there are multiple "s or 's it's a literal string. -} +/** + * Returns ORDER BY sql for insertion into elgg_get_entities(). + * + * @param str $entities_table Prefix for entities table. + * @param str $type_table Prefix for the type table. + * @param str $sort ORDER BY part + * @param str $order ASC or DESC + * @return str + */ +function search_get_order_by_sql($entities_table, $type_table, $sort, $order) { -/** Register init system event **/ + $on = NULL; -register_elgg_event_handler('init','system','search_init');
\ No newline at end of file + switch ($sort) { + default: + case 'relevance': + // default is relevance descending. + // ascending relevancy is silly and complicated. + $on = ''; + break; + case 'created': + $on = "$entities_table.time_created"; + break; + case 'updated': + $on = "$entities_table.time_updated"; + break; + case 'action_on': + // @todo not supported yet in core + $on = ''; + break; + case 'alpha': + // @todo not support yet because both title + // and name columns are used for this depending + // on the entity, which we don't always know. >:O + break; + } + $order = strtolower($order); + if ($order != 'asc' && $order != 'desc') { + $order = 'DESC'; + } + + if ($on) { + $order_by = "$on $order"; + } else { + $order_by = ''; + } + + return $order_by; +} diff --git a/mod/search/views/default/page_elements/searchbox.php b/mod/search/views/default/page_elements/searchbox.php deleted file mode 100644 index cfc0b953b..000000000 --- a/mod/search/views/default/page_elements/searchbox.php +++ /dev/null @@ -1,4 +0,0 @@ -<form id="searchform" action="<?php echo $vars['url']; ?>pg/search/" method="get"> - <input type="text" size="21" name="q" value="<?php echo elgg_echo('search'); ?>" onclick="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" /> -</form> diff --git a/mod/search/views/default/search/comments/entity.php b/mod/search/views/default/search/comments/entity.php new file mode 100644 index 000000000..77e950843 --- /dev/null +++ b/mod/search/views/default/search/comments/entity.php @@ -0,0 +1,55 @@ +<?php +/** + * Default search view for a comment + * + * @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($comment_data['owner_guid']); + +if ($owner instanceof ElggUser) { + $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 = elgg_echo('search:comment_on', array(elgg_echo('search:unavailable_entity'))); + // keep anchor for formatting. + $title = "<a>$title</a>"; +} else { + if ($entity->getType() == 'object') { + $title = $entity->title; + } else { + $title = $entity->name; + } + + if (!$title) { + $title = elgg_echo('item:' . $entity->getType() . ':' . $entity->getSubtype()); + } + + if (!$title) { + $title = elgg_echo('item:' . $entity->getType()); + } + + $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 = $comment_data['text']; +$tc = $comment_data['time_created']; +$time = elgg_view_friendly_time($tc); + +$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/comments/listing.php b/mod/search/views/default/search/comments/listing.php deleted file mode 100644 index 58353a110..000000000 --- a/mod/search/views/default/search/comments/listing.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php -/** - * Elgg comments search listing - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ -?> - -<div class="search_listing"> -<?php -if (!is_array($vars['entities']) || !count($vars['entities'])) { - return FALSE; -} - -$title_str = elgg_echo('comments'); -$body = elgg_view_title($title_str); - -$query = htmlspecialchars(http_build_query( - array( - 'q' => $vars['params']['query'], - 'entity_type' => $vars['params']['type'], - 'entity_subtype' => $vars['params']['subtype'], - 'limit' => get_input('limit', 10), - 'offset' => get_input('offset', 0), - 'search_type' => 'comments', - ) -)); - -$url = "{$vars['url']}pg/search?$query"; -$more = "<a href=\"$url\">+$count more $title_str</a>"; - -echo elgg_view('page_elements/contentwrapper', array('body' => $body)); - -foreach ($vars['entities'] as $entity) { - if ($owner = $entity->getOwnerEntity()) { - $owner_icon = $owner->getIcon('tiny'); - $icon = "<img src=\"$owner_icon\" />"; - } else { - $icon = ''; - } - $title = "Comment on " . elgg_echo('item:' . $entity->getType() . ':' . $entity->getSubtype()); - $description = $entity->getVolatileData('search_matched_comment'); - $url = $entity->getURL(); - $title = "<a href=\"$url\">$title</a>"; - $tc = $entity->getVolatileData('search_matched_comment_time_created');; - $time = friendly_time($tc); - - echo <<<___END -<span class="searchListing"> - <h3 class="searchTitle">$title</h3> - <span class="searchDetails"> - <span class="searchDescription">$description</span><br /> - $icon $time - $more</a> - </span> -</span> -___END; -} -?> -</div> diff --git a/mod/search/views/default/search/css.php b/mod/search/views/default/search/css.php index 28dc82a4a..30ff45172 100644 --- a/mod/search/views/default/search/css.php +++ b/mod/search/views/default/search/css.php @@ -1,101 +1,67 @@ -.searchtype { -background: #FFFACD; -color: black; -} - -.searchtypes { -border: 1px #EEEEEE solid; -padding: 4px; -margin: 6px; -} - -.searchListing { - display: block; - margin-bottom: 2em; -} - -.searchMatch { - background-color: #FFFF66; -} - -.searchMatchColor1 { - background-color: #FFFF66; -} - -.searchMatchColor2 { +<?php +/** + * Elgg Search css + * + */ +?> + +/********************************** +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-list li { + padding: 5px 0 0; +} +.search-heading-category { + margin-top: 20px; + color: #666666; +} + +.search-highlight { + background-color: #bbdaf7; +} +.search-highlight-color1 { + background-color: #bbdaf7; +} +.search-highlight-color2 { background-color: #A0FFFF; } - -.searchMatchColor3 { - background-color: #FF9999; -} - -.searchMatchColor4 { - background-color: #FF66FF; -} - -.searchMatchColor5 { - background-color: #99FF99; -} - - -.searchTitle { - text-decoration: underline; -} - -#searchform input.search_input { - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - background-color:#FFFFFF; - border:1px solid #BBBBBB; - color:#999999; - font-size:12px; - font-weight:bold; - margin:0pt; - padding:2px; - width:180px; - height:12px; -} -#searchform input.search_submit_button { - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - color:#333333; - background: #cccccc; - border:none; - font-size:12px; - font-weight:bold; - margin:0px; - padding:2px; - width:auto; - height:18px; - cursor:pointer; +.search-highlight-color3 { + background-color: #FDFFC3; } -#searchform input.search_submit_button:hover { - color:#ffffff; - background: #4690d6; +.search-highlight-color4 { + background-color: #ccc; } - - -.search_listing { - display: block; - -webkit-border-radius: 8px; - -moz-border-radius: 8px; - background:white; - margin:0 10px 5px 10px; - padding:5px; -} - -.entity_gallery_item .search_listing { - background: none; - text-align: center; +.search-highlight-color5 { + background-color: #4690d6; } - -/* override the entity container piece */ -.search_listing .entity_listing { - -webkit-border-radius: 0px; - -moz-border-radius: 0px; - background: transparent; - margin: 0; - padding: 0; -} - diff --git a/mod/search/views/default/search/entity.php b/mod/search/views/default/search/entity.php new file mode 100644 index 000000000..e04d08836 --- /dev/null +++ b/mod/search/views/default/search/entity.php @@ -0,0 +1,57 @@ +<?php +/** + * 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) + * - search_matched_title + * - search_matched_description + * - search_matched_extra + * - search_url (defaults to entity->getURL()) + * - search_time (defaults to entity->time_updated or entity->time_created) + * + * @uses $vars['entity'] Entity returned in a search + */ + +$entity = $vars['entity']; + +$icon = $entity->getVolatileData('search_icon'); +if (!$icon) { + // display the entity's owner by default if available. + // @todo allow an option to switch to displaying the entity's icon instead. + $type = $entity->getType(); + if ($type == 'user' || $type == 'group') { + $icon = elgg_view_entity_icon($entity, 'tiny'); + } elseif ($owner = $entity->getOwnerEntity()) { + $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_entity_icon($entity, 'tiny'); + } +} + +$title = $entity->getVolatileData('search_matched_title'); +$description = $entity->getVolatileData('search_matched_description'); +$extra_info = $entity->getVolatileData('search_matched_extra'); +$url = $entity->getVolatileData('search_url'); + +if (!$url) { + $url = $entity->getURL(); +} + +$title = "<a href=\"$url\">$title</a>"; +$time = $entity->getVolatileData('search_time'); +if (!$time) { + $tc = $entity->time_created; + $tu = $entity->time_updated; + $time = elgg_view_friendly_time(($tu > $tc) ? $tu : $tc); +} + +$body = "<p class=\"mbn\">$title</p>$description"; +if ($extra_info) { + $body .= "<p class=\"elgg-subtext\">$extra_info</p>"; +} +$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 d709210df..000000000 --- a/mod/search/views/default/search/entity_list.php +++ /dev/null @@ -1,66 +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, - - )); - -$html .= $nav; - -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 753a38684..000000000 --- a/mod/search/views/default/search/gallery.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php - - /** - * Elgg gallery view - * - * @package Elgg - * @subpackage Core - - * @author Curverider Ltd - - * @link http://elgg.org/ - */ - - $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 bbecaf202..000000000 --- a/mod/search/views/default/search/gallery_listing.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/** - * Elgg search listing: gallery view - * - * DEPRECATED VIEW: use entities/gallery_listing instead - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ - - - 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 new file mode 100644 index 000000000..eb597836e --- /dev/null +++ b/mod/search/views/default/search/layout.php @@ -0,0 +1,8 @@ +<?php +/** + * The default search layout + * + * @uses $vars['body'] + */ + +echo elgg_view_layout('one_sidebar', array('content' => $vars['body']));
\ No newline at end of file diff --git a/mod/search/views/default/search/list.php b/mod/search/views/default/search/list.php new file mode 100644 index 000000000..90aa28989 --- /dev/null +++ b/mod/search/views/default/search/list.php @@ -0,0 +1,115 @@ +<?php +/** + * List a section of search results corresponding in a particular type/subtype + * or search type (comments for example) + * + * @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']; +$count = $vars['results']['count'] - count($entities); + +if (!is_array($entities) || !count($entities)) { + return FALSE; +} + +$query = http_build_query( + array( + 'q' => $vars['params']['query'], + 'entity_type' => $vars['params']['type'], + 'entity_subtype' => $vars['params']['subtype'], + '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() . "search?$query"; + +$more_items = $vars['results']['count'] - ($vars['params']['offset'] + $vars['params']['limit']); + +// get pagination +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. +$type_str = NULL; + +if (array_key_exists('type', $vars['params']) && array_key_exists('subtype', $vars['params'])) { + $type_str_tmp = "item:{$vars['params']['type']}:{$vars['params']['subtype']}"; + $type_str_echoed = elgg_echo($type_str_tmp); + if ($type_str_echoed != $type_str_tmp) { + $type_str = $type_str_echoed; + } +} + +if (!$type_str && array_key_exists('type', $vars['params'])) { + $type_str = elgg_echo("item:{$vars['params']['type']}"); +} + +if (!$type_str) { + $type_str = elgg_echo('search:unknown_entity'); +} + +// allow overrides for titles +$search_type_str = elgg_echo("search_types:{$vars['params']['search_type']}"); +if (array_key_exists('search_type', $vars['params']) + && $search_type_str != "search_types:{$vars['params']['search_type']}") { + + $type_str = $search_type_str; +} + +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 = ''; +} + +// @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 $nav; diff --git a/mod/search/views/default/search/listing.php b/mod/search/views/default/search/listing.php deleted file mode 100644 index 37850c911..000000000 --- a/mod/search/views/default/search/listing.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php -/** - * Elgg search listing - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ -?> - -<div class="search_listing"> - -<?php -$entities = $vars['entities']; -$count = $vars['count'] - count($vars['entities']); - -if (!is_array($vars['entities']) || !count($vars['entities'])) { - return FALSE; -} - -$title_str = elgg_echo("item:{$vars['params']['type']}:{$vars['params']['subtype']}"); -$body = elgg_view_title($title_str); - -$query = htmlspecialchars(http_build_query( - array( - 'q' => $vars['params']['query'], - 'entity_type' => $vars['params']['type'], - 'entity_subtype' => $vars['params']['subtype'], - 'limit' => get_input('limit', 10), - 'offset' => get_input('offset', 0), - 'search_type' => 'entities', - ) -)); - -$url = "{$vars['url']}pg/search?$query"; -$more = "<a href=\"$url\">+$count more $title_str</a>"; - -echo elgg_view('page_elements/contentwrapper', array('body' => $body)); - -foreach ($entities as $entity) { - if ($owner = $entity->getOwnerEntity()) { - $owner_icon = $owner->getIcon('tiny'); - $icon = "<img src=\"$owner_icon\" />"; - } else { - $icon = ''; - } - $title = $entity->getVolatileData('search_matched_title'); - $description = $entity->getVolatileData('search_matched_description'); - $url = $entity->getURL(); - $title = "<a href=\"$url\">$title</a>"; - $tc = $entity->time_created; - $tu = $entity->time_updated; - $time = friendly_time(($tu > $tc) ? $tu : $tc); - - echo <<<___END -<span class="searchListing"> - <h3 class="searchTitle">$title</h3> - <span class="searchDetails"> - <span class="searchDescription">$description</span><br /> - $icon $time - $more</a> - </span> -</span> -___END; -} - -?> -</div>
\ No newline at end of file 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 new file mode 100644 index 000000000..7474a280c --- /dev/null +++ b/mod/search/views/default/search/search_box.php @@ -0,0 +1,43 @@ +<?php +/** + * Search box + * + * @uses $vars['value'] Current search query + * @uses $vars['class'] Additional class + */ + +if (array_key_exists('value', $vars)) { + $value = $vars['value']; +} elseif ($value = get_input('q', get_input('tag', NULL))) { + $value = $value; +} else { + $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); + +?> + +<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 82d2d62d1..d6394da0d 100644 --- a/mod/search/views/default/search/startblurb.php +++ b/mod/search/views/default/search/startblurb.php @@ -1,13 +1,6 @@ <?php /** - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @todo This doesn't appear to be called by anything. Look into removing. */ -?> -<div class="contentWrapper"> -<?php - echo sprintf(elgg_echo("tag:search:startblurb"), $vars['query']); -?> -</div>
\ No newline at end of file + +echo elgg_echo("tag:search:startblurb", array($vars['query'])); diff --git a/mod/search/views/default/search/tags/listing.php b/mod/search/views/default/search/tags/listing.php deleted file mode 100644 index 9b229b349..000000000 --- a/mod/search/views/default/search/tags/listing.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -/** - * Elgg tag search listing - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ -?> - -<div class="search_listing"> - -<?php -$entities = $vars['entities']; -$count = $vars['count']; - -if (!is_array($vars['entities']) || !count($vars['entities'])) { - return FALSE; -} - -$title_str = elgg_echo("item:{$vars['params']['type']}:{$vars['params']['subtype']}"); -$body = elgg_view_title(elgg_echo('tags')); - -echo elgg_view('page_elements/contentwrapper', array('body' => $body)); - -foreach ($entities as $entity) { - if ($owner = $entity->getOwnerEntity()) { - $owner_icon = $owner->getIcon('tiny'); - $icon = "<img src=\"$owner_icon\" />"; - } else { - $icon = ''; - } - $tags = $entity->getVolatileData('search_matched_tags'); - - $entity_html = elgg_view_entity($entity); - $url = $entity->getURL(); - $title = "<a href=\"$url\">$title</a>"; - $tc = $entity->time_created; - $tu = $entity->time_updated; - $time = friendly_time(($tu > $tc) ? $tu : $tc); - - echo <<<___END -<span class="searchListing"> - <h3 class="searchTitle">$title</h3> - <span class="searchDetails"> - $entity_html - $tags - </span> -</span> -___END; -} - -?> -</div>
\ No newline at end of file diff --git a/mod/search/views/rss/search/comments/entity.php b/mod/search/views/rss/search/comments/entity.php new file mode 100644 index 000000000..e47afec4a --- /dev/null +++ b/mod/search/views/rss/search/comments/entity.php @@ -0,0 +1,54 @@ +<?php +/** + * Search comment view for RSS feeds. + * + * @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 = $comment_data['owner_guid']; +$author = get_user($comment_author_guid); +if ($author) { + $author_name = $author->name; +} + +// @todo Sometimes we find comments on entities we can't display... +if ($entity->getVolatileData('search_unavailable_entity')) { + $title = elgg_echo('search:comment_on', array(elgg_echo('search:unavailable_entity'))); +} else { + if ($entity->getType() == 'object') { + $title = $entity->title; + } else { + $title = $entity->name; + } + + if (!$title) { + $title = elgg_echo('item:' . $entity->getType() . ':' . $entity->getSubtype()); + } + + if (!$title) { + $title = elgg_echo('item:' . $entity->getType()); + } + + $title = elgg_echo('search:comment_on', array($title)); + $title .= ' ' . elgg_echo('search:comment_by') . ' ' . $author_name; + $url = $entity->getURL() . '#annotation-' . $comment_data['annotation_id']; +} + +$description = $comment_data['text']; +$tc = $comment_data['time_created']; + +?> + +<item> + <guid isPermaLink='true'><?php echo htmlspecialchars($url); ?></guid> + <pubDate><?php echo date("r", $tc) ?></pubDate> + <link><?php echo htmlspecialchars($url); ?></link> + <title><![CDATA[<?php echo $title; ?>]]></title> + <description><![CDATA[<?php echo $description; ?>]]></description> +</item> diff --git a/mod/search/views/rss/search/entity.php b/mod/search/views/rss/search/entity.php new file mode 100644 index 000000000..10d28e8e1 --- /dev/null +++ b/mod/search/views/rss/search/entity.php @@ -0,0 +1,24 @@ +<?php +/** + * Search entity view for RSS feeds. + * + * @uses $vars['entity'] + */ + +if (!array_key_exists('entity', $vars) || !($vars['entity'] instanceof ElggEntity)) { + return FALSE; +} + +// title cannot contain HTML but descriptions can. +$title = strip_tags($vars['entity']->getVolatileData('search_matched_title')); +$description = $vars['entity']->getVolatileData('search_matched_description'); + +?> + +<item> + <guid isPermaLink='true'><?php echo htmlspecialchars($vars['entity']->getURL()); ?></guid> + <pubDate><?php echo date("r", $vars['entity']->time_created) ?></pubDate> + <link><?php echo htmlspecialchars($vars['entity']->getURL()); ?></link> + <title><![CDATA[<?php echo $title; ?>]]></title> + <description><![CDATA[<?php echo $description; ?>]]></description> +</item> diff --git a/mod/search/views/rss/search/layout.php b/mod/search/views/rss/search/layout.php new file mode 100644 index 000000000..2c255a9cc --- /dev/null +++ b/mod/search/views/rss/search/layout.php @@ -0,0 +1,6 @@ +<?php +/** + * Search layout for RSS + */ + +echo $vars['body']; diff --git a/mod/search/views/rss/search/list.php b/mod/search/views/rss/search/list.php new file mode 100644 index 000000000..32082fd31 --- /dev/null +++ b/mod/search/views/rss/search/list.php @@ -0,0 +1,25 @@ +<?php +/** + * List a section of search results for RSS feeds. + * + * @uses $vars['results'] + * @uses $vars['params'] + */ + +$entities = $vars['results']['entities']; + +if (!is_array($entities) || !count($entities)) { + return FALSE; +} + +foreach ($entities as $entity) { + if ($view = search_get_search_view($vars['params'], 'entity')) { + $body .= elgg_view($view, array( + 'entity' => $entity, + 'params' => $vars['params'], + 'results' => $vars['results'] + )); + } +} + +echo $body;
\ No newline at end of file |
