I was interested to see what kind of non-js solution I coul come up with that still works with caching so...
The main caching we have to deal with is:
- Views results cache.
- Views output cache.
- Drupal page cache.
The main hooks we have available are:
- hook_views_pre_execute() is too early to know about results so it's out.
- hook_views_post_execute() happens after we have results and it runs on both cached and non-cached results so it is a viable choice.
- hook_views_pre_render() doesn't run if we are displaying cached output so it's out.
- hook_views_post_render() happens after we have results and runs on both cached and non-cached output so it is a viable choice.
So you could use hook_views_post_execute() or hook_views_post_render() to handle your main logic. It's really up to you which to use. I would probably use hook_views_post_execute() since I'm dealing more with results than display but if you wanted to run after other hook_views_post_render() implementations or something the post_render would be better.
For example if you had custom code that modifies the view output so that not all results are actually shown to the user then you might want to put your logic in post render so that you can account for that.
For this example I will use post_execute.
/**
* Implements hook_views_post_execute().
*/
function MYMODULE_views_post_execute(&$view) {
if ($view->name == 'VIEWNAME' && $view->current_display == 'DISPLAYNAME') {
// Since we're dealing with a single view & display we don't need to work
// out the entity type but there are ways you could make this more generic.
// Loop through the results and log the views.
if ($view->result) {
foreach ($view->result as $row) {
// The view I'm using is a node view using teaser as display.
// You may need to adjust how entity ID is obtained in other cases.
MYMODULE_log_view($view, 'node', $row->nid);
}
}
}
}
Then have something like this to write to the table (you can work out the schema I was playing with from this):
/**
* Log an user's viewing of an entity in a view's results.
*/
function MYMODULE_log_view($view, $entity_type, $entity_id) {
$count = db_query("SELECT *
FROM {TABLENAME}
WHERE view_name = :view_name
AND view_display = :view_display
AND entity_type = :entity_type
AND entity_id = :entity_id",
array(
':view_name' => $view->name,
':view_display' => $view->current_display,
':entity_type' => $entity_type,
':entity_id' => $entity_id,
))->fetchObject();
// If we didn't get a count create a new one, otherwise increament.
$primary_keys = array();
if (!$count) {
$count = new stdClass();
$count->view_name = $view->name;
$count->view_display = $view->current_display;
$count->entity_type = $entity_type;
$count->entity_id = $entity_id;
$count->view_count = 1;
}
else {
$count->view_count++;
$primary_keys[] = 'id';
}
// Write the data.
drupal_write_record('TABLENAME', $count, $primary_keys);
// @todo: Save to a cache table based on path so we can load it again when
// drupal serves a cached page.
return $count;
}
That successfully logs how many times each node is displayed.
That works with views result & output caching on.
Then you have to deal with the views page cache.
As you can see I have left a todo in the above code for that to store a record of the nodes displayed on this page.
Along with that you could need some code to read our cache and log the views for cached pages. Something like this:
/**
* Implements hook_boot().
*/
function MYMODULE_boot() {
// Check to see if we are loading from the cache.
if (drupal_page_get_cache(TRUE)) {
// @todo: Get our cache entries for this page and log the views.
}
}
I'm pretty confident that would all work out to handle the drupal page cache.
After that you have external caches like varnish.
Then you're in trouble and you'll need to go to an ajax request to log the views, which could basically pass in the page url and save like the hook_boot() example above.
I have no time now but I will flesh this out some more a little later and finish the @todos.
I may also make a sandbox module and see how generic I can get it, for supporting other entities, integrating with views UI, etc.
Anyway, it works :)
Then you could add views support for the custom table and build reports etc.
Also notable is that this only stores a total count per entity. If you wanted to go really hard core you could adapt it to save each view separately with a timestamp and then you could do reporting based on date ranges too.
[page-impressions] / ([nodes-in-view] / [nodes-in-pool])
)