diff options
-rw-r--r-- | mod/search/README.txt | 185 | ||||
-rw-r--r-- | mod/search/index.php | 20 | ||||
-rw-r--r-- | mod/search/start.php | 29 | ||||
-rw-r--r-- | mod/search/views/default/search/comments/entity.php | 59 | ||||
-rw-r--r-- | mod/search/views/default/search/comments/listing.php | 107 | ||||
-rw-r--r-- | mod/search/views/default/search/entity.php | 41 | ||||
-rw-r--r-- | mod/search/views/default/search/listing.php | 43 |
7 files changed, 320 insertions, 164 deletions
diff --git a/mod/search/README.txt b/mod/search/README.txt new file mode 100644 index 000000000..047ba10c2 --- /dev/null +++ b/mod/search/README.txt @@ -0,0 +1,185 @@ +Full text search dev reference. + +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 overriden 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 overriden + on a type/subtype basis. + + * Search is separated based upon types/subtypes pairs and any + registered custom search. + + * METADATA, ANNOTATIONS, AND PRIVATE DATA ARE NOT SEARCHED. + These are used in a variety of ways by plugin authors and generally + should not be displayed. There are exceptions (profile fields and + comments) but if a plugin needs to match against metadata, + annotations, or private data it must register a search hook itself. + + +2. SEARCH AND YOUR PLUGIN + + * To appear in search you must register your entity type and subtype + by saying in your plugin's init function: + + register_entity_type($type, $subtype); + + If you are extending ElggObject with your own class, it is also advised + to add a subtype in your plugin's run_once function by saying: + + add_subtype($type, $subtype, $class); + + * 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.1 CONTROLLING SEARCH -- ENTITIES + + * 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 - ENTITY VIEWS + + * 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 interates through each entity + found and passes to the entity view. See 3.3 for more information + about listing views. + + * 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 + params => + results + + * 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 + + +3.3 CONTROLLING SEARCH - LISTING VIEWS + + * 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. + + * 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 + + +3.4 CONTROLLING SEARCH - 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. HINTS + + * 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) + );
\ No newline at end of file diff --git a/mod/search/index.php b/mod/search/index.php index e7081ecb5..adf514caf 100644 --- a/mod/search/index.php +++ b/mod/search/index.php @@ -152,7 +152,9 @@ if ($search_type == 'all' || $search_type == 'entities') { } if (is_array($results['entities']) && $results['count']) { - $results_html .= search_get_listing_html($results['entities'], $results['count'], $params); + if ($view = search_get_search_view($params, 'listing')) { + $results_html .= elgg_view($view, array('results' => $results, 'params' => $params)); + } } } } @@ -169,23 +171,23 @@ if ($search_type == 'all' || $search_type == 'entities') { } if (is_array($results['entities']) && $results['count']) { - $results_html .= search_get_listing_html($results['entities'], $results['count'], $params); + if ($view = search_get_search_view($params, 'listing')) { + $results_html .= elgg_view($view, array('results' => $results, 'params' => $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 (is_array($custom_types)) { + foreach ($custom_types as $type) { if ($search_type != 'all' && $search_type != $type) { continue; } $params['search_type'] = $type; + // custom search types have no subtype. unset($params['subtype']); $results = trigger_plugin_hook('search', $type, $params, array()); @@ -196,7 +198,9 @@ if ($search_type != 'entities' || $search_type == 'all') { } if (is_array($results['entities']) && $results['count']) { - $results_html .= search_get_listing_html($results['entities'], $results['count'], $params); + if ($view = search_get_search_view($params, 'listing')) { + $results_html .= elgg_view($view, array('results' => $results, 'params' => $params)); + } } } } diff --git a/mod/search/start.php b/mod/search/start.php index 92d5c65b6..c54072b91 100644 --- a/mod/search/start.php +++ b/mod/search/start.php @@ -316,48 +316,41 @@ function search_remove_ignored_words($query, $format = 'array') { /** - * 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 = listing || entity * @return string */ -function search_get_listing_html($entities, $count, $params) { - if (!is_array($entities) || !$count) { +function search_get_search_view($params, $view_type) { + if ($view_type != 'listing' && $view_type != 'entity') { return FALSE; } - $view_order = array(); - // check if there's a special search view for this type:subtype + // check if there's a special search listing 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 - ); + $view_order[] = "search/$view_type"; foreach ($view_order as $view) { if (elgg_view_exists($view)) { - return elgg_view($view, $vars); + return $view; } } 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..8b4d286c9 --- /dev/null +++ b/mod/search/views/default/search/comments/entity.php @@ -0,0 +1,59 @@ +<?php +/** + * Elgg search entity + * + * @package Elgg + * @subpackage Core + * @author Curverider Ltd + * @link http://elgg.org/ + */ +$entity = $vars['entity']; + +$owner = get_entity($entity->getVolatileData('search_matched_comment_owner_guid')); + +if ($owner instanceof ElggUser) { + $icon = elgg_view('profile/icon', array('entity' => $owner, 'size' => 'small')); +} else { + $icon = ''; +} + +// @todo Sometimes we find comments on entities we can't display... +if ($entity->getVolatileData('search_unavailable_entity')) { + $title = sprintf(elgg_echo('search:comment_on'), elgg_echo('search:unavailable_entity')); + // 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 = sprintf(elgg_echo('search:comment_on'), $title); + $url = $entity->getURL() . '#annotation-' . $entity->getVolatileData('search_match_annotation_id'); + $title = "<a href=\"$url\">$title</a>"; +} + +$description = $entity->getVolatileData('search_matched_comment'); +$tc = $entity->getVolatileData('search_matched_comment_time_created');; +$time = friendly_time($tc); + +echo <<<___END + <div class="search_listing"> + <div class="search_listing_icon">$icon</div> + <div class="search_listing_info"> + <p class="ItemTitle">$title</p>$description + <p class="ItemTimestamp">$time</p> + </div> + </div> +___END; + +?>
\ No newline at end of file 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 34456bde8..000000000 --- a/mod/search/views/default/search/comments/listing.php +++ /dev/null @@ -1,107 +0,0 @@ -<?php -/** - * Elgg comments search listing - * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ - */ - -if (!is_array($vars['entities']) || !count($vars['entities'])) { - return FALSE; -} - -$title_str = elgg_echo('comments'); - -$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"; - -// get pagination -if (array_key_exists('pagination', $vars) && $vars['pagination']) { - $nav .= elgg_view('navigation/pagination',array( - 'baseurl' => $url, - 'offset' => $vars['params']['offset'], - 'count' => $vars['count'], - 'limit' => $vars['params']['limit'], - )); -} else { - $nav = ''; -} - -// get more links -$more_check = $vars['count'] - ($vars['params']['offset'] + $vars['params']['limit']); -$more = ($more_check > 0) ? $more_check : 0; - -if ($more) { - $title_key = ($more == 1) ? 'comment' : 'comments'; - $more_str = sprintf(elgg_echo('search:more'), $vars['count'], elgg_echo($title_key)); - $more_link = "<div class='search_listing'><a href=\"$url\">$more_str</a></div>"; -} else { - $more_link = ''; -} - -$body = elgg_view_title($title_str); - -foreach ($vars['entities'] as $entity) { - $owner = get_entity($entity->getVolatileData('search_matched_comment_owner_guid')); - - if ($owner instanceof ElggUser) { - $icon = elgg_view('profile/icon', array('entity' => $owner, 'size' => 'small')); - } else { - $icon = ''; - } - - // @todo Sometimes we find comments on entities we can't display... - if ($entity->getVolatileData('search_unavailable_entity')) { - $title = sprintf(elgg_echo('search:comment_on'), elgg_echo('search:unavailable_entity')); - // 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 = sprintf(elgg_echo('search:comment_on'), $title); - $url = $entity->getURL() . '#annotation-' . $entity->getVolatileData('search_match_annotation_id'); - $title = "<a href=\"$url\">$title</a>"; - } - - $description = $entity->getVolatileData('search_matched_comment'); - $tc = $entity->getVolatileData('search_matched_comment_time_created');; - $time = friendly_time($tc); - - $body .= <<<___END - <div class="search_listing"> - <div class="search_listing_icon">$icon</div> - <div class="search_listing_info"> - <p class="ItemTitle">$title</p>$description - <p class="ItemTimestamp">$time</p> - </div> - </div> -___END; -} - -echo $body; -echo $more_link; -echo $nav; diff --git a/mod/search/views/default/search/entity.php b/mod/search/views/default/search/entity.php new file mode 100644 index 000000000..06dd54f39 --- /dev/null +++ b/mod/search/views/default/search/entity.php @@ -0,0 +1,41 @@ +<?php +/** + * Elgg search entity + * + * @package Elgg + * @subpackage Core + * @author Curverider Ltd + * @link http://elgg.org/ + */ + +$entity = $vars['entity']; + +if ($owner = $entity->getOwnerEntity()) { + $icon = elgg_view('profile/icon', array('entity' => $owner, 'size' => 'small')); +} elseif ($entity instanceof ElggUser) { + $icon = elgg_view('profile/icon', array('entity' => $entity, 'size' => 'small')); +} else { + $icon = ''; +} + +$title = $entity->getVolatileData('search_matched_title'); +$description = $entity->getVolatileData('search_matched_description'); +$extra_info = $entity->getVolatileData('search_matched_extra'); +$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 + <div class="search_listing"> + <div class="search_listing_icon">$icon</div> + <div class="search_listing_info"> + <p class="ItemTitle">$title</p>$description + <p class="ItemTimestamp">$time $extra_info</p> + </div> + </div> +___END; + +// php bug. must have close tag after heredocs +?>
\ No newline at end of file diff --git a/mod/search/views/default/search/listing.php b/mod/search/views/default/search/listing.php index 867523d65..36032940d 100644 --- a/mod/search/views/default/search/listing.php +++ b/mod/search/views/default/search/listing.php @@ -8,11 +8,10 @@ * @link http://elgg.org/ */ +$entities = $vars['results']['entities']; +$count = $vars['results']['count'] - count($entities); -$entities = $vars['entities']; -$count = $vars['count'] - count($vars['entities']); - -if (!is_array($vars['entities']) || !count($vars['entities'])) { +if (!is_array($entities) || !count($entities)) { return FALSE; } @@ -34,7 +33,7 @@ if (array_key_exists('pagination', $vars) && $vars['pagination']) { $nav .= elgg_view('navigation/pagination',array( 'baseurl' => $url, 'offset' => $vars['params']['offset'], - 'count' => $vars['count'], + 'count' => $vars['results']['count'], 'limit' => $vars['params']['limit'], )); } else { @@ -84,7 +83,7 @@ if (array_key_exists('pagination', $vars['params']) && $vars['params']['paginati $nav .= elgg_view('navigation/pagination',array( 'baseurl' => $url, 'offset' => $vars['params']['offset'], - 'count' => $vars['count'], + 'count' => $vars['results']['count'], 'limit' => $vars['params']['limit'], )); } else { @@ -92,7 +91,7 @@ if (array_key_exists('pagination', $vars['params']) && $vars['params']['paginati } // get any more links. -$more_check = $vars['count'] - ($vars['params']['offset'] + $vars['params']['limit']); +$more_check = $vars['results']['count'] - ($vars['params']['offset'] + $vars['params']['limit']); $more = ($more_check > 0) ? $more_check : 0; if ($more) { @@ -106,31 +105,13 @@ if ($more) { $body = elgg_view_title($type_str); foreach ($entities as $entity) { - if ($owner = $entity->getOwnerEntity()) { - $icon = elgg_view('profile/icon', array('entity' => $owner, 'size' => 'small')); - } elseif ($entity instanceof ElggUser) { - $icon = elgg_view('profile/icon', array('entity' => $entity, 'size' => 'small')); - } else { - $icon = ''; + if ($view = search_get_search_view($vars['params'], 'entity')) { + $body .= elgg_view($view, array( + 'entity' => $entity, + 'params' => $vars['params'], + 'results' => $vars['results'] + )); } - $title = $entity->getVolatileData('search_matched_title'); - $description = $entity->getVolatileData('search_matched_description'); - $extra_info = $entity->getVolatileData('search_matched_extra'); - $url = $entity->getURL(); - $title = "<a href=\"$url\">$title</a>"; - $tc = $entity->time_created; - $tu = $entity->time_updated; - $time = friendly_time(($tu > $tc) ? $tu : $tc); - - $body .= <<<___END - <div class="search_listing"> - <div class="search_listing_icon">$icon</div> - <div class="search_listing_info"> - <p class="ItemTitle">$title</p>$description - <p class="ItemTimestamp">$time $extra_info</p> - </div> - </div> -___END; } echo $body; echo $more_link; |