Creating a Node View Which Bypasses Access Restrictions

Creating a Node View Which Bypasses Access Restrictions

Posted by stella on Sun, 2010-05-23 23:02 in

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.

My usual solution

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.

Posted by Merlinofchaos (not verified) on Mon, 2010-05-24 04:05
Err

Rather, I mean hook_node_view() -- wrote that on the iphone. It autocorrected me. :/

Posted by merlinofchaos (not verified) on Mon, 2010-05-24 04:38
Is hook_node_view() a Views

Is hook_node_view() a Views hook? I'm having trouble finding where it is documented. Or did you mean hook_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?

Posted by stella on Mon, 2010-05-24 10:54
That's a D7 hook

hook_nodeapi('view') is what you need for D5 and D6. Most of the values for $op got separated into different hooks for D7.

Posted by Island Usurper (not verified) on Mon, 2010-05-24 13:57
Hi Stella and merlinofchaos.

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.

Posted by Tanc (not verified) on Mon, 2010-05-24 11:07
Write up

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.

Posted by Tanc (not verified) on Mon, 2010-05-24 14:48
hook_views_query_alter()?

hook_views_query_alter()?

Posted by Anonymous (not verified) on Mon, 2010-05-24 21:31
don't think so

I don't think so, as it's db_rewrite_sql() that is causing the node access check IIRC, not the views query itself.

Posted by stella on Mon, 2010-05-24 21:34
I think I disagree with

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.

Posted by dalin (not verified) on Tue, 2010-05-25 01:25
This is nothing more than a

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).

Posted by Anonymous (not verified) on Thu, 2010-07-22 18:22
Ignore Node Permissions with Views Filter

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.

Posted by Miss Nifty (not verified) on Tue, 2011-04-12 17:19
I'm facing this right now

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.

Posted by Stacey (not verified) on Sat, 2010-07-24 00:27
hook_views_query_alter()?

hook_views_query_alter()?

Posted by Rehber67 (not verified) on Sat, 2011-02-12 21:56
Pager...

You solution works great however I have lost my pager at the bottom.... is that the expected behavior?

Thanks, Scott

Posted by Scott Thomas (not verified) on Mon, 2011-07-18 18:48
A node access based approach (code heavy)

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;
}
?>

Posted by aklump (not verified) on Wed, 2011-07-20 02:22
Pager

Hi Stella, Great solution, but the pager disappears. I'll find a solution on how to get it back.

Posted by LEternity (not verified) on Tue, 2011-08-02 17:19
Switching the user to user 1

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.

Posted by David Seddon (not verified) on Fri, 2011-09-02 10:53
The implementation of

The implementation of hook_views_pre_render() is a good idea I must say...

Posted by sports shops (not verified) on Sun, 2011-09-25 15:13
Disabling node access via the UI

Now this is really easy - in the view ui, under 'Other' edit the query settings, and check the box marked 'Disable SQL rewriting'.

Posted by David Seddon (not verified) on Tue, 2012-03-27 09:35
+1 !!!!!

Thank you so much David for posting this simple solution! I have been trying for hours to get my Ubercart catalog to display nodes that a user hasn't yet purchased roles for. Now I have two views: a normal one that shows what content the user already owns, and another that shows what is available for purchase. I can't believe this bit of info was so hard to find, but that's Drupal for ya.

Posted by Paul (not verified) on Thu, 2012-09-06 03:59