5 * A custom URL processor for pretty paths.
9 * Extension of FacetapiUrlProcessor.
11 class FacetapiUrlProcessorPrettyPaths
extends FacetapiUrlProcessorStandard
{
14 * An array of filter params.
18 protected
$filterParams = array();
21 * An array of pretty path segments.
25 protected
$pathSegments = array();
28 * The adapter that uses this FacetapiUrlProcessor.
30 * @var FacetapiAdapter
32 protected
$adapter = array();
35 * Options that apply for this FacetapiUrlProcessor.
37 protected
$options = array();
40 * The full path string, as we override $_GET['q].
42 protected
$fullPath = NULL
;
45 * The base path for constructing pretty facet paths.
47 protected
$basePath = NULL
;
50 * Constructor, sets adapter.
52 * @param FacetapiAdapter $adapter
53 * The adapter that uses this FacetapiUrlProcessor.
55 function __construct(FacetapiAdapter
$adapter) {
56 $this->adapter
= $adapter;
57 $this->options
= variable_get('facetapi_pretty_paths_searcher_' .
$this->adapter
->getSearcher() .
'_options');
61 * Pulls facet params from the $_GET variable.
63 * Overrides FacetapiUrlProcessorStandard::fetchParams().
65 public
function fetchParams() {
67 // Skip pretty paths logic for admin pages, as
68 // for pretty paths, we have to manipulate $_GET['q].
69 // @todo: Fix this workaround?
70 if (strpos($_GET['q'], 'admin/') === 0) {
74 $this->fullPath
= $_GET['q'];
75 $this->filterParams
= array();
76 $facets = $this->adapter
->getEnabledFacets();
78 $args = explode('/', $_GET['q']);
80 // Traverse all segments "<alias>/<value>" from right to left (array_pop).
81 while (($value = array_pop($args)) !== NULL
) {
82 if (!($alias = array_pop($args))) {
83 // Restore last url part, if this was an incomplete segment.
84 array_push($args, $value);
88 $alias = $this->decodePathSegmentAlias($alias);
90 // Look for matching facet.
91 foreach ($facets as
$facet_alias => $facet) {
92 $pretty_paths_alias = $this->getFacetPrettyPathsAlias($facet);
94 // Add to params if alias from url matches alias from facet settings.
95 if ($pretty_paths_alias == $alias) {
97 $value = $this->decodePathSegmentValue($value, $facet);
99 $this->filterParams
[] = rawurlencode($facet['field']) .
':' .
$value;
101 // "Copy" to prevent $segment['facet'] from being overridden.
103 $segment = $this->getPathSegment($my_facet, $value);
104 $this->pathSegments
[$segment['key']] = $segment;
107 // When no more facet path segments have been found,
108 // we assume the rest of the url as the search basePath.
110 array_push($args, $alias, $value);
115 // Set the rest of the url as the search basePath.
116 $this->basePath
= implode('/', $args);
118 // Since we traversed all segments from right to left we need to reverse
119 // them here. Reason is because the order matters for some facets.
120 $this->pathSegments
= array_reverse($this->pathSegments
);
121 $this->filterParams
= array_reverse($this->filterParams
);
123 // Return params as expected.
125 $params['q'] = $this->basePath
;
126 $params[$this->filterKey
] = $this->filterParams
;
131 * Remove the filter query part from params and return it.
133 * Overrides FacetapiUrlProcessorStandard::getQueryString().
135 public
function getQueryString(array $facet, array $values, $active) {
136 $params = $this->params
;
137 unset($params[$this->filterKey
]);
142 * Pretty paths will be generated as "search/url/segment1/segment2/".
144 * By default, a segment will look like:
147 * Overrides FacetapiUrlProcessorStandard::getFacetPath().
149 public
function getFacetPath(array $facet, array $values, $active) {
150 $segments = $this->pathSegments
;
151 $active_items = $this->adapter
->getActiveItems($facet);
153 // Adds to segments if inactive, removes if active.
154 foreach ($values as
$value) {
155 $segment = $this->getPathSegment($facet, $value);
156 if ($active && isset($active_items[$value])) {
157 unset($segments[$segment['key']]);
160 $segments[$segment['key']] = $segment;
164 $path = $this->constructPath($segments);
168 /* ### Generic helpers ### */
171 * Construct a path from for a given array of filter segments.
173 * @param array $segments
176 public
function constructPath(array $segments) {
177 if (!empty($this->options
['sort_path_segments'])) {
178 // Sort to avoid multiple urls with duplicate content.
179 uksort($segments, 'strnatcmp');
181 $path = $this->getBasePath();
182 // Add all path segments.
183 foreach ($segments as
$key => $segment) {
184 $this->encodePathSegment($segment, $segment['facet']);
185 $path .
= '/' .
$segment['alias'] .
'/' .
$segment['value'];
191 * Generate a path segment for a given facet + value.
193 protected
function getPathSegment(&$facet, $value) {
194 $pretty_paths_alias = $this->getFacetPrettyPathsAlias($facet);
196 'alias' => $pretty_paths_alias,
199 'key' => $pretty_paths_alias .
"_" .
$value,
203 protected
function encodePathSegment(array &$segment, array $facet) {
204 facetapi_pretty_paths_coder_callback('encodePathSegment',
206 'segment' => &$segment,
208 'adapter' => $this->adapter
,
213 protected
function decodePathSegmentAlias($alias) {
214 return facetapi_pretty_paths_coder_callback('decodePathSegmentAlias',
221 protected
function decodePathSegmentValue($value, array $facet) {
222 return facetapi_pretty_paths_coder_callback('decodePathSegmentValue',
225 'adapter' => $this->adapter
,
232 * Returns the pretty_paths_alias for a given facet.
234 * If there is no custom pretty_paths_alias settings, in will default
235 * to rawurlencode($facet['field alias']).
237 public
function getFacetPrettyPathsAlias(array $facet) {
238 $facet_settings = $this->adapter
->getFacetSettingsGlobal($facet);
239 // Path alias defaults to facet_name.
240 return !empty($facet_settings->settings
['pretty_paths_alias']) ?
241 $facet_settings->settings
['pretty_paths_alias'] : $facet['field alias'];
245 * Implements FacetapiUrlProcessorPrettyPaths::setBreadcrumb().
247 public
function setBreadcrumb() {
248 $breadcrumb = drupal_get_breadcrumb();
250 // $facets to use later to get the segment
251 $facets = $this->adapter
->getEnabledFacets();
253 // Gets search keys and active items form the adapter.
254 $keys = $this->adapter
->getSearchKeys();
255 $active_items = $this->adapter
->getAllActiveItems();
257 $item = menu_get_item();
258 $last_load_func = is_array($item['load_functions']) ?
end($item['load_functions']) : NULL
;
259 if (!empty($item['title']) && ((!$keys && $active_items) || ($keys && $last_load_func != 'menu_tail_load'))) {
260 $last = end($breadcrumb);
261 $this_page = l($item['title'], $item['href'], $item['localized_options']);
262 if ($last != $this_page) {
263 $breadcrumb[] = $this_page;
267 // Initializes base breadcrumb query.
268 $query = $this->params
;
269 unset($query[$this->filterKey
]);
271 // Adds the current search to the query.
273 // The last item should be text, not a link.
274 $breadcrumb[] = $active_items ?
l($keys, $this->getBasePath(), array('query' => $query)) : check_plain($keys);
277 // Use this to track the active facet trail.
280 // Keep track of what the last item is.
281 $last = end($active_items);
283 foreach ($active_items as
$item) {
284 // Add this breadcrumb segment to the complete segments list.
285 // Do not use aliases, use the real facet
286 $segments[] = $this->getPathSegment($facets[$item['facets'][0]], $item['value']);
288 // Replaces with the mapped value.
289 $value = $this->adapter
->getMappedValue($item['facets'][0], $item['value']);
291 // The last item should be text, not a link.
292 if ($last == $item) {
293 $breadcrumb[] = !empty($value['#html']) ?
$value['#markup'] : check_plain($value['#markup']);
296 $base = $this->adapter
->getSearchPath();
297 $path = $this->constructPath($segments);
298 $breadcrumb[] = l($value['#markup'], $path, array('html' => !empty($value['#html'])));
302 // Sets the breadcrumb trail with h keys and filters.
303 drupal_set_breadcrumb($breadcrumb);
306 public
function getFullPath() {
307 return $this->fullPath
;
310 public
function getBasePath() {
311 $basePath = $this->basePath
;
312 // Mimic search api base path logic which allows to set a custom base path.
313 // @see SearchApiFacetapiAdapter::getSearchPath().
314 if (module_exists('search_api_facetapi') && $this->adapter instanceof SearchApiFacetapiAdapter
) {
315 $search = $this->adapter
->getCurrentSearch();
316 if ($search && $search[0]->getOption('search_api_base_path')) {
317 $basePath = $search[0]->getOption('search_api_base_path');