1

I need to restrict access to a node based on the distance of the current user to a location attached to the node (with Location module). The distance to decide, if access is allowed is node based and stored in a field of the node. Currently I achive this feature with hook_node_access(). I get the current location of the user through the Smart IP module and are able to calculate the distance by passing the coordinates of the user and the node to location_distance_between(), which is provided by the Location module.

Because hook_node_access() isn't considered by Views I think about accomplishing this feature with hook_node_access_records() and hook_node_grants(). Since the distance is dynamically, I have no idea how to achive this. In my understanding hook_node_grants() is called before the database query, so I have no chance to get the distance right?

Any ideas?

Thanks, Daniel

2 Answers 2

0

Option 1

I use the Geofield module, which provides a similar function to the Location module. It provides the concept of a proximity filter. This allows you to implement views such as "show all nodes within 50km of specified point". I don't know whether the Location module has a similar function, or whether you might be willing to change modules. The Smart IP module claims some Views integration but D6 only, so you might need a bit of code to integrate with views.

This approach would not use access control at all - so that if the user specifically navigated by URL to a far away node, they would still see it.

Option 2

If you definitely want access control, then I think you will have to give up exact distances. You could divide your target area into regions, maybe around 50-500 of them - most simply using a simple grid. In your hook_node_access_records, you decide which regions are close enough to the node (calculate distance to the region centre). Finally in hook_node_grants, you need code to take the output of the Smart IP module and calculate which region it is in, and set the appropriate grant.

If you need some more help with the details of either approach, feel free to edit your question to say which approach you prefer, what you have working and where you are stuck.

7
  • Hi thanks for your answer. Location module also provides a proximity filter, but I can just enter a static distance. I think it is the same in Geofield. Regarding option 2: I also thought about a similar approach, but unfortunately it is not very flexible and scalable and it needs a lot of work... in the moment both options don't fit the requirements. But thanks for your ideas!
    – Daniel
    Commented Apr 6, 2015 at 20:26
  • The proximity filter relies on an origin point. In order to pick up the output of the Smart IP module as the origin, you would need a contextual filter with "Provide default value". If the Smart IP module doesn't provide this, it's not too hard to write one: you need a simple Views plug-in.
    – AdamS
    Commented Apr 6, 2015 at 20:29
  • Yes, but imagine the following example: let's say I have three nodes. And I want, that the first one should be listed for all users within a proximity of 20km, the second one within 30km and the third one should be listed for all users within 60km. These distances are stored in a field of the node. If a user has a distance of 25km to all three nodes, he should just see the second and third one. And Views is just able to know these values after the query and cannot change the result afterwards right?
    – Daniel
    Commented Apr 7, 2015 at 17:01
  • Ah sorry I hadn't appreciated that the distance was different for each node. Yes that's tricky. It might be possible to add a tag to the view (see under "Query Settings"), then implement hook_query_TAG_alter, and somehow tweak the SQL to compare against the field. Good luck!
    – AdamS
    Commented Apr 7, 2015 at 20:24
  • Thanks for this idea. But that would means, that I should run an additional query to get the distances for all nodes, calculate the distance to user and then pass an array with all relevant nids to the original query. So that would be some kind of double load right? I think the best solution is to use hook_views_pre_render to clean up the results.
    – Daniel
    Commented Apr 8, 2015 at 17:19
0

Ok I got it with a little work around.

I use hook_views_query_alter() to get all the distances of the relevant nodes. So I build an own query like this:

function mymodule_views_query_alter(&$view, &$query) {
  if($view->name === 'my_view' && $view->current_display === 'my_display') {
    // build the query to get the distances
    $location_query = db_select($query->table_queue['node']['table'], $query->table_queue['node']['alias']);
    $location_query->leftJoin('location_instance', 'li', $query->table_queue['node']['alias'] . '.vid = li.vid');
    $location_query->leftJoin('location', 'l', 'li.lid = l.lid');
    $location_query->leftJoin('field_data_field_distance', 'd', $query->table_queue['node']['alias'] . '.nid = d.entity_id');
    $location_query->fields('l', array('latitude', 'longitude'))
    ->fields('d', array('field_distance_value'))
    ->fields($query->table_queue['node']['alias'], array('nid'));

    // consider all filters which were set in the views ui
    foreach ($query->where[1]['conditions'] as $condition) {
      $location_query->condition($condition['field'], $condition['value'], $condition['operator']);

    }

    // execute the query
    $result = $location_query->execute()->fetchAll();
    // go through all results to check the distance
    foreach($result as $node) {
      // do the calculation of the distance
      // ...
      // check if distance is close enough
      if($distance <= $node->field_distance_value) {
        $nids[] = $node->nid;
      }
    }

    // remove all conditions from the views query, because we already used them
    unset($query->where[1]['conditions']);

    // let views just load all the relevant NIDs
    $query->where[1]['conditions'][0] = array(
      'field' => $query->table_queue['node']['alias'] . '.nid',
      'value' => $nids,
      'operator' => 'in',
    );
  }
}

Well, in this way I anticipate the Views query and change it, so that Views just needs to load the NIDs which were required.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.