1

I need to execute a few lines of PHP code before a page is rendered. Basically what I need to do is to store in a database the user's original http_referer together with an anonymous visitID, and save this visitID in a session. So the code i need to execute is something like this:

if(!isset($_SESSION['lock'])) { // we only want this to run when the user enters the site, not on subsequent pages
   $_SESSION['lock'] = true;
   save_http_referer_in_database();
   $_SESSION['visitID'] = mysql_insert_id(); // gets the id of the last inserted row
}

I've found a few solutions but not very convincing. UPDATE 16th Oct: I'm making a short summary here as I've edited this post many many times.

  • First, I tried putting the required code at the top of html.tpl.php... it works, but stops running if i enable caching. Not good. I don't want to disable caching.

  • I tried putting the code in the settings.php and index.php, but drupal's session aren't initialized yet in there.

  • As suggested here, I've tried creating a custom module implementing hook_boot, but this solution has weird problems when caching is enabled. What happens is that the save_http_referer_in_database() function correctly runs the first time, but when the user loads another page, it runs again! All the subsequent page loads don't trigger it again though. This post has some more informations about it. This bug report on Drupal.org shows some other bad things that happen with sessions and caching and hook_boot()

  • SOLUTION: Use cookies instead of sessions.

Regarding @znerol's answer:

  • I wouldn't have DB problems anyway because i save visitID and referer in another database, so i manually db_connect() to my database and manually run mysql_query(). Hopefully it's not a bad thing.

  • How could exposing the visitID to the user create problems? The visitID is a completely anonymous ID, generated by MySql as the Auto Increment column of the table containing the http_referers. I can't imagine how someone could use this ID to do something 'bad'.

  • The reason why I want to do it inside my application, is that I need the visitID to "connect" certain actions on my site to the http_referer (through the visit_id foreign key). So basically i have a structure like this:

VISITS (visitID, http_referer) EVENTS (eventID, event_description, fk_visitID)

When an event is triggered, I add it to the database by fetching the fk_visitID from the cookie (or session)

I couldn't find a way to accomplish that through AWSTATS (which is installed) or Google Analytics or other similar software.

I didn't know Piwik but thanks to the API it might be fine for what I need, right?

6
  • 1
    make sure if you really need to check the session variable before every page load!
    – arpitr
    Oct 14, 2013 at 22:02
  • you're right, i've just edited the original question with additional details.
    – Sifro
    Oct 15, 2013 at 7:02
  • 1
    I think using a separate cookie (i.e do not depend on $_SESSION) would do the trick. Cookie data will be available in any stage in $_COOKIE and just like $_SESSION, it's browser-session based.
    – AKS
    Oct 16, 2013 at 7:50
  • thanks, using cookies is indeed a simple solution that should work fine. I'm just wondering: might this solution be less reliable, as some people disable cookies while almost no one disable sessions?
    – Sifro
    Oct 16, 2013 at 8:17
  • 1
    There is no $_SESSION without a session-cookie. Drupal sets the ini-value session.use_only_cookies in drupal_environment_initialize.
    – znerol
    Oct 16, 2013 at 19:19

5 Answers 5

2

There are a couple of answers for this question because there are multiple problems involved.

1. $_SESSION and page cache cannot coexist

You probably noted the call to drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); in index.php. This function essentially calls the following functions in exactly that order:

// 1. Run settings.php
_drupal_bootstrap_configuration();
// 2. Call `hook_boot` and attempt to deliver page from cache. Exit if successful.
_drupal_bootstrap_page_cache();
// 3. Connect to the database
_drupal_bootstrap_database();
// 4. Load the variables table (think initialize `variable_get`)
_drupal_bootstrap_variables();
// 5. Load the session and populate `$_SESSION`
drupal_session_initialize();
// 6. Invoke `hook_boot` (For pages not served by the cache)
_drupal_bootstrap_page_header();
// 7. Prepare for multilingual sites
drupal_language_initialize();
// 8. Load all modules and build the page
_drupal_bootstrap_full();

Note that _drupal_bootstrap_page_cache is called before the session is initialized. Therefore when delivering a page from the cache, $_SESSION is simply not yet defined when hook_boot is called.

Even more important is that _drupal_bootstrap_page_cache does not attempt to deliver a page from the cache when it detects a session-cookie on the request.

Page caching is mutually exclusive to having a session open in drupal core.

2. Use your own cookie for the visit-ID

As suggested by others, you may set your own cookie to track visits and http_referer. You can use hook_boot in this case and implement something like this:

if (!isset($_COOKIE['visitid'])) {
    $params = session_get_cookie_params();
    $visitid = save_http_referer_in_database_and_return_visit_id();
    $expire = 0; /* remove when browser window closes */
    setcookie('visitid', $visitid, $expire, $params['path'], $params['domain']);
}

Recalling the bootstrapping order, one might argue that the database may not be ready when hook_boot is called and therefore save_http_referer_in_database_and_return_visit_id would fail. However unless the setting page_cache_without_database is set to true in settings.php, _drupal_bootstrap_page_cache will make sure that the database connection exists before hook_boot is called.

The solution presented has the problem that the visit-id will be exposed to the client. That said, it should better be something which is not predictable (i.e. no serial number but some randomly generated unique id for example).

3. (Offtopic): Use third party web analytic software

Even the good old AWStats is capable of presenting metrics about HTTP referrers with the additional benefit, that no database inserts are necessary for delivering cached pages. Another good option is Piwik which also exposes an API where you could fetch exactly the metrics you need.

2
  • thanks a loto, this is a great answer and you helped me to clarify so many doubts! So the cookie is the way to go. I'm editing my original post for a few final questions, I'd love to hear your take on those.
    – Sifro
    Oct 16, 2013 at 11:57
  • 1
    @Sifro: Regarding visitid, it is a bad idea to expose the auto increment. The most banal reason is that you expose your traffic stats to anyone. Your customers/partners/opponents just need to look at the cookie and they now how many visitors you have. Regarding the events, you may want to take a look at piwik goals.
    – znerol
    Oct 16, 2013 at 19:14
5

hook_boot runs on every page, even on cached pages. Implement it in a custom module.

Generally it's better to not require code to run on cached pages, but that's the best option you have, as opposed to hacking index.php. The primary problem I see with depending on hook_boot, is that you open your self up to a very simple denial of service attack. Anyone with curl and a shell can potentially bring your site down with much less resources when caching isn't used well.

One option to consider it relying on javascript to make a call the site via ajax. That way you can save yourself quite a bit of load on the server.

1
  • original post updated... i'm seriously starting to think that i can't have both the custom code AND caching.
    – Sifro
    Oct 15, 2013 at 22:12
1

I guess you can implement hook_boot() in your custom module.

Perform setup tasks for all page requests.

This hook is run at the beginning of the page request. It is typically used to set up global parameters that are needed later in the request. Perform setup tasks for all page requests.

This hook is run at the beginning of the page request. It is typically used to set up global parameters that are needed later in the request.

0

I'd use hook_init in a custom module.

https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_init/7

2
  • 4
    hook_init() will not be invoked on cached pages.
    – arpitr
    Oct 14, 2013 at 21:57
  • very true! hopefully he'll have it saved by then, but I'd also suggest using hook_boot like everyone else has said. Oct 14, 2013 at 22:26
0

You can use drupal Rules module.

Create a new rule:

  1. add a title
  2. React on event "Drupal is initializing"
  3. add your condition
  4. add your action in php format.

Here is an export of that rule.


{ "rules_run_session_query" : {
    "LABEL" : "Run session query",
    "PLUGIN" : "reaction rule",
    "REQUIRES" : [ "php", "rules" ],
    "ON" : { "init" : [] },
    "IF" : [
      { "php_eval" : { "code" : "if ( isset($some_value) ) {\r\nreturn TURE;\r\n} \r\nelse {\r\nreturn FALSE;\r\n}" } }
    ],
    "DO" : [ { "php_eval" : { "code" : "\/\/ Do your query here." } } ]
  }
}
1
  • 1
    "Drupal is init ializing", is run during hook init. Which doesn't solve the problem.
    – Letharion
    Oct 15, 2013 at 11:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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