Commit ced8df20 authored by Nathan Ogden's avatar Nathan Ogden
Browse files

SearchService::search now returns a SearchResults object

parent f4570fd2
......@@ -11,26 +11,9 @@ Add the bundle to app/AppKernel.php (ezpublish/EzPublishKernel.php)...
new ThinkCreative\SearchBundle\ThinkCreativeSearchBundle()
Run...
php app/console assets:install --symlink (php ezpublish/console assets:install --symlink)
Add...
ThinkCreativeSearchBundleRoutes:
resource: "@ThinkCreativeSearchBundle/Resources/config/routing.yml"
to app/config/routing.yml (ezpublish/config/routing.yml).
Optionally add...
@ThinkCreativeSearchBundle/Resources/public/scss/styles.scss
to your base header template's stylesheet(s) to get included styles.
## Sample Config Entry
The following is an example of what
The following is a sample configuration...
think_creative_search:
solr:
......@@ -43,38 +26,39 @@ The following is an example of what
filter: thinkcreative.search.filter.ezpublish
search:
query: "meta_class_identifier_ms:article OR meta_class_identifier_ms:blog_post OR meta_class_identifier_ms:news_item OR meta_class_identifier_ms:event meta_class_identifier_ms:folder"
highlighting: true
facet_fields:
- display_name: "Content Type"
identifier: category
field: "meta_class_identifier_ms"
facet_ranges:
- display_name: "Date"
identifier: date
fields:
- "attr_publication_date_dt"
- "meta_published_dt"
start: "*"
end: "NOW"
gaps:
- name: "Less Than A Week"
value: "-7DAYS"
- name: "Less Than A Month"
value: "-1MONTH"
- name: "Less Than 3 Months"
value: "-3MONTH"
- name: "More Than 3 Months"
value: "-3MONTH+1MINUTE"
sort:
- name: "Relevance"
value: "score desc, attr_publication_date_dt desc, meta_published_dt desc"
- name: "Content Type"
value: "meta_class_identifier_ms asc, score desc"
- name: "Alphabetically"
value: "meta_name_t asc"
- name: "Publication Date"
value: "attr_publication_date_dt desc, meta_published_dt desc, score desc"
- name: default
query: "meta_class_identifier_ms:article OR meta_class_identifier_ms:blog_post OR meta_class_identifier_ms:news_item OR meta_class_identifier_ms:event meta_class_identifier_ms:folder"
highlighting: true
facet_fields:
- display_name: "Content Type"
identifier: category
field: "meta_class_identifier_ms"
facet_ranges:
- display_name: "Date"
identifier: date
fields:
- "attr_publication_date_dt"
- "meta_published_dt"
start: "*"
end: "NOW"
gaps:
- name: "Less Than A Week"
value: "-7DAYS"
- name: "Less Than A Month"
value: "-1MONTH"
- name: "Less Than 3 Months"
value: "-3MONTH"
- name: "More Than 3 Months"
value: "-3MONTH+1MINUTE"
sort:
- name: "Relevance"
value: "score desc, attr_publication_date_dt desc, meta_published_dt desc"
- name: "Content Type"
value: "meta_class_identifier_ms asc, score desc"
- name: "Alphabetically"
value: "meta_name_t asc"
- name: "Publication Date"
value: "attr_publication_date_dt desc, meta_published_dt desc, score desc"
**solr:**
Information needed to connect to solr server.
......@@ -99,3 +83,17 @@ Used to create facet searches based on ranges, such as a date range.
**search.sort:**
Used by sort dropdown on search page to provide sorting options for search results.
## Usage
Get the 'thinkcreative.search.search' and run the method 'search' with the following paramters...
- Search Type :: Search configuration to use
- Query String :: Query string for search
- Limit :: Limit results per page
- Offset :: Result offset
- Sort :: Defines how search results is sorted
- Facet Values :: Array of current facet values "facet_identifier => facet_value" ... example: "date => -1MONTH"
Returns ThinkCreative\SearchBundle\Classes\SearchResults.
\ No newline at end of file
......@@ -15,7 +15,8 @@
"require": {
"php": ">=5.3.2",
"symfony/symfony": "*",
"ezsystems/ezpublish-kernel": "*"
"ezsystems/ezpublish-kernel": "*",
"pagerfanta/pagerfanta" : "*"
},
"target-dir": "",
"autoload": {
......
<?php
namespace ThinkCreative\SearchBundle\Classes;
use Pagerfanta\Adapter\FixedAdapter;
use Pagerfanta\Pagerfanta;
use Pagerfanta\View\TwitterBootstrapView;
/**
* Class containing results of a SOLR search
*/
class SearchResults
{
/**
* Array containing results of search.
* @var array
*/
protected $resultsArray;
/**
* Pagerfanta pager
* @var Pagerfanta
*/
protected $pager;
/**
* Constructor.
* @param array $results_array
*/
public function __construct(array $results_array)
{
// results array
$this->resultsArray = $results_array;
// build pager object
if (isset($this->resultsArray['solr']['response']['docs'])) {
// build adapter
$adapter = new FixedAdapter($this->getNumberFound(), $this->resultsArray['solr']['response']['docs']);
// get pager
$this->pager = new Pagerfanta($adapter);
$this->pager->setMaxPerPage(
isset($this->resultsArray['solr']['responseHeader']['params']['rows']) ? $this->resultsArray['solr']['responseHeader']['params']['rows'] : 10
);
$this->pager->setCurrentPage(
floor( (isset($this->resultsArray['solr']['responseHeader']['params']['start']) ? $this->resultsArray['solr']['responseHeader']['params']['start'] : 0) / $this->pager->getMaxPerPage() ) + 1
);
}
}
/**
* Return query string.
*/
public function getQueryString()
{
return isset($this->resultsArray['solr']['responseHeader']['params']['q']) ? strip_tags(trim($this->resultsArray['solr']['responseHeader']['params']['q'])) : "";
}
/**
* Return number of results.
*/
public function getNumberFound()
{
return isset($this->resultsArray['solr']['response']['numFound']) ? $this->resultsArray['solr']['response']['numFound'] : 0;
}
/**
* Return search results array
*/
public function getResults()
{
return isset($this->resultsArray['solr']['response']['docs']) ? $this->resultsArray['solr']['response']['docs'] : array();
}
/**
* Get a Pagerfanta pagination object
* @return Pagerfanta
*/
public function getPager()
{
return $this->pager;
}
/**
* Render pagination HTML for Twitter Bootstrap
* @param array $params
* @param string $baseUrl
* @return string
*/
public function renderControlsForTwitterBootstrap(array $url_params = array(), array $options = array(), $base_url = "")
{
// get pager
$pager = $this->getPager();
// router generator
$routeGenerator = function($page) use ($pager, $url_params, $base_url) {
$url_params['offset'] = ($page * $pager->getMaxPerPage()) - $pager->getMaxPerPage();
return $base_url . "?" . http_build_query($url_params);
};
// create view
$view = new TwitterBootstrapView();
$options = array_merge($options, array(
'proximity' => 3,
'prev_message' => "&laquo;",
'next_message' => "&raquo;"
));
$output = $view->render($pager, $routeGenerator, $options);
// slight hack to get the 'pagination' class on the UL element
$output = str_replace("<ul", "<ul class='pagination'", $output);
return $output;
}
/**
* Returns true if there are facets
* @param boolean
*/
public function hasFacets()
{
if (!isset($this->resultsArray['facets'])) {
return false;
}
foreach ($this->resultsArray['facets'] as $facet) {
if (count($facet) > 0) {
return true;
}
}
return false;
}
/**
* Return array containing all types of facets as key
* @return array
*/
public function getAllFacets()
{
if (!$this->hasFacets()) {
return array();
}
$arr = array();
foreach ($this->resultsArray['facets'] as $facets) {
$arr = array_merge($arr, $facets);
}
return $arr;
}
/**
* Return facet fields of given type.
* @param string $type
* @return array
*/
public function getFacets($type)
{
return isset($this->resultsArray['facets'][$type]) ? $this->resultsArray['facets'][$type] : array();
}
/**
* Get facet fields
* @return array
*/
public function getFacetFields() {
return $this->getFacets("fields");
}
/**
* Get facet ranges
* @return array
*/
public function getFacetRanges() {
return $this->getFacets("ranges");
}
/**
* Get facet queries
* @return array
*/
public function getFacetQueries() {
return $this->getFacets("queries");
}
/**
* Get highest score in search results
* @return integer
*/
public function getMaxScore()
{
return isset($this->resultsArray['solr']['response']['maxScore']) ? $this->resultsArray['solr']['response']['maxScore'] : 0;
}
}
\ No newline at end of file
{# Search Feedback Template #}
<div class="feedback">
{% if query|striptags|trim %}
<p>Search for "{{ query|striptags|trim }}" returned {{ results.solr.response.numFound }} result(s).</p>
{% if searchResults.getQueryString() %}
<p>Search for "{{ searchResults.getQueryString() }}" returned {{ searchResults.getNumberFound() }} result(s).</p>
{% else %}
<p>Search returned {{ results.solr.response.numFound }} result(s).</p>
<p>Search returned {{ searchResults.getNumberFound() }} result(s).</p>
{% endif %}
</div>
\ No newline at end of file
{# Make a pagination macro so we don't have to repeat the following code #}
{% macro pagination(search_type, query, params, results_count) %}
{% set offset = params.offset %}
{% set limit = params.limit %}
{% set total_pages = (results_count / limit)|round(0, 'ceil') %}
{% set current_page = (offset / limit)|round(0, 'floor') + 1 %}
{% set max_page_display = 5 %}
{% if results_count > limit %}
<ul class="pagination">
{# Previous Page #}
<li{% if offset <= 0 %} class="disabled"{% endif %}><a href="{% if offset <= 0 %}javascript: void(0);{% else %}?{{ params|merge({'query': query, "offset" : offset - limit, "search_type" : search_type })|url_encode }}{% endif %}">«</a></li>
{# First page #}
<li{% if offset <= 0 %} class="active"{% endif %}><a href="?{{ params|merge({'query': query, "offset" : 0, "search_type" : search_type })|url_encode }}">1{% if total_pages > 1 %}..{% endif %}</a></li>
{# Add extra pages if page is > total_pages - max_page_display / 2 #}
{% if total_pages > 1 and current_page > total_pages - (max_page_display / 2)|round(0, 'floor') %}
{% for i in (total_pages + 1) - max_page_display..current_page - (max_page_display / 2)|round(0, 'ceil') %}
{% if i < total_pages and i > 1 %}
<li{% if offset == (i * limit) - limit %} class="active"{% endif %}><a href="?{{ params|merge({'query': query, "offset" : (i * limit) - limit, "search_type" : search_type })|url_encode }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% endif %}
{# Pages +- max_page_display of current page #}
{% if total_pages > 1 %}
{% for i in (current_page - (max_page_display / 2))|round(0, 'floor') + 1..((offset / limit) + (max_page_display / 2))|round(0, 'floor') + 1 %}
{% if i > 1 and i <= total_pages - 1 %}
<li{% if offset == (i * limit) - limit %} class="active"{% endif %}><a href="?{{ params|merge({'query': query, "offset" : (i * limit) - limit, "search_type" : search_type })|url_encode }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% endif %}
{# Add extra pages if current page is < max_page_display / 2 #}
{% if current_page < (max_page_display / 2) + 1 %}
{% for i in current_page + 1 + (max_page_display / 2)|round(0, 'floor')..max_page_display + 1 %}
{% if i < total_pages %}
<li{% if offset == (i * limit) - limit %} class="active"{% endif %}><a href="?{{ params|merge({'query': query, "offset" : (i * limit) - limit, "search_type" : search_type })|url_encode }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% endif %}
{# Last page #}
{% if total_pages > 1 %}
<li{% if offset + limit >= results_count %} class="active"{% endif %}><a href="?{{ params|merge({'query': query, "offset" : (total_pages * limit) - limit, "search_type" : search_type })|url_encode }}">..{{ total_pages }}</a></li>
{% endif %}
{# Next Page #}
<li{% if offset + limit >= results_count %} class="disabled"{% endif %}><a href="{% if offset + limit >= results_count %}javascrip: void(0);{% else %}?{{ params|merge({'query': query, "offset" : offset + limit, "search_type" : search_type })|url_encode }}{% endif %}">»</a></li>
</ul>
{% endif %}
{% endmacro %}
{# Search Form Template #}
<div class="bg gray-lighter search search-form">
<div class="form-group">
<input type="text" placeholder="Keyword" name="query" class="searchtext form-control" value="{{ query }}">
<select name="sort" class="form-control">
<input type="text" placeholder="Keyword" name="query" class="searchtext form-control" value="{{ searchResults.getQueryString() }}">
{# <select name="sort" class="form-control">
{% for value in sort_options %}
<option{% if params.sort == value.value %} selected="selected"{% endif %} value="{{ value.value }}">{{ value.name }}</option>
{% endfor %}
</select>
</select> #}
<input type="submit" name="search_submit" value="Search" class="btn btn-large brand-b">
<div class="clear"></div>
</div>
......
......@@ -5,9 +5,9 @@
<div class="search-results">
{# No Results #}
{% if results.solr.response.numFound is not defined or results.solr.response.numFound <= 0 %}
{% if searchResults.getNumberFound() <= 0 %}
<div class="warning alert alert-warning">
<strong>No results were found when searching for "{{ query }}".</strong>
<strong>No results were found when searching for "{{ searchResults.getQueryString() }}".</strong>
<p>Search tips</p>
<ul>
<li>Check spelling of keywords.</li>
......@@ -24,14 +24,14 @@
<h2>Results</h2>
</div>
<div class="col-md-8 col-xs-12">
{{ search_macros.pagination(search_type, query, params, results.solr.response.numFound) }}
{{ searchResults.renderControlsForTwitterBootstrap(app.request.query.all)|raw }}
<div class="clear"></div>
</div>
<div class="clear"></div>
</div>
<div class="spacer"></div>
{% for item in results.solr.response.docs %}
{% for item in searchResults.getIterator() %}
<div class="search-item">
<h3>
......@@ -46,12 +46,12 @@
{#{% for keyword in item.keywords %}
<a href="{{ path("search", params|merge({'query': keyword})) }}" class="btn btn-large btn-primary">{{ keyword }}</a>
{% endfor %}#}
<div class="score-percent">{{ ((item.score / results.solr.response.maxScore) * 100)|round }}% Match</div>
<div class="score-percent">{{ ((item.score / searchResults.getMaxScore()) * 100)|round }}% Match</div>
</div>
{% endfor %}
{{ search_macros.pagination(search_type, query, params, results.solr.response.numFound) }}
{{ searchResults.renderControlsForTwitterBootstrap(app.request.query.all)|raw }}
<div class="clear"></div>
{% endif %}
......
{# Search Sidebar #}
{% set params = app.request.query.all %}
<div class="search-sidebar">
{% if results.facets is defined %}
{% if searchResults.hasFacets() %}
<nav id="sidemenu" class="bg brand-b">
<h2 class="white">Filter Results</h2>
{% for name, items in searchResults.getAllFacets() %}
<h3 class="white">By {{ name|title }}</h3>
<ul class="facets">
{% for item in items %}
<li{% if params[item.identifier] is defined and ( params[item.identifier] == item.query_value or item.query_value in params[item.identifier] ) %} class="active"{% endif %}>
{% for facet in results.facets %}
{% if facet %}
{% for name, items in facet %}
<h3 class="white">By {{ name|title }}</h3>
<ul class="facets">
{% for item in items %}
<li{% if params[item.identifier] is defined and ( params[item.identifier] == item.query_value or item.query_value in params[item.identifier] ) %} class="active"{% endif %}>
<div class="checkbox">
<label>
<input type="checkbox" name="{{ item.identifier }}[]" value="{{ item.query_value }}"{% if params[item.identifier] is defined and ( params[item.identifier] == item.query_value or item.query_value in params[item.identifier] ) %} checked{% endif %}>
{{ item.display }} ({{ item.count }})
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="{{ item.identifier }}[]" value="{{ item.query_value }}"{% if params[item.identifier] is defined and ( params[item.identifier] == item.query_value or item.query_value in params[item.identifier] ) %} checked{% endif %}>
{{ item.display }} ({{ item.count }})
</label>
</div>
</li>
{% endfor %}
</ul>
{% if not loop.last %}<div class="spacer"></div>{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</li>
{% endfor %}
</ul>
{% if not loop.last %}<div class="spacer"></div>{% endif %}
{% endfor %}
</nav>
{% endif %}
</div>
\ No newline at end of file
......@@ -2,6 +2,7 @@
namespace ThinkCreative\SearchBundle\Services;
use ThinkCreative\SearchBundle\Solr\SolrQuery;
use ThinkCreative\SearchBundle\Exception\SearchTypeNotAvailableException;
use ThinkCreative\SearchBundle\Classes\SearchResults;
/**
* Service for performing SOLR searches
......@@ -301,7 +302,7 @@ class SearchService
}
// perform search
return $solr->search();
return new SearchResults($solr->search());
}
......
......@@ -394,10 +394,8 @@ class SolrQuery
}
return array(
"solr" => $results,
"total_results" => $results['response']['numFound'],
"facets" => array(
"fields" => $facetFields,
"ranges" => $facetRanges,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment