? workflow.scheduling.patch
Index: workflow.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/workflow/workflow.install,v
retrieving revision 1.6
diff -u -p -r1.6 workflow.install
--- workflow.install	11 Sep 2006 13:54:51 -0000	1.6
+++ workflow.install	18 Oct 2006 22:46:14 -0000
@@ -93,6 +93,19 @@ CREATE TABLE {workflow_node_history} (
 ) /*!40100 DEFAULT CHARACTER SET utf8 */;
 QUERY
       );
+      
+        $result[] = db_query(
+<<<QUERY
+CREATE TABLE {workflow_scheduled_transition} (
+  nid int(10) unsigned NOT NULL default '0',
+  old_sid int(10) unsigned NOT NULL default '0',
+  sid int(10) unsigned NOT NULL default '0',
+  scheduled int(10) unsigned NOT NULL default '0',
+  comment longtext,
+  KEY nid (nid,sid)
+) /*!40100 DEFAULT CHARACTER SET utf8 */;
+QUERY
+      );
       break;
 
     case 'pgsql':
@@ -214,6 +227,23 @@ QUERY
 CREATE INDEX {workflow_node_history}_nid_sid_idx ON {workflow_node_history}(nid,sid);
 QUERY
       );
+      
+      $result[] = db_query(
+<<<QUERY
+CREATE TABLE {workflow_scheduled_transition} (
+  nid integer NOT NULL default '0',
+  old_sid integer NOT NULL default '0',
+  sid integer NOT NULL default '0',
+  scheduled integer NOT NULL default '0',
+  comment text
+);
+QUERY
+      );
+      $result[] = db_query(
+<<<QUERY
+CREATE INDEX {workflow_scheduled_transition}_nid_sid_idx ON {workflow_scheduled_transition}(nid,sid);
+QUERY
+      );
       break;
   }            
 
@@ -339,3 +369,47 @@ function _workflow_fix_seq($old_name, $n
   $new_name = db_prefix_tables($new_name);
   return update_sql("UPDATE {sequences} SET name = '" . $new_name . "' WHERE name = '" . $old_name . "'");
 }
+
+// Add scheduling tables
+function workflow_update_5() {
+  $ret = array();
+  
+  switch ($GLOBALS['db_type']) {
+    case 'mysqli':
+    case 'mysql':
+        $ret[] = db_query(
+<<<QUERY
+CREATE TABLE {workflow_scheduled_transition} (
+  nid int(10) unsigned NOT NULL default '0',
+  old_sid int(10) unsigned NOT NULL default '0',
+  sid int(10) unsigned NOT NULL default '0',
+  scheduled int(10) unsigned NOT NULL default '0',
+  comment longtext,
+  KEY nid (nid,sid)
+) /*!40100 DEFAULT CHARACTER SET utf8 */;
+QUERY
+      );
+      break;
+    case 'pgsql':
+      $ret[] = db_query(
+<<<QUERY
+CREATE TABLE {workflow_scheduled_transition} (
+  nid integer NOT NULL default '0',
+  old_sid integer NOT NULL default '0',
+  sid integer NOT NULL default '0',
+  scheduled integer NOT NULL default '0',
+  comment text
+);
+QUERY
+      );
+      $ret [] = db_query(
+<<<QUERY
+CREATE INDEX {workflow_scheduled_transition}_nid_sid_idx ON {workflow_scheduled_transition}(nid,sid);
+QUERY
+      );
+
+     break;
+  }
+  
+  return $ret;
+}
Index: workflow.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/workflow/workflow.module,v
retrieving revision 1.51
diff -u -p -r1.51 workflow.module
--- workflow.module	27 Jul 2006 21:08:13 -0000	1.51
+++ workflow.module	18 Oct 2006 22:46:15 -0000
@@ -128,9 +128,20 @@ function workflow_tab_page($nid) {
     ksort($choices);
     $wid = workflow_get_workflow_for_type($node->type);
     $name = check_plain(workflow_get_name($wid));
-
+    // see if scheduling information is present
+    if ($node->_workflow_scheduled_timestamp && $node->_workflow_scheduled_sid) {
+      global $user;
+      if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
+        $timezone = $user->timezone;
+      }
+      else {
+        $timezone = variable_get('date_default_timezone', 0);
+      }
+      $current = $node->_workflow_scheduled_sid; // the default value should be the upcoming sid
+      $timestamp = $node->_workflow_scheduled_timestamp;
+    }
     $form = array();
-    workflow_node_form($form, t('Change %s state', array('%s' => $name)), $name, $current, $choices);
+    workflow_node_form($form, t('Change %s state', array('%s' => $name)), $name, $current, $choices, $timestamp);
     $form['node'] = array(
       '#type' => 'value',
       '#value' => $node,
@@ -163,15 +174,21 @@ function workflow_tab_page($nid) {
 
 function workflow_tab_submit($form_id, $form_values) {
   $node = $form_values['node'];
-  $sid = $form_values['workflow'];
-
-  // make sure new state is a valid choice
-  if (array_key_exists($sid, workflow_field_choices($node))) {
-    workflow_execute_transition($node, $sid, $form_values['workflow_comment']); // do transition
-  }
+  
+  // mockup a node so we don't need to repeat the code for processing this
+  $node->workflow = $form_values['workflow'];
+  $node->workflow_comment = $form_values['workflow_comment'];
+  $node->workflow_scheduled = $form_values['workflow_scheduled'];
+  $node->workflow_scheduled_date = $form_values['workflow_scheduled_date'];
+  $node->workflow_scheduled_hour = $form_values['workflow_scheduled_hour'];
+  
+  // call node save to make sure all saving properties run on this node
+  node_save($node);
+  
   return 'node/' . $node->nid;
 }
 
+
 /**
  * Implementation of hook_nodeapi().
  * Summary of nodeapi ops we can see (Drupal 4.7):
@@ -193,10 +210,18 @@ function workflow_nodeapi(&$node, $op, $
 
   case 'load':
     $node->_workflow = workflow_node_current_state($node);
-      break;
+    
+    // scheduling information
+    $res = db_query('SELECT * FROM {workflow_scheduled_transition} WHERE nid = %d', $node->nid);
+    if ($row = db_fetch_object($res)) {
+      $node->_workflow_scheduled_sid = $row->sid;
+      $node->_workflow_scheduled_timestamp = $row->scheduled;
+    }
+    break;
 
     case 'insert':
     case 'update':
+
       // stop if no workflow for this node type
       $wid = workflow_get_workflow_for_type($node->type);
       if (!$wid) {
@@ -213,7 +238,38 @@ function workflow_nodeapi(&$node, $op, $
 
       // make sure new state is a valid choice
       if (array_key_exists($sid, workflow_field_choices($node))) {
-        workflow_execute_transition($node, $sid, $node->workflow_comment); // do transition
+        // check to see if this is an immediate change or a scheduled change
+        if (!$node->workflow_scheduled) {
+          workflow_execute_transition($node, $sid, $node->workflow_comment); // do transition
+        }
+        else { // schedule the the time to change the state
+          $nid = $node->nid;
+          $comment = $node->workflow_comment;
+          $old_sid = workflow_node_current_state($node);
+          // TODO use the user's TZ
+          $scheduled = $node->workflow_scheduled_date['year'] . 
+                       $node->workflow_scheduled_date['month'] . 
+                       $node->workflow_scheduled_date['day'] .
+                       ' ' . $node->workflow_scheduled_hour . 'Z';
+
+          if ($scheduled = strtotime($scheduled)) {
+            // adjust for user and site timezone settings
+            global $user;
+            if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
+              $timezone = $user->timezone;
+            }
+            else {
+              $timezone = variable_get('date_default_timezone', 0);
+            }
+            $scheduled = $scheduled - $timezone;
+            
+            // clear previous entries and insert
+            db_query("DELETE FROM {workflow_scheduled_transition} WHERE nid = $nid");
+            db_query('INSERT INTO {workflow_scheduled_transition} VALUES (%d, %d, %d, %d,\'%s\')', $nid, $old_sid, $sid, $scheduled, $comment);
+            
+            drupal_set_message("$node->title is scheduled for state change on " . format_date($scheduled));
+          }
+        }
       }
       break;
 
@@ -231,7 +287,7 @@ function workflow_nodeapi(&$node, $op, $
  * @param string $current
  * @param array $choices
  */
-function workflow_node_form(&$form, $title, $name, $current, $choices) {
+function workflow_node_form(&$form, $title, $name, $current, $choices, $timestamp = NULL) {
   if (sizeof($choices) == 1) {
     $form['workflow'][$name] = array(
       '#type' => 'hidden',
@@ -239,6 +295,7 @@ function workflow_node_form(&$form, $tit
     );
   }
   else {
+    
     $form['workflow'][$name] = array(
       '#type' => 'radios',
       '#title' => $title,
@@ -247,6 +304,34 @@ function workflow_node_form(&$form, $tit
       '#parents' => array('workflow'),
       '#default_value' => $current
     );
+    
+    if (!workflow_is_system_state($current)) {
+      $scheduled = $timestamp ? 1 : 0;
+      $form['workflow']['workflow_scheduled'] = array (
+        '#type' => 'radios',
+        '#title' => t('Schedule'),
+        '#options' => array (
+          t('Immediately'),
+          t('Schedule for state change at:'),
+        ),
+        '#default_value' => $scheduled,
+      );
+      
+      $form['workflow']['workflow_scheduled_date'] = array (
+        '#type' => 'date',
+        '#default_value' => $scheduled ? array('day' => format_date($timestamp, 'custom', 'j'),
+                              'month' => format_date($timestamp, 'custom', 'n'),
+                              'year' => format_date($timestamp, 'custom', 'Y')) : NULL,
+      );
+  
+      $hours = date('H:i', $timestamp);
+      $form['workflow']['workflow_scheduled_hour'] = array (
+        '#type' => 'textfield',
+        '#description' => t('Please enter a time in 24 hour (eg. HH:MM) format. If no date is included, the default will be midnight on the specified date.'),
+        '#default_value' => $scheduled ? $hours : NULL,
+      );
+    }
+    
     $form['workflow']['workflow_comment'] = array(
       '#type' => 'textarea',
       '#title' => t('Comment'),
@@ -291,7 +376,20 @@ function workflow_form_alter($form_id, &
         '#collapsed' => FALSE,
       );
     }
-    workflow_node_form($form, $name, $name, $current, $choices);
+    
+    // see if scheduling information is present
+    if ($node->_workflow_scheduled_timestamp && $node->_workflow_scheduled_sid) {
+      global $user;
+      if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
+        $timezone = $user->timezone;
+      }
+      else {
+        $timezone = variable_get('date_default_timezone', 0);
+      }
+      $current = $node->_workflow_scheduled_sid; // the default value should be the upcoming sid
+      $timestamp = $node->_workflow_scheduled_timestamp;
+    }
+    workflow_node_form($form, $name, $name, $current, $choices, $timestamp);
   }
 }
 
@@ -345,6 +443,9 @@ function workflow_execute_transition($no
   // Notify modules that transition has occurred. Actions should take place
   // in response to this callback, not the previous one.
   module_invoke_all('workflow', 'transition post', $old_sid, $sid, $node);
+  
+  // clear any references in the scheduled listing
+  db_query('DELETE FROM {workflow_scheduled_transition} WHERE nid = %d', $node->nid);
 }
 
 /**
@@ -1595,4 +1696,33 @@ function workflow_handler_arg_sid($op, &
       $state = db_fetch_object(db_query("SELECT state FROM {workflow_states} WHERE sid=%d", $query));
       return $state->state;
   }
-}
\ No newline at end of file
+}
+
+/**
+ * Implementation of hook_cron
+ */
+function workflow_cron() {
+  $clear_cache = FALSE;
+  
+  //if the time now is greater than the time to publish a node, publish it
+  $nodes = db_query('SELECT * FROM {workflow_scheduled_transition} s WHERE s.scheduled > 0 AND s.scheduled < %d', time());
+  
+  while ($row = db_fetch_object($nodes)) {
+    $node = node_load($row->nid);
+    
+    // make sure transition is still valid
+    if ($node->_workflow == $row->old_sid) {
+      // do transistion 
+      workflow_execute_transition($node, $row->sid, $row->comment); 
+      
+      watchdog('content', t('%type: scheduled transition of %title.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title))), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid));
+      $clear_cache = TRUE;
+      
+    }
+  }
+  
+  if ($clear_cache) {
+    // clear the cache so an anonymous poster can see the node being published or unpublished
+    cache_clear_all();
+  }
+}
