Index: modules/worklog/worklog.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/worklog/worklog.module,v
retrieving revision 1.4
diff -U65535 -r1.4 worklog.module
--- modules/worklog/worklog.module	30 Jun 2006 17:41:55 -0000	1.4
+++ modules/worklog/worklog.module	20 Jan 2007 19:40:59 -0000
@@ -1,1046 +1,1025 @@
 <?php
 
 /**
  * @file
  * Keep track of time spent on tasks.
  */
 
 define('WORKLOG_ROUND_MINUTES', 15); // 6 minutes = .1 hour
 
 // hook_worklog operations
 define('WORKLOG_OP_INVOICE_HEADER', 'worklog_invoice_header');
 define('WORKLOG_OP_INVOICE_FOOTER', 'worklog_invoice_footer');
 
 /**
  * Implementation of hook_help
  */
 function worklog_help($section) {
   switch ($section) {
-  case 'admin/modules#description':
-	return t('Keep track of time spent working.');
-  case 'node/add#worklog':
-	return t('Log time spent working on something.');
-  case 'node/add#worklog-invoice':
-	return t('A collection of worklog entries.');
+  
   }
   
 }
 
 /**
  * Implementation of hook_node_info().
  */
 function worklog_node_info() {
-  return array('worklog' => array('name' => t('worklog'), 'base' => 'worklog'),
-               'worklog-invoice' => array('name' => t('worklog invoice'),
-                                          'base' => 'worklog'));
+  return array('worklog' => array('name' => t('worklog'),
+                              'module' => 'worklog',
+                              'description' => 'Log time spent working on something.'),
+               'worklog_invoice' => array('name' => t('worklog invoice'),
+                              'module' => 'worklog_invoice',
+                              'description' => 'A collection of worklog entries.'));
 }
 
 /**
  * Implementation of hook_perm().
  */
 define ('WORKLOG_PERM_CREATE', 'create worklog');
 define ('WORKLOG_PERM_EDIT', 'edit own worklog');
 define ('WORKLOG_PERM_VIEW_OTHERS', 'view others worklog');
+define ('WORKLOG_PERM_ADMIN', 'admin worklog settings');
 function worklog_perm() {
-  return array(WORKLOG_PERM_EDIT, WORKLOG_PERM_CREATE, 
-			   WORKLOG_PERM_VIEW_OTHERS);
+  return array(WORKLOG_PERM_EDIT, WORKLOG_PERM_CREATE, WORKLOG_PERM_VIEW_OTHERS, WORKLOG_PERM_ADMIN);
 }
 
 /**
  * Implementation of hook_access().
  */
 function worklog_access($op, $node) {
   global $user;
   
   if ($op == 'create') {
     return user_access(WORKLOG_PERM_CREATE);
   }
   
   if ($op == 'update' || $op == 'delete') {
     if (user_access(WORKLOG_PERM_EDIT) && ($user->uid == $node->uid)) {
       return TRUE;
     }
   }
   
   if ($op == 'view' && ($user->uid != $node->uid))
 	return user_access(WORKLOG_PERM_VIEW_OTHERS);
 }
 
 
 /**
  * Implementation of hook_menu().
  */
 function worklog_menu($may_cache) {
   $items = array();
 
   if ($may_cache) {
-    $items[] = array('path' => 'node/add/worklog', 'title' => t('worklog'),
-      'access' => user_access(WORKLOG_PERM_CREATE));
-    $items[] = array('path' => 'node/add/worklog-invoice', 
-                     'title' => t('worklog invoice'),
-                     'access' => user_access(WORKLOG_PERM_CREATE)
-    );    
+    $items[] = array('path' => 'admin/settings/worklog',
+      'title' => t('Worklog settings'),
+      'description' => t('Configure worklog settings'),
+      'callback' => 'drupal_get_form',
+      'callback arguments' => array('worklog_settings'),
+      'access' => user_access(WORKLOG_PERM_ADMIN),
+      'type' => MENU_NORMAL_ITEM
+    );
   }
-  
+
   return $items;
 }
 
 /**
  * hook_db_rewrite_sql
  * 
  * Prevent unauthorized users from seeing other user worklog
  */
 function worklog_db_rewrite_sql($query, $primary_table, $primary_field, $args) {
   if (!user_access(WORKLOG_PERM_VIEW_OTHERS)) {
 	global $user;
 	if ($primary_table == 'n' || $primary_table == 'node') {
-	  $where = "$primary_table.type != 'worklog' OR $primary_table.type != 'worklog-invoice' OR $primary_table.uid=$user->uid";
+	  $where = "$primary_table.type != 'worklog' OR $primary_table.type != 'worklog_invoice' OR $primary_table.uid=$user->uid";
 	  return array('where' => $where);
 	}
   }
 }
 
 
 /**
  * Implementation of hook_validate().
  */
 function worklog_validate($node) {
   
 }
 
 /**
  * Implementation of hook_insert().
  */
 function worklog_insert($node) {
-  if ($node->type == 'worklog')
-    return _worklog_insert($node);
-  else if ($node->type == 'worklog-invoice')
-    return _worklog_invoice_insert($node);
-}
-
-function _worklog_insert($node) {
   $form_values = $node->worklog;
   if ($form_values['wlid'])
 	// if worklog row already exists, simply update
 	return worklog_update($node);
   
   // else, we need to create a new row
   $elapsed = $form_values['elapsed'];
   $started = $form_values['started'];
 
   db_query('INSERT INTO {worklog} (nid, started, elapsed, name, rate) VALUES (%d, %d, %d, \'%s\', %d)', 
 		   $node->nid, $started, $elapsed, $node->title, $form_values['rate']);
 }
 
-function _worklog_invoice_insert($node) {
+function worklog_invoice_insert($node) {
   db_query('INSERT INTO {worklog_invoice} (nid, closed) VALUES (%d, %d)',
            $node->nid, $node->worklog['closed']);
 }
 
 /**
  * Implementation of hook_update().
  */
 function worklog_update($node) {
-  if ($node->type == 'worklog')
-    return _worklog_update($node);
-  else if ($node->type == 'worklog-invoice')
-    return _worklog_invoice_update($node);
-}
-
-function _worklog_update($node) {
   $form_values = $node->worklog;
   $elapsed = $form_values['elapsed'];
   $started = $form_values['started'];
 
   db_query('REPLACE INTO {worklog} (wlid, nid, started, elapsed, name, rate) VALUES (%d, %d, %d, %d, \'%s\', %f)', 
 		   $form_values['wlid'], $node->nid, $started, $elapsed, $node->title, $form_values['rate']);
   
 }
 
-function _worklog_invoice_update($node) {
+function worklog_invoice_update($node) {
   // will this work if new revision created?
   db_query('REPLACE INTO {worklog_invoice} (nid, closed) VALUES (%d, %d)',
            $node->nid, $node->worklog['closed']);
 }
 
 
 /**
  * Implementation of hook_delete().
  */
 function worklog_delete($node) {
-  if ($node->type == 'worklog') {
-    db_query('DELETE FROM {worklog} WHERE wlid=%d', $node->worklog->wlid);
-    db_query('DELETE FROM {worklog_invoice_map} WHERE worklog_nid=%d', $node->nid);
-  }
-  else if ($node->type == 'worklog-invoice') {
-    // TODO: what should happen to orphaned worklog entries?
-    db_query('DELETE FROM {worklog_invoice_map} WHERE invoice_nid=%d', $node->nid);
-    db_query('DELETE FROM {worklog_invoice} WHERE nid=%d', $node->nid);
-  }
+  db_query('DELETE FROM {worklog} WHERE wlid=%d', $node->worklog->wlid);
+  db_query('DELETE FROM {worklog_invoice_map} WHERE worklog_nid=%d', $node->nid);
+}
+function worklog_invoice_delete($node) {
+  // TODO: what should happen to orphaned worklog entries?
+  db_query('DELETE FROM {worklog_invoice_map} WHERE invoice_nid=%d', $node->nid);
+  db_query('DELETE FROM {worklog_invoice} WHERE nid=%d', $node->nid);
 }
 
 /**
  * Implementation of hook_load().
  */
 function worklog_load($node) {
-  if ($node->type == 'worklog') {
-    $worklog = db_fetch_object(db_query('SELECT * FROM {worklog} WHERE nid=%d', $node->nid));
-    $invoice = db_result(db_query('SELECT invoice_nid FROM {worklog_invoice_map} WHERE worklog_nid = %d', $node->nid));
-    $worklog->invoice_nid = $invoice;
-
-    return array('worklog' => $worklog);
-  }
-
-  else if ($node->type == 'worklog-invoice') {
-    $invoice_map = db_query('SELECT * FROM {worklog_invoice_map} WHERE invoice_nid = %d', $node->nid);
-    $invoice = db_fetch_object(db_query('SELECT * FROM {worklog_invoice} WHERE nid=%d', $node->nid));
-    while ($row = db_fetch_object($invoice_map)) {
-      $invoice->worklog_nids[] = $row->worklog_nid;
-    }
-    return array('worklog' => $invoice);
+  $worklog = db_fetch_object(db_query('SELECT * FROM {worklog} WHERE nid=%d', $node->nid));
+  $invoice = db_result(db_query('SELECT invoice_nid FROM {worklog_invoice_map} WHERE worklog_nid = %d', $node->nid));
+  $worklog->invoice_nid = $invoice;
+
+  return array('worklog' => $worklog);
+}
+function worklog_invoice_load($node) {
+  $invoice_map = db_query('SELECT * FROM {worklog_invoice_map} WHERE invoice_nid = %d', $node->nid);
+  $invoice = db_fetch_object(db_query('SELECT * FROM {worklog_invoice} WHERE nid=%d', $node->nid));
+  while ($row = db_fetch_object($invoice_map)) {
+    $invoice->worklog_nids[] = $row->worklog_nid;
   }
+  return array('worklog' => $invoice);
 }
 
 /**
  * Implementation of hook_form().
  */
 function worklog_form(&$node) {
-  if ($node->type == 'worklog')
-    return _worklog_form($node);
-  else if ($node->type == 'worklog-invoice')
-    return _worklog_invoice_form($node);
-}
-
-function _worklog_form(&$node) {
   // if we are creating a new node, get default values from wlid passed in
   if (!isset($node->nid) && !isset($node->worklog)) {
 	$timer = _worklog_get_timer($_REQUEST['wlid']);
 	if ($timer) {
 	  $node->worklog = $timer;
 	  $node->title = $timer->name;
 	}
 	else {
 	  // defaults if no timer used
 	  // TODO 
 	}
   }
 
   $form['title'] = array('#type' => 'textfield', '#title' => t('Title'), '#required' => TRUE, '#default_value' => $node->title);
 
   $form['worklog'] = array('#tree' => true,
 						   '#weight' => -1,
 						   );
   // should this be a select menu?
   $form['worklog']['wlid'] = array('#type' => 'hidden',
 								   '#value' => $node->worklog->wlid);
   // use variable name "_elapsed" instead of "elapsed", because it
   // will be parsed later.  See worklog_node_form_submit.  Perhaps
   // there is a better way to do this?
   $form['worklog']['_elapsed'] = array('#type' => 'textfield',
 									  '#title' => t('Time spent'),
 									  '#default_value' => _worklog_format_duration($node->worklog->elapsed),
 									  '#description' => t('Task duration in hours:minutes:seconds, or simply in hours.'));
   $form['worklog']['rate'] = array('#type'=> 'textfield',
 								   '#title' => t('Rate'),
 								   '#default_value' => $node->worklog->rate,
 								   '#description' => t('Billable rate in units per hour.'),
 								   );
   $form['worklog']['_started'] = array('#type' => 'textfield',
 									  '#title' => 'Started on',
 									  '#default_value' => format_date($node->worklog->started, 'custom', 'Y-m-d H:i:s O'),
 									  '#description' => t('When the task began.'));
   $form['body'] = array('#type' => 'textarea', '#title' => t('Description'), '#default_value' => $node->body, '#rows' => 5, '#required' => false);
   $form['format'] = filter_form($node->format);
 
   return $form;
 }
 
-function _worklog_invoice_form(&$node) {
+function worklog_invoice_form(&$node) {
   $form['title'] = array('#type' => 'textfield', '#title' => t('Title'), '#required' => TRUE, '#default_value' => $node->title);
 
-  $form['worklog'] = array('#tree' => true,
-						   '#weight' => -1,
-  );
+  $form['worklog'] = array('#tree' => true, '#weight' => -1,);
   $form['worklog']['closed'] = array('#type' => 'checkbox',
                                      '#title' => t('Closed to new entries'),
                                      '#default_value' => $node->worklog->closed,
                                      '#description' => t('Check this box when you will not add more worklog entries to this invoice.  For example, after you have sent the invoice to a client.'),
   );
-  
   $form['body'] = array('#type' => 'textarea', '#title' => t('Description'), '#default_value' => $node->body, '#rows' => 5, '#required' => false);
   $form['format'] = filter_form($node->format);
 
   return $form;
 }
 
 /**
  * When submitting a new worklog node, we need to parse the elapsed
  * time and start time.
  */
 function worklog_node_form_submit($form_id, &$form) {
   // convert formatted strings back into numbers
   $form['worklog']['elapsed'] = _worklog_parse_duration($form['worklog']['_elapsed']);
   $form['worklog']['started'] = strtotime($form['worklog']['_started']);
   return node_form_submit($form_id, $form);
 }
 
 
 define ('WORKLOG_BLOCK_TIMER', 0);
 define ('WORKLOG_BLOCK_PAUSED', 1);
 
 define('WORKLOG_BUTTON_PAUSE', t('Pause'));
 define('WORKLOG_BUTTON_STOP', t('Stop'));
 define('WORKLOG_BUTTON_RESUME', t('Resume'));
 define('WORKLOG_BUTTON_SAVE', t('Save'));
 
+function worklog_forms() {
+  $forms['worklog_paused_form']= array(
+    'callback' => 'worklog_paused_form',
+    'callback arguments' => array('worklog_paused_form'),
+  );
+  $forms['worklog_stop_form']= array(
+    'callback' => 'worklog_stop',
+    'callback arguments' => array('worklog_stop_form'),
+  );
+  $forms['worklog_start_form']= array(
+    'callback' => 'worklog_start',
+    'callback arguments' => array('worklog_start_form'),
+  );
+  return $forms;
+}
+
+function worklog_stop_form($timer) {
+  $form['name'] = array('#type' => 'markup',
+    '#value' => "<p>$timer->name</p>"
+  );
+  $form['wlid'] = array('#type' => 'hidden',
+    '#value' => $timer->wlid);
+  $form['buttons'][] = array('#type' => 'submit',
+    '#name' => 'op',
+    '#value' => WORKLOG_BUTTON_PAUSE
+  );
+  $form['buttons'][] = array('#type' => 'submit',
+    '#name' => 'op',
+    '#value' => WORKLOG_BUTTON_STOP
+  );
+  return $form;
+}
+function worklog_start_form() {
+  // the form to start a new timer
+  $form['name'] = array('#type' => 'textfield',
+    '#title' => t('Task name'),
+    '#size' => 20,
+  );
+  $form['submit'] = array('#type' => 'submit',
+    '#value' => t('Start Timer')
+  );
+  // $form['#attributes'] = array('target' => '_blank'); // new window
+  return $form;
+}
+
+function worklog_paused_form($timer) {
+  $form[$timer->wlid] = array('#type' => 'fieldset');
+  $form[$timer->wlid][] = array('#type' => 'markup', '#value' => "<div class=\"worklog_pause_header\"><span class=\"name\">$timer->name</span> <span class=\"elapsed\">"._worklog_format_duration($timer->elapsed)."</span></div>");
+  $form[$timer->wlid][] = array('#type' => 'submit', '#name' => 'worklog_'.$timer->wlid, '#value' => WORKLOG_BUTTON_RESUME, '#attributes' => $resume_attrs, );
+  $form[$timer->wlid][] = array('#type' => 'submit', '#name' => 'worklog_'.$timer->wlid, '#value' => WORKLOG_BUTTON_SAVE);
+  return $form;
+}
+
 /**
  * Define the blocks.  Implementation of hook_block()
  */
-function worklog_block($op = 'list', $delta=0, $edit=array()) {
+function worklog_block($op = 'list', $delta=0) {
   switch ($op) {
   case 'list':
-	$items[WORKLOG_BLOCK_TIMER]['info'] = 
-	  t('Worklog: start a worklog timer');
-	$items[WORKLOG_BLOCK_PAUSED]['info'] =
-	  t('Worklog: paused timers');
-    $items[WORKLOG_BLOCK_OP]['info'] =
-      t('Worklog: operations');
-	return $items;
+    $blocks[WORKLOG_BLOCK_TIMER]['info'] = t('Worklog: start a worklog timer');
+    $blocks[WORKLOG_BLOCK_PAUSED]['info'] = t('Worklog: paused timers');
+    $blocks[WORKLOG_BLOCK_OP]['info'] = t('Worklog: operations');
+    return $blocks;
   case 'view':
 	switch ($delta) {
-	case WORKLOG_BLOCK_TIMER:
-	  if (!user_access(WORKLOG_PERM_CREATE))
-		return;
-
-	  if ($timer = _worklog_current_timer()) {
-		$form['name'] = array('#type' => 'markup',
-							  '#value' => "<p>$timer->name</p>");
-		$form['wlid'] = array('#type' => 'hidden',
-							  '#value' => $timer->wlid);
-		$form['buttons'][] = array('#type' => 'submit',
-							   '#name' => 'op',
-							   '#value' => WORKLOG_BUTTON_PAUSE,
-							   );
-		$form['buttons'][] = array('#type' => 'submit',
-							  '#name' => 'op',
-							  '#value' => WORKLOG_BUTTON_STOP,
-							  );
-		$block['content'] = drupal_get_form('worklog_stop_form', $form);
-		$block['subject'] = t('Worklog Timer');
-	  }
-	  else {
-		// the form to start a new timer
-		$form['name'] = array('#type' => 'textfield',
-							  '#title' => t('Task name'),
-							  '#size' => 20,
-							  );
-		$form['submit'] = array('#type' => 'submit',
-								'#value' => t('Start Timer')
-								);
-		// $form['#attributes'] = array('target' => '_blank'); // new window
-		$block['content'] = drupal_get_form('worklog_start_form', $form);
-		$block['subject'] = t('Worklog New Timer');
-	  }
-	  return $block;
-	case WORKLOG_BLOCK_PAUSED:
+        case WORKLOG_BLOCK_TIMER && user_access(WORKLOG_PERM_CREATE):
+          if($timer = _worklog_current_timer()) {
+            $block['content'] = drupal_get_form('worklog_stop_form',$timer);
+          }
+          else {
+          $block['content'] = drupal_get_form('worklog_start_form');
+          }
+          $block['subject'] = t('Worklog New Timer');
+          return $block;
+        case WORKLOG_BLOCK_PAUSED && user_access(WORKLOG_PERM_EDIT):
 	  global $user;
-	  if (!user_access(WORKLOG_PERM_EDIT))
-		return;
-
 	  // give user control over paused timers
 	  $result = db_query("SELECT * FROM {worklog} WHERE nid IS NULL AND timer_start = 0 AND uid=%d", $user->uid);
 	  if (!db_num_rows($result))
 		return;
-
-	  $form = array();
 	  $current_timer = _worklog_current_timer();
 	  if ($current_timer)
-		$resume_attrs = array('disabled' => 1);
+            $resume_attrs = array('disabled' => 1);
 	  while ($timer = db_fetch_object($result)) {
-		$form[$timer->wlid] = array('#type' => 'fieldset');
-		$form[$timer->wlid][] = array('#type' => 'markup',
-									  '#value' => "<div class=\"worklog_pause_header\"><span class=\"name\">$timer->name</span> <span class=\"elapsed\">"._worklog_format_duration($timer->elapsed)."</span></div>");
-		$form[$timer->wlid][] = array('#type' => 'submit',
-									  '#name' => 'worklog_'.$timer->wlid,
-									  '#value' => WORKLOG_BUTTON_RESUME,
-									  '#attributes' => $resume_attrs,
-									  );
-		$form[$timer->wlid][] = array('#type' => 'submit',
-									  '#name' => 'worklog_'.$timer->wlid,
-									  '#value' => WORKLOG_BUTTON_SAVE);
-	  }
-	  $block['content'] .= drupal_get_form('worklog_paused_form', $form);
+            $block['content'] .= drupal_get_form('worklog_paused_form',$timer);
+          }
 	  $block['subject'] = t('Worklog: Paused Timers');
 	  return $block;
 	}
   }
 }
-
+/**
+* Theme the block worklog form.
+*/
+function theme_worklog_block_form($form) {
+//return drupal_render($form);
+}
 /**
  * Our pause form presents buttons for each paused timer.
  * Here we figure out which timer to act on and perform the necessary action.
  */
 function worklog_paused_form_submit($form_id, $form_values) {
   // Form API does not help us here, we have to find out which timer has been acted upon
   foreach ($_REQUEST as $key => $value) {
 	if (strpos($key, 'worklog_') === 0) {
 	  $wlid = substr($key, 8);
 	  $op = $value;
 	  switch ($op) {
 	  case WORKLOG_BUTTON_SAVE:
 		drupal_goto('node/add/worklog', "wlid=$wlid");
 		return;
 	  case WORKLOG_BUTTON_RESUME:
 		$now = time();
 		db_query("UPDATE {worklog} SET timer_start=%d WHERE wlid=%d",
 				 $now, $wlid);
 		drupal_set_message(t('Worklog: Resumed timer'));
 		drupal_goto(); // this is necessary to make the page reflect our changes
 		return;
 	  }
 	}
   }
 }
 
 /**
  * Here we ensure that only one timer is running concurrently.
  */
 function worklog_start_form_validate($form_id, $form_values) {
   // only one timer at a time
   if ($timer = _worklog_current_timer()) {
 	form_set_error('', t("Worklog: only one timer may be running."));
   }
 }
 /**
  * Start a timer by recording in the database when it was started.
  */
 function worklog_start_form_submit($form_id, $form_values) {
   global $user;
   $now = time();
   db_query("INSERT INTO {worklog} (name, started, timer_start, uid) VALUES ('%s', %d, %d, %d)",
 		   db_escape_string($form_values['name']), $now, $now, $user->uid);
   drupal_set_message("Worklog: started timer for $form_values[name]");
   drupal_goto(); // this is necessary to make the page reflect our changes
 }
 
 /**
  * Validation for stopping a timer (a timer must be running in order
  * to be stopped).
  */
 function worklog_stop_form_validate($form_id, $form_values) {
   $timer = _worklog_get_timer($form_values['wlid']);
   if (!$timer->timer_start) {
 	form_set_error('', t("Worklog: the timer is not running."));
   }
   global $user;
   if ($timer->uid != $user->uid)
 	drupal_access_denied(); // TODO: give admin permission to stop all timers
 }
 
 /**
  * Depending on the button pressed, either pause a timer, or stop and
  * save the timer.
  */
 function worklog_stop_form_submit($form_id, $form_values) {
   $timer = _worklog_get_timer($form_values['wlid']);
   switch ($_REQUEST['op']) {
   case WORKLOG_BUTTON_PAUSE:
 	_worklog_stop_timer($timer);
 	drupal_set_message(t("Worklog: paused timer %name", array('%name' => $timer->name)));
 	// nothing else to do for pause
 	drupal_goto(); // this is necessary to make the page reflect our changes
 	break;
   case WORKLOG_BUTTON_STOP:
 	_worklog_stop_timer($timer);
 	// for stopping, let's save our worklog node
 	drupal_goto('node/add/worklog', "wlid=$timer->wlid");
 	break;
   }
 }
 
 /**
  * Find what timer, if any, is currently running.
  * 
  * @return
  * An object representing the timer.
  */
 function _worklog_current_timer() {
   global $user;
   $timer = db_fetch_object(db_query("SELECT * FROM {worklog} WHERE timer_start > 0 AND uid=%d", $user->uid));
   return $timer;
 }
 /**
  * Get a timer by its ID.
  */
 function _worklog_get_timer($wlid) {
   $timer = db_fetch_object(db_query("SELECT * FROM {worklog} WHERE wlid=%d", $wlid));
   return $timer;
 }
 
 /**
  * Stop the timer by marking it as not running, and recording the elapsed time.
  */
 function _worklog_stop_timer($timer) {
   $now = time();
   // because timers can be stopped and restarted, 'elapsed' is our running total
   if ($timer->timer_start) {
 	$delta = $now - $timer->timer_start;
 	$elapsed = $timer->elapsed + $delta;
   }
   else
 	// we should never get here, but just in case, leave time unchanged
 	$elapsed = $timer->elapsed;
 
   db_query("UPDATE {worklog} SET timer_start=0, elapsed=$elapsed WHERE wlid=$timer->wlid");
   
 }
 
 // http://us2.php.net/function.fmod
 /**
  * Create a string representing the elapsed time.
  */
 function _worklog_format_duration( $int_seconds=0, $show_seconds = true)
 {
   $key_suffix = 's';
   $periods = array(
 				   //                     'year'        => 31556926,
 				   //                     'month'        => 2629743,
 				   //                     'day'        => 86400,
 				   'hour'        => 3600,
 				   'minute'    => 60,
 				   //'second'    => 1
                     );
   if ($show_seconds)
 	$periods['second'] = 1;
 
   // used to hide 0's in higher periods
     $flag_hide_zero = false;
 	
     // do the loop thang
     foreach( $periods as $key => $length )
 	  {
         // calculate
         $temp = floor( $int_seconds / $length );
 		
         // determine if temp qualifies to be passed to output
         if( !$flag_hide_zero || $temp > 0 )
 		  {
             // store in an array
 			$build[] = sprintf("%02d", $temp);
  
 			// set flag to false, to allow 0's in lower periods
 			$flag_hide_zero = false;
 		  }
 		
         // get the remainder of seconds
         $int_seconds = fmod($int_seconds, $length);
 	  }
 	
     // return output, if !empty, implode into string, else output $if_reached
     return ( !empty($build)?implode(':', $build):'' );
 }
 
 function _worklog_parse_duration($str) {
   $vals = explode(":", $str);
   if (count($vals) >=2 && count($vals) <= 3)
 	$seconds = $vals[0] * 3600 + $vals[1] * 60 + $vals[2];
   else if (is_numeric($str)) {
 	// assume a number of hours passed in
 	$seconds = $str * 3600;
   }
   else {
 	drupal_set_message(t('Unable to parse time given: "%str"', array('%str' => $str)), 'error');
   }
   return $seconds;
 }
 
 
 /**
  * Helper themeable function used when displaying a worklog node
  */
 function theme_worklog_info($node) {
   $worklog = (object)$node->worklog;
   $output = '<div class="worklog"><dl>';
   $output .= '<dt>'.t('Started on').'</dt>';
   $output .= '<dd>'.format_date($worklog->started).'</dd>';
   $output .= '<dt>'.t('Elapsed time').'</dt>';
   $output .= '<dd>'._worklog_format_duration($worklog->elapsed).'</dd>';
   $output .= '<dt>'.t('Hourly rate').'</dt>';
   $output .= '<dd>'.($worklog->rate).'</dd>';
   $output .= '<dt>'.t('Billable amount').'</dt>';
   $output .= '<dd>'.worklog_format_price(worklog_calculate_total($node)).'</dd>';
   $output .= "</dl></div>\n";
   return $output;
 }
 
 function theme_worklog_invoice_info($node) {
   $worklog = (object)$node->worklog;
   $output = '<div class="worklog"><dl>';
   $output .= '<dt>'.t('Total bill').'</dt>';
   $output .= '<dd>'.worklog_format_price(worklog_invoice_calculate_total($node)).'</dd>';
   $output .= "</dl></div>\n";
   return $output;
 }
 
 /*
  * Calculate the total amount of an invoice.
  */
 function worklog_invoice_calculate_total($node) {
   $nids = $node->worklog->worklog_nids;
   $total = 0;
   if (count($nids)) {
     foreach ($nids as $nid) {
       $worklog = node_load($nid);
       $total += worklog_calculate_total($worklog);
     }
   }
   return $total;
 }
 
 function worklog_calculate_total($node) {
   return _worklog_calculate_bill($node->worklog->elapsed, 
                                  $node->worklog->rate);
 }
 /**
  * Implementation of hook_view()
  */
 function worklog_view(&$node, $teaser=false, $page=false) {
   if ($node->type == 'worklog')
     return _worklog_view($node, $teaser, $pager);
-  else if ($node->type == 'worklog-invoice') {
+  else if ($node->type == 'worklog_invoice') {
     return _worklog_invoice_view($node, $teaser, $pager);
   }
 }
 
 function _worklog_view(&$node, $teaser, $page) {
   if ($node->in_preview) {
 	// seems like we should not have to do this!
 	$node->worklog['elapsed'] = _worklog_parse_duration($node->worklog['_elapsed']);
 	$node->worklog['started'] = strtotime($node->worklog['_started']);
   }
 
   // drupal_set_message(theme('debug', $node));
   $node = node_prepare($node, $teaser);
   $info = theme('worklog_info', $node);
   $node->body = $info . $node->body;
   $node->teaser = $info . $node->teaser;
 }
 
 function _worklog_invoice_view(&$node, $teaser, $page) {
   $node = node_prepare($node, $teaser);
   
   if ($vid = variable_get('worklog_invoice_view', 0)) {
     // use a view to render invoice items
     $view = views_get_view($vid);
     $node->body .= views_build_view('embed', $view, array($node->nid),
                                     $view->use_pager, $view->nodes_per_page);
     
     
   }
   $info = theme('worklog_invoice_info', $node);
   $node->body .= $info;
   $node->teaser .= $info;
 
   // allow third party modules to supply additional header and footer
   $extra_header = module_invoke_all('worklog', WORKLOG_OP_INVOICE_HEADER,
                                     $node, $teaser, $page);
   $extra_footer = module_invoke_all('worklog', WORKLOG_OP_INVOICE_FOOTER,
                                     $node, $teaser, $page);
   if (!$teaser)
     $node->body = implode('',$extra_header) . $node->body . 
       implode('',$extra_footer);
   else
     $node->teaser = implode('',$extra_header) . $node->teaser . 
       implode('',$extra_footer);
 
 }
 
 /*
  * hook_links
  */
 function worklog_link($type, $node=NULL, $teaser=FALSE) {
   $items = array();
   if ($type == 'node' && $node->type == 'worklog') {
     if ($invoice_nid = $node->worklog->invoice_nid) {
       $invoice = node_load($invoice_nid);
       if ($invoice->status)
-        $items[] = l($invoice->title, "node/$invoice_nid");
+        $links['worklog_invoice_$invoice_nid'] = array('title' => t($invoice->title), 'href' => "node/$invoice_nid");
     }
   }
-  return $items;
+  return $links;
 }
 
 function worklog_views_tables() {
   $tables['worklog'] = 
 	array('name' => 'worklog',
 		  'join' => array('left' => array('table' => 'node',
 										  'field' => 'nid'),
 						  'right' => array('field' => 'nid')),
 		  'fields' => array('started' => 
 							array('name' => t('Worklog: start time'),
 								  'sortable' => true,
 								  'handler' => 'worklog_views_handler_field_date',
 								  ),
 							'elapsed' =>
 							array('name' => t('Worklog: elapsed time'),
 								  'sortable' => true,
 								  'handler' => 'worklog_views_handler_field_elapsed',
 								  ),
 							'rate' =>
 							array('name' => t('Worklog: hourly rate'),
 								  'sortable' => true,
 								  ),
 							'bill' =>
 							array('notafield' => true,
 								  'name' => t('Worklog: billable'),
 								  'handler' => 'worklog_views_handler_field_bill',
 								  'addlfields' => array('rate'),
 								  ),
 							),
 		  'sorts' => array('started' => 
 						   array('name' => t('Worklog: start time'))),
 		  );
   $tables['worklog_invoice_map'] =
     array('name' => 'worklog_invoice_map',
           'join' => array('left' => array('table' => 'node',
                                           'field' => 'nid'),
                           'right' => array('field' => 'worklog_nid')),
     );
   return $tables;
 }
 
 function worklog_views_arguments() {
   $items = array();
 
   $items[] = array('name' => t('Worklog: Invoice nid'),
                    'handler' => '_worklog_handler_invoice_nid');
   return $items;
 }
 
 function _worklog_handler_invoice_nid($op, &$query, $argtype, $arg = '') {
   //drupal_set_message("_worklog_handler_invoice_nid($op)" . dprint_r($query, 1). dprint_r($arg, 1));
   if ($op == 'summary') {
     $query->add_field('title');
     $query->add_field('nid');
-    $query->add_where("node.type = 'worklog-invoice'");
+    $query->add_where("node.type = 'worklog_invoice'");
     return array('field' => 'node.title');
   }
   else if ($op == 'title') {
     $invoice = node_load($query);
     return $invoice->title;
   }
   else if ($op == 'filter') {
     $query->add_table('worklog_invoice_map');
     $query->add_where("worklog_invoice_map.invoice_nid = $arg");
   }
   else if ($op == 'link') {
     return l($query->title, "$arg/$query->nid");
   }
 }
 
 function _worklog_round_duration($seconds) {
   $minutes = $seconds / 60;
 
   // round to nearest X minutes
 
   $rminutes = round($minutes/WORKLOG_ROUND_MINUTES)*WORKLOG_ROUND_MINUTES;
   return $rminutes * 60; // seconds, rounded to an even minute value
 }
 
 /**
  * Round monetary amounts to nearest cent
  */
 function _worklog_round_bill($bill) {
   return round($bill * 100) / 100;
 }
 
 function _worklog_calculate_bill($elapsed, $rate) {
   return _worklog_round_bill(($rate * _worklog_round_duration($elapsed) / (60 * 60)));
 }
 
 function worklog_views_handler_field_elapsed($fieldinfo, $fielddata, $value, $data) {
   $value = _worklog_round_duration($value);
   return theme('worklog_duration', $value);
 }
 
 function worklog_views_handler_field_date($fieldinfo, $fielddata, $value, $data) {
   return format_date($value, 'small');
 }
 function worklog_views_handler_field_bill($fieldinfo, $fielddata, $value, $data) {
   $billable = _worklog_calculate_bill($data->worklog_elapsed,
                                       $data->worklog_rate);
 
   return theme('worklog_price', $billable);
 }
 
 /**
  * theme function called by views module
  */
 /* XXX
 function theme_views_view_worklog($view, $type, $data) {
   // TODO: break data up into weekly chunks
   
   if ($type == 'page') {
 	drupal_set_title(views_get_title($view));
   }
   if ($view->header) {
     $header = check_markup($view->header, $view->header_format, false);
     $output = "<div class='view-header' id='view-header-$view->name'>$header</div>\n";
   }
   
   $output .= theme('views_view_table', $view, $data);
 
   // compute totals
   $total_elapsed = 0;
   $total_bill = 0;
   foreach ($data as $datum) {
 	//drupal_set_message(theme('debug', $datum));
 	$elapsed = _worklog_round_duration($datum->worklog_elapsed);
 	$total_elapsed += $elapsed;
 	if ($datum->worklog_rate) {
 	  $total_bill += _worklog_round_bill(($datum->worklog_rate * $elapsed) / (60 * 60));
 	}
   }
   
   $output .= "<dl>\n<dt>".t("Total Time")."</dt><dd>".theme('worklog_duration', $total_elapsed)."</dd>\n";
   // TODO: format nicely
   $output .= "<dt>".t("Total Bill")."</dt><dd>".$total_bill."</dd>\n";
   $output .= "</dl>\n";
 
   return $output;
 }
 */
 
 /**
  * Display seconds as hours
  */
 function theme_worklog_duration($seconds) {
   $hours = $seconds / (60 *60);
   return $hours . t(' hours');
 }
 
 function theme_worklog_price($amount) {
   return worklog_format_price($amount);
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // functions for invoices
 
 /**
  * Use hook_form_alter to manage the one-to-many relationship between invoices
  * and worklog entries
  */
 function worklog_form_alter($form_id, &$form) {
   if (isset($form['type']) &&
       isset($form['type']['#value'])) {
     $type = $form['type']['#value'];
     if ($type . '_node_form' == $form_id) {
       // it's a node form
       $nid = $form['nid']['#value'];
-      if ($type == 'worklog-invoice') {
+      if ($type == 'worklog_invoice') {
         // on invoice form, allow choice of all non-invoiced worklogs
         $result = db_query(db_rewrite_sql('SELECT n.title, w.* FROM {node} n LEFT JOIN {worklog} w ON n.nid = w.nid LEFT JOIN {worklog_invoice_map} wim ON wim.worklog_nid = n.nid WHERE n.type=\'worklog\' AND n.status = 1 AND (wim.invoice_nid IS NULL OR wim.invoice_nid = %d)'), $nid);
         $options = array();
         while ($row = db_fetch_object($result)) {
           // TODO: show more than title, perhaps build a table.
           $options[$row->nid] = t('%title (%hours @ %rate = %amount)',
                                   array('%title' =>$row->title,
                                         '%hours' => _worklog_format_duration($row->elapsed),
                                         '%rate' => $row->rate,
                                         '%amount' => _worklog_calculate_bill($row->elapsed, $row->rate)
                                   ));
         }
         $form['worklog_invoice'] = 
           array('#type' => 'fieldset',
                 '#title' => t('Invoice items'),
                 '#tree' => TRUE,
                 '#collapsible' => TRUE,
           );
         //drupal_set_message(dprint_r($form['#node'], 1));
         $form['worklog_invoice']['worklog_nids'] =
           array('#type' => 'checkboxes',
                 '#title' => t('Select items'),
                 '#options' => $options,
                 '#default_value' => $form['#node']->worklog->worklog_nids,
                 '#description' => t('Choose the items to include in this invoice.'),
           );
-        
+
         // callback to perform invoice to worklog mapping
-        $form['#submit'] = array('_worklog_invoice_map_form_submit' => array()) +
-          (array)$form['#submit'];
-                                 
+        $form['#submit'] = array('_worklog_invoice_map_form_submit' => array()) + (array)$form['#submit'];
+
       }
       else if ($type == 'worklog') {
         $invoice_nid = $form['#node']->worklog->invoice_nid;
         $invoice = new stdClass();
         if ($invoice_nid) {
           $invoice = node_load($invoice_nid);
           if ($invoice->worklog->closed) {
             drupal_set_message(t('This worklog entry may have already been invoiced.'));
             $form['worklog_invoice'] = 
               array('#type' => 'fieldset',
                     '#title' => t('Invoice'),
                     '#description' => t('This worklog entry is included in the invoice %invoice_link.  The invoice has been marked closed.  It is recommended not to make any changes to this entry.',
                                         array('%invoice_link' => $invoice->title)),
               );
           }
         }
         if (!$invoice_nid || !$invoice->worklog->closed) {
           // let user assign a worklog to an invoice
           $form['worklog_invoice'] = 
             array('#type' => 'fieldset',
                   '#title' => t('Invoice'),
                   '#tree' => TRUE,
                   '#collapsible' => TRUE,
             );
-          
-          $result = db_query(db_rewrite_sql('SELECT n.title, n.nid FROM {node} n LEFT JOIN {worklog_invoice} wi ON n.nid = wi.nid WHERE n.type=\'worklog-invoice\' AND n.status = 1 AND (wi.closed = 0 OR n.nid=%d)'), $invoice_nid);
+            $result = db_query('SELECT n.title, n.nid FROM {node} n LEFT JOIN {worklog_invoice} wi ON n.nid = wi.nid WHERE n.type=\'%s\' AND n.status = 1 AND (wi.closed = 0)',array('%s'=>'worklog_invoice'));
           $options = array(0 => t('no invoice'));
           while ($row = db_fetch_object($result)) {
-            $options[$row->nid] = t('%title',
+            $options[$row->nid] = t('%title', 
                                     array('%title' =>$row->title,
                                   )
             );
           }
           $form['worklog_invoice']['invoice_nid'] =
             array('#type' => 'radios',
                   '#title' => t('Select invoice'),
                   '#options' => $options,
                   '#default_value' => $invoice_nid,
                   '#description' => t('This item will be included in the chosen invoice.'),
           );
           
         }
         // callback to perform invoice to worklog mapping
-        $form['#submit'] = array('_worklog_map_form_submit' => array()) +
-          (array)$form['#submit'];
+        $form['#submit'] = array('_worklog_map_form_submit' => array()) + (array)$form['#submit'];
         
         
       }
     }
   }
 }
 
 function _worklog_invoice_map_form_submit($form_id, $values) {
   //drupal_set_message("_worklog_invoice_map_form_submit($form_id)" . dprint_r($values, 1));
   $invoice_nid = $values['nid'];
   db_query('DELETE FROM {worklog_invoice_map} WHERE invoice_nid = %d', $invoice_nid);
   if (count($values['worklog_invoice']) &&
       count($values['worklog_invoice']['worklog_nids'])) {
     foreach ($values['worklog_invoice']['worklog_nids'] as $worklog_nid => $checked) {
       if ($checked)
         db_query('INSERT INTO {worklog_invoice_map} (invoice_nid, worklog_nid) VALUES (%d, %d)', $invoice_nid, $worklog_nid);
     }
   }
 }
 
 function _worklog_map_form_submit($form_id, $values) {
   //drupal_set_message("_worklog_map_form_submit($form_id)" . dprint_r($values, 1));
   $worklog_nid = $values['nid'];
   db_query('DELETE FROM {worklog_invoice_map} WHERE worklog_nid = %d', $worklog_nid);
   if (count($values['worklog_invoice'])) {
     if ($invoice_nid = $values['worklog_invoice']['invoice_nid']) {
       db_query('INSERT INTO {worklog_invoice_map} (invoice_nid, worklog_nid) VALUES (%d, %d)', $invoice_nid, $worklog_nid);      
     }
   }
 }
 
 function worklog_settings() {
   $form['worklog_invoice'] = 
     array('#type' => 'fieldset',
           '#title' => t('Invoice settings'),
     );
   $options = array(0 => '<' . t('none') . '>') +
     _worklog_get_views();
   $form['worklog_invoice']['worklog_invoice_view'] =
     array('#type' => 'select',
           '#title' => t('Worklog view'),
           '#description' => t('Use the Views module to define a view showing all the entries in an invoice.  The view\'s first argument should be a worklog invoice ID.  Select the view here, and it will be included when viewing an invoice.'),
           '#options' => $options,
           '#default_value' => variable_get('worklog_invoice_view', 0),
     );
 
   // Begin price format section
   $form['price_format'] = array(
     '#type' => 'fieldset', 
     '#title' => t('Price Formatting'), 
     '#collapsible' => TRUE, 
     '#collapsed' => TRUE, 
   );
   $form['price_format']['worklog_symbol'] = array(
     '#type' => 'textfield', 
     '#title' => t('Currency Symbol'), 
     '#default_value' => variable_get('worklog_symbol', '$'), 
     '#size' => 3, 
     '#maxlength' => 5, 
     '#desciption' => t('Enter the currency symbol you wish to associate with your price. This will be displayed in front of the price. Default is the dollar symbol.')
   );
   $form['price_format']['worklog_symbol_position'] = array(
     '#type' => 'radios', 
     '#title' => t('Currency Symbol'), 
     '#default_value' => variable_get('worklog_symbol_position', 1), 
     '#options' => array(t('Right'), t('Left')),
     '#desciption' => t('This option places the currency symbol of the left or right side of the price.')
   );
   $form['price_format']['worklog_thousands'] = array(
     '#type' => 'textfield', 
     '#title' => t('Thousands separator'), 
     '#default_value' => variable_get('worklog_thousands', ','), 
     '#size' => 3, '#maxlength' => 5, 
     '#desciption' => t('Enter the sign for the thousands separator.')
    );
    $form['price_format']['worklog_decimal'] = array(
     '#type' => 'textfield', 
     '#title' => t('Decimal separator'), 
     '#default_value' => variable_get('worklog_decimal', '.'), 
     '#size' => 3, '#maxlength' => 5, 
     '#desciption' => t('Enter the sign to seperate real numbers from floating numbers.')
    );
   $form['price_format']['worklog_decimal_places'] = array(
     '#type' => 'textfield', 
     '#title' => t('Number of places after the decimal separator'), 
     '#default_value' => variable_get('worklog_decimal_places', 2), 
     '#size' => 3, '#maxlength' => 5, 
     '#desciption' => t('How many slots are needed after the decimal?')
    );
-
-
-
-  return $form;
+  return system_settings_form($form);
 }
 
 /**
  * Fetches a list of all views from the database.
  * 
  * Unfortunately the views module does not provide a helper for us, so we have to use some knowledge of the views database schema.
  *
  * @return
  *   An array of views, where each key is the view's ID, and each value is its
  *   description (not its title).
  */
 function _worklog_get_views() {
   $views = array();
   
   if (function_exists('views_build_view')) {
     // views module is installed
-    $result = db_query("SELECT vid, description FROM {view_view} ORDER BY description");
+    $result = db_query("SELECT vid, name FROM {view_view} ORDER BY description");
     while ($view = db_fetch_object($result)) {
-      $views[$view->vid] = $view->description;
+      $views[$view->vid] = $view->name;
     }
   }
   
   return $views;
 }
 
 /**
  * Format the price according to config options.
  */
 function worklog_format_price($price) {
   $price = number_format((float) $price,
     variable_get('worklog_decimal_places', 2), 
     variable_get('worklog_decimal', '.'), 
     variable_get('worklog_thousands', ','));
   return (variable_get('worklog_symbol_position', 1) == 1) ? variable_get('worklog_symbol', '$') . $price : $price . variable_get('worklog_symbol', '$');
 }
 
-?>
\ No newline at end of file
+?>
