Hello all together!

I am using the module computed fields and with it I calculate the age and the next birthday in profile nodes on base of a date field birthday date.
Unfortunatly the values in the database are just recalculated when the node is updated/saved. I have to save them in the database because I need the computed fields in Views.
In the post http://drupal.org/node/313498 the idea came up to create a php script, which saves the relevant nodes, and to trigger it once a day by cron (which is often enough in this situation).

Do you have some ideas how I can create such a script?
I especially don't understand some things:

  • How can Drupal functions in this standalone php script work, when it is called as a stand alonescript outside the Drupal framework?
  • How can this script work, when just special users with a special role have the right to update this profile nodes? When this script is called e.g. with wget/curl I guess the active user would be the anonymous user "guest"?

So this not the question how to configure cron. But maybe you can give me some tipps or links how I get started with it ...

Thanks for your help in advance,

Chris

Comments

jasonnovember’s picture

How about creating a small module that implements hook_cron()??
That way whenever Drupal's cron is executed, you can execute the php script that you want inside hook_cron().

You can select the node ids from the database and then fetch those nodes using node_load($nid);
Then for each node you can execute your script for recalculating values and then do a node_save(&$node);.

Hope this helps.

finke77’s picture

Hello!

hook_cron sounds interesting, but as far I understood this will be executed every time cron.php is triggered. If cron would be triggered every 5 minutes, maybe I have an unnecessary high workload with processing the nodes. A recalculation is just needed in this case once a day, because the calculated fields are based on a date (without time).

Is there a possibility to schedule the execution of a hook_cron similar like in Linux cron (execute it just once a day at midnight)?

Is there also a different possibility with a standalone script? Or am I on the wrong track?

Thanks,

Chris

johnhanley’s picture

To fire a cron task once a day create a module containing hook_cron() and wrap the code in a condition. Use a system variable to determine whether or not the code has executed for each particular day. For example:

function mymodule_hook() {
  // today's date as of midnight, exp. 04/16/09 12:00:00 am
  define('TODAY', mktime(0, 0, 0, date('n'), date('j'), date('Y'), 0));

  if (variable_get('daily_cron', TODAY) != TODAY) {
    variable_set('daily_cron', TODAY);
    // do whatever else you need
  }
}
finke77’s picture

Hello!

The idea sounds good!

variable_set('daily_cron', TODAY);
Does this with other words mean the date of the last run is stored in the variable TODAY?

What is the livetime of a variable? Is it also available after a clear cache?
By the way: where is this variable available? Everywhere in Drupal once it is registered?

Would it be better to save the last run date in the database?

What is the user ID when cron.php is triggered by Linux cron/wget/curl etc.? Is it UID = 0 (superuser)?

Sorry if I ask stupid questions, but I am still a beginner with developing modules ...

Thanks for your help in advance!

Chris

jasonnovember’s picture

Well it means that today's date is stored in the variable 'daily_cron'
When you call 'variable_set', it gets stored in the variables table in the drupal database.
Next time you call 'variable_get' you will get the previously stored value.
You can use 'variable_get' to check if the date stored is todays date or not i.e. the condition for the code to execute.

I just started developing in drupal :D its ok to ask questions. Never stupid :D

Regards,
Jason

johnhanley’s picture

The example code defined a constant called TODAY.

Drupal persistent variables are stored in the database and do not expire.

Cron tasks are executed by user account 'anonymous' (UID: 0).

finke77’s picture

Hello!

Drupal persistent variables are stored in the database and do not expire.

Perfect!

Cron tasks are executed by user account 'anonymous' (UID: 0).

I guess this is a problem. In this case I have a content_profile node (called profile). Just the creator (profile user) and users with the role "administrator" are allowed to update and save this nodes in the normal frontend.
If the cron script is executed and UID:0 anonymous is used, I guess the script is not allowed to update ( node_save() ) the nodes, is it? Or am I wrong and cron.php doesn't care about the Drupal rights?
If this is a problem, is there a way to start (this part of) the cron script with a different UID (1 => admin)?

Thanks all for your help!

Chris

johnhanley’s picture

All access rights, form validation, etc. SHOULD be enforced for cron tasks (assuming they are not circumvented either inadvertently or deliberately.)

To switch users you can do something like this:


function mymodule_cron() {
global $user;
// preserve current user object
$current_user = $user;

// switch user from anonymous to another uid
$user = user_load(array('uid' => 2));

// do whatever else you need

// restore current user object before exiting
$user = $current_user;
}

Note this technique should be used sparingly and with care as it raises some potential security concerns.

finke77’s picture

Bingo!

I think this is the last puzzle to the solution!

To summarize it:

function mymodule_cron() {
  // today's date as of midnight, exp. 04/16/09 12:00:00 am
  define('TODAY', mktime(0, 0, 0, date('n'), date('j'), date('Y'), 0));

  if (variable_get('daily_cron', TODAY) != TODAY) {
    variable_set('daily_cron', TODAY);
    
    // preserve current user object
$current_user = $user;

// switch user from anonymous to another uid
$user = user_load(array('uid' => 2));

// do whatever else you need
// ==> script which selects all relevant nodes and calls node_save for each node (see also: http://drupal.org/node/313498#comment-1141505)


$res = db_query("SELECT n.nid FROM {node} n WHERE n.type = 'profile'");
$i=0;
while ($n = db_fetch_object($res)) {
  node_save(&$n);
  $i++;
}
$updateMessage = "updated ".$i." profile nodes";
print($updateMessage);
// maybe some other logging => watchdog?


// restore current user object before exiting
$user = $current_user;
  }
}

Maybe the last question from (hopefully, if this works ;-):
How can I log the $updateMessage into the admin/reports/dblog page?

THANK YOU ALL FOR YOUR HELP!!!!

Chris

johnhanley’s picture

Yeah, use watchdog() to make log entries.

You have to make sure to define the global $user object first before altering its value.

Note the user_load() example uses uid: 2. You'll want to use a relevant uid. For instance the uid for each corresponding node. You therefore might want to move the user_load() inside your while loop. For example:

while ($n = db_fetch_object($res)) {
  $user = user_load(array('uid' => $n->uid));
  node_save(&$n);
  $i++;
}

This might in fact make changing the user object unnecessary, which would be the preferred method.

finke77’s picture

Hello,

the suggested way with the cron hook doesn't seem to work. I created a module which I called computed_field_cron. I enabled it, flushed the caches and started the cron.php manually.
The cron run successfully but the cron hook of my module doesn't seemed to be called :-(
Neither the variable daily_cron, nor the nodes are recalculated (new update date), nor an entry in the watchdog is visible.

Does anyone now what the reason for this is?

See also below my current computed_field_cron.module file.

Thanks for your help!

Chris



/**
* Valid permissions for this module
* @return array An array of valid permissions for the onthisdate module
*/
function computed_field_cron_perm() {
	return array('access computed_field_cron');
} // function computed_field_cron_perm()


function computed_field_cron_cron() {
	global $user;

  // today's date as of midnight, exp. 04/16/09 12:00:00 am
  define('TODAY', mktime(0, 0, 0, date('n'), date('j'), date('Y'), 0));

  if (variable_get('daily_cron', TODAY) != TODAY) {
    variable_set('daily_cron', TODAY);

    // preserve current user object
$current_user = $user;

// switch user from anonymous to another uid
$user = user_load(array('uid' => 1));

// do whatever else you need
// ==> script which selects all relevant nodes and calls node_save for each node
$res = db_query("SELECT n.nid FROM {node} n WHERE n.type = 'profile'");
$i=0;
while ($n = db_fetch_object($res)) {
  node_save(&$n);
  $i++;
}
$updateMessage = "updated ".$i." profile nodes";
print($updateMessage);
// maybe some other logging => watchdog?
$log = array (
	$log['user']->uid = 1,
    $log['type'] = "cron",
    $log['message'] = $updateMessage,
    serialize($log['variables']),
    $log['severity'] = 6,
    $log['timestamp'] = now()
);

dblog_watchdog($log = array());


// restore current user object before exiting
$user = $current_user;
  }
}

jasonnovember’s picture

Quick question.
I installed the development module on drupal.
I was playing around with the user id in the hook_cron()
I could access the current user's id i.e. whenever cron executed it displayed the current user's id and it was not 0.

So is it necessary to do the user switch??