Creating a Node View Which Bypasses Access Restrictions
First of all a disclaimer, part of the intention of this blog post is to see if anyone else has a better solution. This is something I came up with but I'm not entirely happy with the solution as it involves running the sql query twice. :(
Recently I was working on a site which had some subscription content, where basically only members of the site were allowed to view specific content types. However in order to encourage site visitors to register we wanted to display a teaser listing of the 5 most recent articles. We created a node view to display the listing, but the Views module, appropriately, only displays nodes which the user has access to. As the subscription content is only available to logged in users, this defeated the purpose of our teaser listing for site visitors.
To overcome the node access checks, I implemented hook_views_pre_render(). This is a Views hook which is invoked after the SQL query has been run, but before the view has been rendered. It checks that the view it is modifying is called 'myviewname' and that the display is 'block_1' - you'd need to change these as appropriate for your view. The following code essentially rebuilds the SQL query and reruns it a second time, but this time without the db_rewrite_sql() call that causes the node permissions to be checked.
<?php
/**
* Implements hook_views_pre_render().
*/
function mymodule_views_pre_render(&$view) {
// For myviewname, bypass node access checks.
if ($view->name == 'myviewname' && $view->current_display == 'block_1' && empty($view->result)) {
// This does the views token replacements.
$replacements = module_invoke_all('views_query_substitutions', $view);
$query = str_replace(array_keys($replacements), $replacements, $view->build_info['query']);
$args = $view->build_info['query_args'];
$offset = $view->pager['current_page'] * $view->pager['items_per_page'] + $view->pager['offset'];
// Runs the query a second time.
$result = db_query_range($query, $args, $offset, $view->pager['items_per_page']);
// Overwrites the default empty result set with the results from our 2nd sql query.
$view->result = array();
while ($item = db_fetch_object($result)) {
$view->result[] = $item;
}
}
}
?>While the above solution works, I would be interested in learning if there is a better way that avoids running the query a second time and without creating the listing in a custom module that is.


Do not use node-access for this. Instead, use hook_node_vow to restrict actual node views and then you can add a ess control to all views. However you have to be careful about drupal's default frntpage which is always available at 'node' and could contain restricted nodes if using the front page flag. This will likely improve site performance as drupal will not override your queries.
Rather, I mean hook_node_view() -- wrote that on the iphone. It autocorrected me. :/
Is
hook_node_view()a Views hook? I'm having trouble finding where it is documented. Or did you meanhook_view()? I can see how that would work for node row style listings. Any suggestions on how to do the same for field row style ones?hook_nodeapi('view') is what you need for D5 and D6. Most of the values for $op got separated into different hooks for D7.
Hi Stella and merlinofchaos. I'm implementing something along the lines of what merlinofchaos suggests and doing access checks on node view. For rows I'm implementing an access check on the theme layer before the row is rendered. It's kind of nasty but works. I'll dig up some code and paste it in so you can see what I mean.
Hi Stella, I've written it up here: http://tancredi.co.uk/node/37
If anything doesn't make sense (quite possible) let me know and I'll try and explain.
hook_views_query_alter()?
I don't think so, as it's
db_rewrite_sql()that is causing the node access check IIRC, not the views query itself.I think I disagree with @merlinofchaos here. Which could very well mean that I'm wrong. However, by blowing away node access you cause a world of pain where you need to always be hiding/showing things manually and always need to be cognisant of how someone could bypass your tricks. If it were me, I would just write up a custom chunk of SQL for this teaser block, and pass it through db_query() without first using db_rewrite_sql(). You then have node access in place for everything, but this short list is the only thing that bypasses it.
This is nothing more than a suggestion. But it sounds to me like you want a view filter or view option called "Ignore Node Permissions". Is this not possible to do using views? I dont know how to make a view filter, but thats probably where I would start. This way your view could show any field u like (likely would be just title and maybe a teaser in your case).
I having been looking for a solution to this problem and found the module:
Views Ignore Permissions by crystaldawn at http://drupal.org/project/views_ignore_node_permissions going to give it a try.
I'm facing this right now myself. My client wants there to be a block on the front page with links to both open and restricted content, not even the teaser content. They want users to be able to click on the links to restricted content and get a login/register page. I can't seem to do this out of the box.
hook_views_query_alter()?
You solution works great however I have lost my pager at the bottom.... is that the expected behavior?
Thanks, Scott
Hi Stella,
I found your post when searching for a way to do what you described... here is my final solution that I landed on after some time...
(The following is not meant to be a working example, but rather a recipe to
achieve the goal of temporarily bypassing the normal node access check for the loading of a
view.)
Here is where I'm going to load my view and I want the access check to be bypassed:
<?php
global $user;
//temporarily give the global $user realm access to open_access
$user->open_access = 1;
$view = views_get_view('some_view_id');
$view->set_display('some_display');
//you would then do something with the output, like in a preprocess function...
$output = views_embed_view($view_name, $display_id);
//now immeditely after loading the view, remove the open access; we only had it
//as long as the view needed to be loaded.
unset($user->open_access);
?>
The following hook will create a realm where the view access is true for anyone
with the GID = 1
<?php/**
* Implements hook_node_access_records
*/
function hook_node_access_records($node) {
$grants = array();
$grants[] = array(
'realm' => 'open_access',
'gid' => 1,
'grant_view' => TRUE,
'grant_update' => FALSE,
'grant_delete' => FALSE ,
'priority' => $priority,
);
return $grants;
}
?>
This will grant the value of $user->open_access as the GID, so if you set it to
1 like in the example, the user will have view access to the node as long as
$user->open_access is 1
<?php/**
* Implementation of hook_node_grants
*/
function hook_node_grants($account, $op) {
$grants = array();
if (!empty($account->open_access)) {
$grants['open_access'] = array($account->open_access);
}
return $grants;
}
?>
Hi Stella, Great solution, but the pager disappears. I'll find a solution on how to get it back.
I was using a view programmatically to determine node access, and ran into problems with an infinite loop.
This is a bit of a hack, but it's very code light. I wonder if anyone thinks there are any issues with it. All you do is temporarily switch the user to user 1 (which bypasses any node access controls) and then switch back afterwards:
<?php$view = views_get_view('MY_VIEW');
$user = $GLOBALS['user'];
/Temporarily become root user
$GLOBALS['user'] = user_load(1);
$view->preview();
$GLOBALS['user'] = $user;
//Do something with the result
?>
Obviously you need to be careful if you're making any decisions in the view based on the current user, but the way around that is just to make sure you specify the user as an argument.
The implementation of hook_views_pre_render() is a good idea I must say...
Now this is really easy - in the view ui, under 'Other' edit the query settings, and check the box marked 'Disable SQL rewriting'.