views_navigation.inc in Views navigation 7
Views navigation main include file.
File
views_navigation.incView source
<?php
/**
* @file
* Views navigation main include file.
*/
/**
* Get a view query from cache.
*/
function views_navigation_get_cached_query($cid) {
$cache = cache_get('query-' . $cid, VIEWS_NAVIGATION_CACHE_BIN);
if ($cache && _views_navigation_query_is_supported($cache->data)) {
return $cache->data;
}
}
/**
* Get a view result from cache.
*/
function views_navigation_get_cached_result($cid) {
$cache = cache_get('result-' . $cid, VIEWS_NAVIGATION_CACHE_BIN);
return $cache ? $cache->data : FALSE;
}
/**
* Store a view query in cache.
*
* Return the cid or FALSE if the query is not stored.
*/
function views_navigation_store_query($view) {
if (_views_navigation_query_is_supported($view->query)) {
$query_to_store = clone $view->query;
$view_to_store = clone $view;
$dh_to_store = clone $view_to_store->display_handler;
// Handle the case when there is no pager.
if (!isset($view_to_store->total_rows) || empty($view_to_store->total_rows)) {
$view_to_store->total_rows = count($view_to_store->result);
}
// If there is zero or one result, we do nothing.
if ($view_to_store->total_rows < 2) {
return FALSE;
}
// Store the back destination if needed.
if ($view->display_handler
->get_option('views_navigation_back')) {
$raw_destination = drupal_get_destination();
// We must remove the "page=X" query parameter to keep the stored info
// identical across pages (same hash needed).
// Thus the back link will always lead to the first page (we can't easily
// know which page the user was on when he left the view : that would need
// an extra GET parameter).
$parsed_destination = drupal_parse_url(urldecode(reset($raw_destination)));
unset($parsed_destination['query']['page']);
$view_to_store->back_destination = $parsed_destination;
// Store the view's title if needed.
if ($view->display_handler
->get_option('views_navigation_title')) {
$view_to_store->back_title = $view_to_store
->get_title();
}
}
// Remove useless properties and query limit. This is the not easy part.
// We must ensure that the stored query is the same across pagination.
$query_to_store->pager_backup = $query_to_store->pager;
unset($query_to_store->display);
unset($query_to_store->header);
unset($query_to_store->pager);
$plugin = _views_navigation_get_query_plugin($query_to_store);
switch ($plugin) {
case 'default':
unset($query_to_store->limit);
$query_to_store->offset = 0;
break;
case 'search_api':
$query_to_store
->set_limit(NULL);
$query_to_store
->set_offset(0);
break;
}
$keys = [
'base_database',
'name',
'total_rows',
'back_destination',
'back_title',
];
foreach ($view_to_store as $key => $value) {
if (!in_array($key, $keys)) {
unset($view_to_store->{$key});
}
}
// Most of the display handler info is unneeded and might change across
// pagination. But we need some info though.
// Unsure we keep inherited cache options.
$dh_to_store->options['cache'] = $dh_to_store
->get_option('cache');
foreach ($dh_to_store as $key => $value) {
// For now, we found no bug by keeping all the options, and only that.
if (!in_array($key, [
'options',
])) {
unset($dh_to_store->{$key});
}
}
$view_to_store->display_handler = $dh_to_store;
$query_to_store->view = $view_to_store;
// Allow modules to alter the stored info.
drupal_alter('views_navigation_stored_query', $query_to_store, $view);
$cid = views_navigation_get_query_cid($query_to_store);
// The query may be stored already.
if (!views_navigation_get_cached_query($cid)) {
// We need to store the query as long as the user navigates across the
// result set. One day should be far enough.
// @see views_navigation_get_links()
cache_set('query-' . $cid, $query_to_store, VIEWS_NAVIGATION_CACHE_BIN, REQUEST_TIME + 86400);
}
return $cid;
}
return FALSE;
}
/**
* Get the unique cid corresponding to a view query.
*/
function views_navigation_get_query_cid($query) {
return drupal_hash_base64(serialize($query));
}
/**
* Redirect to the next or previous entity.
*/
function views_navigation_router($cid, $pos) {
$back_pos = arg(3);
if (list($path, $options) = _views_navigation_get_data($cid, $pos, $back_pos)) {
drupal_goto($path, $options);
}
// Something went wrong.
watchdog('views_navigation', 'Could not find the requested page.', \func_get_args(), WATCHDOG_ERROR);
return t("Sorry, could not find the requested page.");
}
/**
* Get the result of a query, as an array of etids keyed by position.
*/
function views_navigation_get_result($cid) {
if (!($result = views_navigation_get_cached_result($cid))) {
if ($query = views_navigation_get_cached_query($cid)) {
$result = [];
$plugin = _views_navigation_get_query_plugin($query);
$entity_type = _views_navigation_get_entity_type($query);
$id_key = _views_navigation_get_id_key($entity_type);
views_module_include('views');
switch ($plugin) {
case 'default':
$real_query = $query
->query();
$rows = $real_query
->execute();
foreach ($rows as $pos => $row) {
$result[$pos] = $row->{$id_key};
}
break;
case 'search_api':
$real_query = $query
->getSearchApiQuery();
$real_query
->range(0, NULL);
$response = $real_query
->execute();
$rows = $response['results'];
$pos = 0;
foreach ($rows as $row) {
$result[$pos] = $row['id'];
$pos++;
}
break;
}
// Try to store the result set for a relevant time according to the view's
// cache settings.
switch ($query->view->display_handler->options['cache']['type']) {
case 'none':
// Never cache the result. We assume the view is lightweight and
// results won't change during navigation.
break;
case 'time':
// This cache could be set at a different time from the normal view's
// one. So that this can still lead to inconsistency with fast-moving
// views. But this is better than nothing as we have a idea on how
// often this view changes.
$ttl = REQUEST_TIME + $query->view->display_handler->options['cache']['results_lifespan'];
break;
default:
// Unhandled cache backends. Let's store the result set during one
// hour. This is better than never or for ever.
$ttl = REQUEST_TIME + 3600;
}
if (isset($ttl)) {
cache_set('result-' . $cid, $result, VIEWS_NAVIGATION_CACHE_BIN, $ttl);
}
}
}
return $result;
}
/**
* Build and render the previous/next links for the entity being viewed.
*/
function views_navigation_get_links($view_mode = 'full') {
// Ensure we can show links.
$cid = isset($_GET[VIEWS_NAVIGATION_CACHE_ID_PARAMETER]) ? $_GET[VIEWS_NAVIGATION_CACHE_ID_PARAMETER] : NULL;
$pos = isset($_GET[VIEWS_NAVIGATION_POSITION_PARAMETER]) ? $_GET[VIEWS_NAVIGATION_POSITION_PARAMETER] : NULL;
if (!isset($cid) || !isset($pos) || $view_mode != 'full') {
return FALSE;
}
$query = views_navigation_get_cached_query($cid);
if (!$query) {
return FALSE;
}
$data = [];
if ($query->view->display_handler
->get_option('views_navigation') && ($query->view->display_handler
->get_option('views_navigation_cycle') || $pos > 0)) {
$data['previous'] = [
'default_title' => t('Previous'),
'pos' => $pos - 1,
'back pos' => NULL,
];
}
if (isset($query->view->back_destination)) {
$data['back'] = [
'default_title' => isset($query->view->back_title) ? t('Back to %title', [
'%title' => $query->view->back_title,
]) : t('Back'),
'pos' => 'back',
'back pos' => $pos,
];
}
if ($query->view->display_handler
->get_option('views_navigation') && ($query->view->display_handler
->get_option('views_navigation_cycle') || $pos < $query->view->total_rows - 1)) {
$data['next'] = [
'default_title' => t('Next'),
'pos' => $pos + 1,
'back pos' => NULL,
];
}
$links = [];
foreach ($data as $key => $value) {
if ($query->view->display_handler
->get_option('views_navigation_seo_first')) {
list($path, $options, $text) = _views_navigation_get_data($cid, $value['pos'], $value['back pos'], TRUE);
$links[$key] = $options;
$links[$key]['title'] = isset($text) ? $text : $value['default_title'];
$links[$key]['href'] = $path;
}
else {
$path_parts = [
'views_navigation',
$cid,
$value['pos'],
];
if (!empty($value['back pos'])) {
$path_parts[] = $value['back pos'];
}
$links[$key]['title'] = $value['default_title'];
$links[$key]['href'] = implode('/', $path_parts);
$links[$key]['attributes']['rel'] = 'nofollow';
}
$links[$key]['html'] = TRUE;
$links[$key]['attributes']['class'] = [
'views-navigation-' . $key,
];
}
// Allow modules to alter the navigation links.
drupal_alter('views_navigation_navigation_links', $links, $cid, $pos);
return [
'#theme' => 'links',
'#links' => $links,
];
}
/**
* Add the query parameters to append to the entity url.
*/
function _views_navigation_build_query($etid, $view, $query = []) {
if (isset($view->views_navigation_cid)) {
// Todo handle the case when the etid is in the result more than one time.
$etids = [];
$plugin = _views_navigation_get_query_plugin($view->query);
$entity_type = _views_navigation_get_entity_type($view->query);
$id_key = _views_navigation_get_id_key($entity_type);
foreach ($view->result as $result) {
switch ($plugin) {
case 'default':
$etids[] = $result->{$id_key};
break;
case 'search_api':
if (is_object($result->entity)) {
$etids[] = $result->entity->{$id_key};
}
else {
$etids[] = $result->entity;
}
break;
}
}
$index = array_search($etid, $etids);
// Index of first item on second page must be items_per_page, not 0.
$pager = $view->query->pager;
if ($pager
->use_pager()) {
$index += $pager
->get_items_per_page() * $pager
->get_current_page();
}
$query = array_merge($query, [
VIEWS_NAVIGATION_POSITION_PARAMETER => $index,
VIEWS_NAVIGATION_CACHE_ID_PARAMETER => $view->views_navigation_cid,
]);
}
// Allow modules to alter the query string.
drupal_alter('views_navigation_query_string', $query, $index, $view);
return $query;
}
/**
* Used when the view handler needs an already built url.
*
* This is the case when it will be passed to parse_url() as in
* views_handler_field::render_as_link().
*
* @param int $etid
* The entity ID.
* @param object $view
* The views object.
* @param array $options
* Link options.
*
* @return string
* The generated HTML for the link.
*/
function _views_navigation_build_url($etid, $view, array $options = [
'absolute' => TRUE,
]) {
$options['query'] = _views_navigation_build_query($etid, $view, isset($options['query']) ? $options['query'] : []);
$entity_type = _views_navigation_get_entity_type($view->query);
$entities = entity_load($entity_type, [
$etid,
]);
$uri = entity_uri($entity_type, reset($entities));
return url($uri['path'], array_merge($options, $uri['options']));
}
/**
* Function for check views navigation query is supported.
*
* @param object $query
* The views object.
*
* @return bool
* Whether the query type is supported.
*/
function _views_navigation_query_is_supported($query) {
$plugin = _views_navigation_get_query_plugin($query);
return isset($plugin);
}
/**
* Function for getting the query plugin type.
*
* @param object $query
* The query object.
*
* @return string
* Either search_api or default.
*/
function _views_navigation_get_query_plugin($query) {
// ATM we only handle the default and Search API views queries.
if (is_a($query, 'views_plugin_query_default')) {
return 'default';
}
if (is_a($query, 'SearchApiViewsQuery')) {
return 'search_api';
}
}
/**
* Based on EntityFieldHandlerHelper::render_entity_link().
*
* @param object $handler
* The field handler whose field is rendered.
* @param string $value
* The single value to render.
* @param object $values
* The values for the current row retrieved from the Views query, as an
* object.
*
* @return string
* The rendered value.
*/
function _views_navigation_render_entity_link($handler, $value, $values) {
$render = EntityFieldHandlerHelper::render_single_value($handler, $value, $values);
if (!$handler->options['link_to_entity']) {
return $render;
}
$entity = $handler
->get_value($values, 'entity object');
if (is_object($entity) && ($uri = entity_uri($handler->entity_type, $entity))) {
$id_key = _views_navigation_get_id_key($handler->entity_type);
if (isset($entity->{$id_key})) {
$uri['options']['query'] = _views_navigation_build_query($entity->{$id_key}, $handler->view);
}
return l($render, $uri['path'], [
'html' => TRUE,
] + $uri['options']);
}
return $render;
}
/**
* Helper function to get the data.
*
* Return an array containing $path and $options, as needed by url() and
* drupal_goto() functions. If $get_label is set, the third value will be the
* entity's label, else NULL.
*/
function _views_navigation_get_data($cid, $pos, $back_pos = NULL, $get_label = FALSE) {
if ($query = views_navigation_get_cached_query($cid)) {
if ($pos === 'back') {
if (isset($query->view->back_destination)) {
$options = $query->view->back_destination;
if ($pager = $query->pager_backup) {
if (!empty($back_pos)) {
$options['query']['page'] = floor($back_pos / $pager->options['items_per_page']);
}
}
return [
$options['path'],
$options,
NULL,
];
}
}
elseif ($result = views_navigation_get_result($cid)) {
// Manage array ends, cycling behavior.
$max_index = count($result) - 1;
$pos = $pos > $max_index ? 0 : ($pos < 0 ? $max_index : $pos);
if ($etid = $result[$pos]) {
$params = [
VIEWS_NAVIGATION_POSITION_PARAMETER => $pos,
VIEWS_NAVIGATION_CACHE_ID_PARAMETER => $cid,
];
$entity_type = _views_navigation_get_entity_type($query);
$entities = entity_load($entity_type, [
$etid,
]);
$entity = reset($entities);
$uri = entity_uri($entity_type, $entity);
$uri['options']['query'] = $params;
$return = [
$uri['path'],
$uri['options'],
];
if ($get_label) {
$return[] = entity_label($entity_type, $entity);
}
else {
$return[] = NULL;
}
return $return;
}
}
}
}
/**
* Function to get the id of the entity key.
*
* @param string $entity_type
* The entity type.
*
* @return int
* The id of the entity key.
*/
function _views_navigation_get_id_key($entity_type) {
$info = entity_get_info($entity_type);
return $info['entity keys']['id'];
}
/**
* Function to get the entity type.
*
* @param object $query
* The query object.
*
* @return string
* The entity type.
*/
function _views_navigation_get_entity_type($query) {
// TODO Have it work with search API.
$entity_type = $query->base_table;
if ($entity_type == 'taxonomy_term_data') {
$entity_type = 'taxonomy_term';
}
if (get_class($query) == 'SearchApiViewsQuery') {
$entity_type = $query
->getIndex()
->getEntityType();
}
return $entity_type;
}
/**
* Helper function to replace the links in HTML.
*
* @param string $html
* The HTML to replace links in.
* @param object $entity
* The entity to render.
* @param object $view
* The views object.
*/
function _views_navigation_replace_href_in_html(&$html, $entity, $view) {
$entity_type = _views_navigation_get_entity_type($view->query);
$uri = entity_uri($entity_type, $entity);
$alias = base_path() . drupal_get_path_alias($uri['path']);
$path = url($uri['path'], array(
'absolute' => TRUE,
));
$pattern = '@href="(' . $path . '|' . $alias . ')"@';
if (preg_match($pattern, $html)) {
$id_key = _views_navigation_get_id_key($entity_type);
$url = _views_navigation_build_url($entity->{$id_key}, $view, [
'absolute' => FALSE,
]);
$html = preg_replace($pattern, 'href="' . $url . '"', $html);
}
}
Functions
Name | Description |
---|---|
views_navigation_get_cached_query | Get a view query from cache. |
views_navigation_get_cached_result | Get a view result from cache. |
views_navigation_get_links | Build and render the previous/next links for the entity being viewed. |
views_navigation_get_query_cid | Get the unique cid corresponding to a view query. |
views_navigation_get_result | Get the result of a query, as an array of etids keyed by position. |
views_navigation_router | Redirect to the next or previous entity. |
views_navigation_store_query | Store a view query in cache. |
_views_navigation_build_query | Add the query parameters to append to the entity url. |
_views_navigation_build_url | Used when the view handler needs an already built url. |
_views_navigation_get_data | Helper function to get the data. |
_views_navigation_get_entity_type | Function to get the entity type. |
_views_navigation_get_id_key | Function to get the id of the entity key. |
_views_navigation_get_query_plugin | Function for getting the query plugin type. |
_views_navigation_query_is_supported | Function for check views navigation query is supported. |
_views_navigation_render_entity_link | Based on EntityFieldHandlerHelper::render_entity_link(). |
_views_navigation_replace_href_in_html | Helper function to replace the links in HTML. |