Fixed import-export for correct associations in custom field log.
[timetracker.git] / WEB-INF / lib / ttOrgImportHelper.class.php
index 2758ac2..1bfa07c 100644 (file)
@@ -32,7 +32,6 @@ import('ttTaskHelper');
 import('ttClientHelper');
 import('ttInvoiceHelper');
 import('ttTimeHelper');
-import('ttCustomFieldHelper');
 import('ttExpenseHelper');
 import('ttFavReportHelper');
 
@@ -46,7 +45,7 @@ class ttOrgImportHelper {
   var $firstPass      = true;    // True during first pass through the file.
   var $org_id         = null;    // Organization id (same as top group_id).
   var $current_group_id        = null; // Current group id during parsing.
-  var $current_parent_group_id = null; // Current parent group id during parsing.
+  var $parents        = array(); // A stack of parent group ids for current group all the way to the root including self.
   var $top_role_id    = 0;       // Top role id.
 
   // Entity maps for current group. They map XML ids with database ids.
@@ -107,9 +106,10 @@ class ttOrgImportHelper {
       if ($name == 'GROUP') {
         // Create a new group.
         $this->current_group_id = $this->createGroup(array(
-          'parent_id' => $this->current_parent_group_id,
+          'parent_id' => $this->current_group_id, // Note: after insert current_group_id changes.
           'org_id' => $this->org_id,
           'name' => $attrs['NAME'],
+          'description' => $attrs['DESCRIPTION'],
           'currency' => $attrs['CURRENCY'],
           'decimal_mark' => $attrs['DECIMAL_MARK'],
           'lang' => $attrs['LANG'],
@@ -135,8 +135,8 @@ class ttOrgImportHelper {
           $sql = "update tt_groups set org_id = $this->current_group_id where org_id is NULL and id = $this->current_group_id";
           $affected = $mdb2->exec($sql);
         }
-        // Set parent group to create subgroups with this group as parent at next entry here.
-        $this->current_parent_group_id = $this->current_group_id;
+        // Add self to parent stack.
+        array_push($this->parents, $this->current_group_id);
         return;
       }
 
@@ -331,7 +331,7 @@ class ttOrgImportHelper {
 
       if ($name == 'LOG_ITEM') {
         // We get here when processing <log_item> tags for the current group.
-        $log_item_id = ttTimeHelper::insert(array(
+        $log_item_id = $this->insertLogEntry(array(
           'user_id' => $this->currentGroupUserMap[$attrs['USER_ID']],
           'group_id' => $this->current_group_id,
           'org_id' => $this->org_id,
@@ -339,11 +339,11 @@ class ttOrgImportHelper {
           'start' => $attrs['START'],
           'finish' => $attrs['FINISH'],
           'duration' => $attrs['DURATION'],
-          'client' => $this->currentGroupClientMap[$attrs['CLIENT_ID']],
-          'project' => $this->currentGroupProjectMap[$attrs['PROJECT_ID']],
-          'task' => $this->currentGroupTaskMap[$attrs['TASK_ID']],
-          'invoice' => $this->currentGroupInvoiceMap[$attrs['INVOICE_ID']],
-          'note' => (isset($attrs['COMMENT']) ? $attrs['COMMENT'] : ''),
+          'client_id' => $this->currentGroupClientMap[$attrs['CLIENT_ID']],
+          'project_id' => $this->currentGroupProjectMap[$attrs['PROJECT_ID']],
+          'task_id' => $this->currentGroupTaskMap[$attrs['TASK_ID']],
+          'invoice_id' => $this->currentGroupInvoiceMap[$attrs['INVOICE_ID']],
+          'comment' => (isset($attrs['COMMENT']) ? $attrs['COMMENT'] : ''),
           'billable' => $attrs['BILLABLE'],
           'paid' => $attrs['PAID'],
           'status' => $attrs['STATUS']));
@@ -388,9 +388,9 @@ class ttOrgImportHelper {
 
       if ($name == 'CUSTOM_FIELD_OPTION') {
         // We get here when processing <custom_field_option> tags for the current group.
-        $custom_field_option_id = ttCustomFieldHelper::insertOption(array(
-          // 'group_id' => $this->current_group_id, TODO: add this when group_id field is added to the table.
-          // 'org_id' => $this->org_id, TODO: add this when org_id field is added to the table.
+        $custom_field_option_id = $this->insertCustomFieldOption(array(
+          'group_id' => $this->current_group_id,
+          'org_id' => $this->org_id,
           'field_id' => $this->currentGroupCustomFieldMap[$attrs['FIELD_ID']],
           'value' => $attrs['VALUE']));
         if ($custom_field_option_id) {
@@ -402,9 +402,9 @@ class ttOrgImportHelper {
 
       if ($name == 'CUSTOM_FIELD_LOG_ENTRY') {
         // We get here when processing <custom_field_log_entry> tags for the current group.
-        if (!ttCustomFieldHelper::insertLogEntry(array(
-          // 'group_id' => $this->current_group_id, TODO: add this when group_id field is added to the table.
-          // 'org_id' => $this->org_id, TODO: add this when org_id field is added to the table.
+        if (!$this->insertCustomFieldLogEntry(array(
+          'group_id' => $this->current_group_id,
+          'org_id' => $this->org_id,
           'log_id' => $this->currentGroupLogMap[$attrs['LOG_ID']],
           'field_id' => $this->currentGroupCustomFieldMap[$attrs['FIELD_ID']],
           'option_id' => $this->currentGroupCustomFieldOptionMap[$attrs['OPTION_ID']],
@@ -541,6 +541,22 @@ class ttOrgImportHelper {
     }
   }
 
+  // endElement - callback handler for ending tags in XML.
+  // We use this only for process </group> element endings and
+  // set current_group_id to an immediate parent.
+  // This is required to import group hierarchy correctly.
+  function endElement($parser, $name) {
+    // No need to care about first or second pass, as this is used only in second pass.
+    // See 2nd xml_set_element_handler, where this handler is set.
+    if ($name == 'GROUP') {
+      // Remove self from the parent stack.
+      $self = array_pop($this->parents);
+      // Set current group id to an immediate parent.
+      $len = count($this->parents);
+      $this->current_group_id = $len ? $this->parents[$len-1] : null;
+    }
+  }
+
   // importXml - uncompresses the file, reads and parses its content. During parsing,
   // startElement, endElement, and dataElement functions are called as many times as necessary.
   // Actual import occurs in the endElement handler.
@@ -575,7 +591,7 @@ class ttOrgImportHelper {
     // Initialize XML parser.
     $parser = xml_parser_create();
     xml_set_object($parser, $this);
-    xml_set_element_handler($parser, 'startElement', false);
+    xml_set_element_handler($parser, 'startElement', false); // No need to process end tags in 1st pass.
 
     // We need to parse the file 2 times:
     //   1) First pass: determine if import is possible.
@@ -608,9 +624,9 @@ class ttOrgImportHelper {
     // Now we can do a second pass, where real work is done.
     $parser = xml_parser_create();
     xml_set_object($parser, $this);
-    xml_set_element_handler($parser, 'startElement', false);
+    xml_set_element_handler($parser, 'startElement', 'endElement'); // Need to process ending tags too.
 
-    // Read and parse the content of the file. During parsing, startElement is called back for each tag.
+    // Read and parse the content of the file. During parsing, startElement and endElement are called back for each tag.
     $file = fopen($filename, 'r');
     while (($data = fread($file, 4096)) && $this->errors->no()) {
       if (!xml_parse($parser, $data, feof($file))) {
@@ -656,7 +672,7 @@ class ttOrgImportHelper {
     global $i18n;
     $mdb2 = getConnection();
 
-    $columns = '(parent_id, org_id, name, currency, decimal_mark, lang, date_format, time_format'.
+    $columns = '(parent_id, org_id, name, description, currency, decimal_mark, lang, date_format, time_format'.
       ', week_start, tracking_mode, project_required, task_required, record_type, bcc_email'.
       ', allow_ip, password_complexity, plugins, lock_spec'.
       ', workday_minutes, config, created, created_ip, created_by)';
@@ -665,6 +681,7 @@ class ttOrgImportHelper {
     $values .= $mdb2->quote($fields['parent_id']);
     $values .= ', '.$mdb2->quote($fields['org_id']);
     $values .= ', '.$mdb2->quote(trim($fields['name']));
+    $values .= ', '.$mdb2->quote(trim($fields['description']));
     $values .= ', '.$mdb2->quote(trim($fields['currency']));
     $values .= ', '.$mdb2->quote($fields['decimal_mark']);
     $values .= ', '.$mdb2->quote($fields['lang']);
@@ -939,4 +956,90 @@ class ttOrgImportHelper {
     $last_id = $val['last_insert_id'];
     return $last_id;
   }
+
+  // insertCustomFieldOption - a helper function to insert a custom field option.
+  private function insertCustomFieldOption($fields) {
+    $mdb2 = getConnection();
+
+    $group_id = (int) $fields['group_id'];
+    $org_id = (int) $fields['org_id'];
+    $field_id = (int) $fields['field_id'];
+    $value = $fields['value'];
+
+    $sql = "insert into tt_custom_field_options (group_id, org_id, field_id, value)".
+      " values ($group_id, $org_id, $field_id, ".$mdb2->quote($value).")";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error'))
+      return false;
+
+    $last_id = 0;
+    $sql = "select last_insert_id() as last_insert_id";
+    $res = $mdb2->query($sql);
+    $val = $res->fetchRow();
+    $last_id = $val['last_insert_id'];
+    return $last_id;
+  }
+
+  // insertLogEntry - a helper function to insert a time log entry.
+  private function insertLogEntry($fields) {
+    global $user;
+    $mdb2 = getConnection();
+
+    $group_id = (int) $fields['group_id'];
+    $org_id = (int) $fields['org_id'];
+    $user_id = (int) $fields['user_id'];
+    $date = $fields['date'];
+    $start = $fields['start'];
+    $duration = $fields['duration'];
+    $client_id = $fields['client_id'];
+    $project_id = $fields['project_id'];
+    $task_id = $fields['task_id'];
+    $invoice_id = $fields['invoice_id'];
+    $comment = $fields['comment'];
+    $billable = (int) $fields['billable'];
+    $paid = (int) $fields['paid'];
+    $status = $fields['status'];
+
+    $sql = "insert into tt_log".
+      " (user_id, group_id, org_id, date, start, duration, client_id, project_id, task_id, invoice_id, comment".
+      ", billable, paid, created, created_ip, created_by, status)".
+      " values ($user_id, $group_id, $org_id".
+      ", ".$mdb2->quote($date).
+      ", ".$mdb2->quote($start).
+      ", ".$mdb2->quote($duration).
+      ", ".$mdb2->quote($client_id).
+      ", ".$mdb2->quote($project_id).
+      ", ".$mdb2->quote($task_id).
+      ", ".$mdb2->quote($invoice_id).
+      ", ".$mdb2->quote($comment).
+      ", $billable, $paid".
+      ", now(), ".$mdb2->quote($_SERVER['REMOTE_ADDR']).", ".$mdb2->quote($user->id).
+      ", ". $mdb2->quote($status).")";
+    $affected = $mdb2->exec($sql);
+    if (is_a($affected, 'PEAR_Error')) {
+      $this->errors->add($i18n->get('error.db')); // TODO: review whether or not to add error here in all insert calls.
+      return false;
+    }
+
+    $log_id = $mdb2->lastInsertID('tt_log', 'id');
+    return $log_id;
+  }
+
+  // insertCustomFieldLogEntry - a helper function to insert a custom field log entry.
+  private function insertCustomFieldLogEntry($fields) {
+    $mdb2 = getConnection();
+
+    $group_id = (int) $fields['group_id'];
+    $org_id = (int) $fields['org_id'];
+    $log_id = (int) $fields['log_id'];
+    $field_id = (int) $fields['field_id'];
+    $option_id = $fields['option_id'];
+    $value = $fields['value'];
+    $status = $fields['status'];
+
+    $sql = "insert into tt_custom_field_log (group_id, org_id, log_id, field_id, option_id, value, status)".
+      " values ($group_id, $org_id, $log_id, $field_id, ".$mdb2->quote($option_id).", ".$mdb2->quote($value).", ".$mdb2->quote($status).")";
+    $affected = $mdb2->exec($sql);
+    return (!is_a($affected, 'PEAR_Error'));
+  }
 }