X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=WEB-INF%2Flib%2FttOrgImportHelper.class.php;h=77458cfaafa2e45174f314fcbd78413177d46b99;hb=509d1d70d0580e26a4467e123c06d2c8939f32cc;hp=ccdc05b20f8c4665094894c5b90a6e557ec326b7;hpb=b06601362567c117bdc889277d554c8c58f0efdc;p=timetracker.git diff --git a/WEB-INF/lib/ttOrgImportHelper.class.php b/WEB-INF/lib/ttOrgImportHelper.class.php index ccdc05b2..77458cfa 100644 --- a/WEB-INF/lib/ttOrgImportHelper.class.php +++ b/WEB-INF/lib/ttOrgImportHelper.class.php @@ -31,19 +31,22 @@ import('ttRoleHelper'); import('ttTaskHelper'); import('ttProjectHelper'); import('ttClientHelper'); +import('ttInvoiceHelper'); +import('ttCustomFieldHelper'); +import('ttExpenseHelper'); +import('ttFavReportHelper'); -// ttOrgImportHelper - this class is a future replacement for ttImportHelper. -// Currently, it is work in progress. -// When done, it should handle import of complex groups consisting of other groups. +// ttOrgImportHelper class is used to import organization data from an XML file +// prepared by ttOrgExportHelper and consisting of nested groups with their info. class ttOrgImportHelper { var $errors = null; // Errors go here. Set in constructor by reference. - var $conflicting_entities = null; // A comma-separated list of entity names we cannot import. + var $schema_version = null; // Database schema version from XML file we import from. + var $conflicting_logins = null; // A comma-separated list of logins we cannot import. var $canImport = true; // False if we cannot import data due to a conflict such as login collision. 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. - // Set when we create a new group. var $top_role_id = 0; // Top role id. // Entity maps for current group. They map XML ids with database ids. @@ -52,6 +55,10 @@ class ttOrgImportHelper { var $currentGroupProjectMap = array(); var $currentGroupClientMap = array(); var $currentGroupUserMap = array(); + var $currentGroupInvoiceMap = array(); + var $currentGroupLogMap = array(); + var $currentGroupCustomFieldMap = array(); + var $currentGroupCustomFieldOptionMap = array(); // Constructor. function __construct(&$errors) { @@ -59,17 +66,34 @@ class ttOrgImportHelper { $this->top_role_id = ttRoleHelper::getRoleByRank(512, 0); } - // startElement - callback handler for opening tag of an XML element in the file. + // startElement - callback handler for opening tags in XML. function startElement($parser, $name, $attrs) { global $i18n; - // First pass. We only check user logins for potential collisions with existing. + // First pass through the file determines if we can import data. + // We require 2 things: + // 1) Database schema version must be set. This ensures we have a compatible file. + // 2) No login coillisions are allowed. if ($this->firstPass) { + if ($name == 'ORG' && $this->canImport) { + if ($attrs['SCHEMA'] == null) { + // We need (database) schema attribute to be available for import to work. + // Old Time Tracker export files don't have this. + // Current import code does not work with old format because we had to + // restructure data in export files for subgroup support. + $this->canImport = false; + $this->errors->add($i18n->get('error.format')); + return; + } + } + + // In first pass we check user logins for potential collisions with existing. if ($name == 'USER' && $this->canImport) { $login = $attrs['LOGIN']; if ('' != $attrs['STATUS'] && ttUserHelper::getUserByLogin($login)) { // We have a login collision. Append colliding login to a list of things we cannot import. - $this->conflicting_entities .= ($this->conflicting_entities ? ", $login" : $login); + $this->conflicting_logins .= ($this->conflicting_logins ? ", $login" : $login); + // The above is printed in error message with all found colliding logins. } } } @@ -112,6 +136,7 @@ class ttOrgImportHelper { } // Set parent group to create subgroups with this group as parent at next entry here. $this->current_parent_group_id = $this->current_group_id; + return; } if ($name == 'ROLES') { @@ -119,22 +144,24 @@ class ttOrgImportHelper { unset($this->currentGroupRoleMap); $this->currentGroupRoleMap = array(); // Role map is reconstructed after processing elements in XML. See below. + return; } if ($name == 'ROLE') { // We get here when processing tags for the current group. $role_id = ttRoleHelper::insert(array( - 'group_id' => $this->current_group_id, - 'org_id' => $this->org_id, - 'name' => $attrs['NAME'], - 'description' => $attrs['DESCRIPTION'], - 'rank' => $attrs['RANK'], - 'rights' => $attrs['RIGHTS'], - 'status' => $attrs['STATUS'])); + 'group_id' => $this->current_group_id, + 'org_id' => $this->org_id, + 'name' => $attrs['NAME'], + 'description' => $attrs['DESCRIPTION'], + 'rank' => $attrs['RANK'], + 'rights' => $attrs['RIGHTS'], + 'status' => $attrs['STATUS'])); if ($role_id) { // Add a mapping. $this->currentGroupRoleMap[$attrs['ID']] = $role_id; } else $this->errors->add($i18n->get('error.db')); + return; } if ($name == 'TASKS') { @@ -142,6 +169,7 @@ class ttOrgImportHelper { unset($this->currentGroupTaskMap); $this->currentGroupTaskMap = array(); // Task map is reconstructed after processing elements in XML. See below. + return; } if ($name == 'TASK') { @@ -156,6 +184,7 @@ class ttOrgImportHelper { // Add a mapping. $this->currentGroupTaskMap[$attrs['ID']] = $task_id; } else $this->errors->add($i18n->get('error.db')); + return; } if ($name == 'PROJECTS') { @@ -163,6 +192,7 @@ class ttOrgImportHelper { unset($this->currentGroupProjectMap); $this->currentGroupProjectMap = array(); // Project map is reconstructed after processing elements in XML. See below. + return; } if ($name == 'PROJECT') { @@ -184,6 +214,7 @@ class ttOrgImportHelper { // Add a mapping. $this->currentGroupProjectMap[$attrs['ID']] = $project_id; } else $this->errors->add($i18n->get('error.db')); + return; } if ($name == 'CLIENTS') { @@ -191,15 +222,18 @@ class ttOrgImportHelper { unset($this->currentGroupClientMap); $this->currentGroupClientMap = array(); // Client map is reconstructed after processing elements in XML. See below. + return; } if ($name == 'CLIENT') { // We get here when processing tags for the current group. // Prepare a list of project ids. - $projects = explode(',', $attrs['PROJECTS']); - foreach ($projects as $id) - $mapped_projects[] = $this->currentGroupProjectMap[$id]; + if ($attrs['PROJECTS']) { + $projects = explode(',', $attrs['PROJECTS']); + foreach ($projects as $id) + $mapped_projects[] = $this->currentGroupProjectMap[$id]; + } $client_id = ttClientHelper::insert(array( 'group_id' => $this->current_group_id, @@ -213,6 +247,7 @@ class ttOrgImportHelper { // Add a mapping. $this->currentGroupClientMap[$attrs['ID']] = $client_id; } else $this->errors->add($i18n->get('error.db')); + return; } if ($name == 'USERS') { @@ -220,6 +255,7 @@ class ttOrgImportHelper { unset($this->currentGroupUserMap); $this->currentGroupUserMap = array(); // User map is reconstructed after processing elements in XML. See below. + return; } if ($name == 'USER') { @@ -242,6 +278,7 @@ class ttOrgImportHelper { // Add a mapping. $this->currentGroupUserMap[$attrs['ID']] = $user_id; } else $this->errors->add($i18n->get('error.db')); + return; } if ($name == 'USER_PROJECT_BIND') { @@ -254,6 +291,194 @@ class ttOrgImportHelper { 'status' => $attrs['STATUS']))) { $this->errors->add($i18n->get('error.db')); } + return; + } + + if ($name == 'INVOICES') { + // If we get here, we have to recycle $currentGroupInvoiceMap. + unset($this->currentGroupInvoiceMap); + $this->currentGroupInvoiceMap = array(); + // Invoice map is reconstructed after processing elements in XML. See below. + return; + } + + if ($name == 'INVOICE') { + // We get here when processing tags for the current group. + $invoice_id = ttInvoiceHelper::insert(array( + 'group_id' => $this->current_group_id, + 'org_id' => $this->org_id, + 'name' => $attrs['NAME'], + 'date' => $attrs['DATE'], + 'client_id' => $this->currentGroupClientMap[$attrs['CLIENT_ID']], + 'status' => $attrs['STATUS'])); + if ($invoice_id) { + // Add a mapping. + $this->currentGroupInvoiceMap[$attrs['ID']] = $invoice_id; + } else $this->errors->add($i18n->get('error.db')); + return; + } + + if ($name == 'LOG') { + // If we get here, we have to recycle $currentGroupLogMap. + unset($this->currentGroupLogMap); + $this->currentGroupLogMap = array(); + // Log map is reconstructed after processing elements in XML. See below. + return; + } + + if ($name == 'LOG_ITEM') { + // We get here when processing tags for the current group. + $log_item_id = ttTimeHelper::insert(array( + 'user_id' => $this->currentGroupUserMap[$attrs['USER_ID']], + 'group_id' => $this->current_group_id, + 'org_id' => $this->org_id, + 'date' => $attrs['DATE'], + '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'] : ''), + 'billable' => $attrs['BILLABLE'], + 'paid' => $attrs['PAID'], + 'status' => $attrs['STATUS'])); + if ($log_item_id) { + // Add a mapping. + $this->currentGroupLogMap[$attrs['ID']] = $log_item_id; + } else $this->errors->add($i18n->get('error.db')); + return; + } + + if ($name == 'CUSTOM_FIELDS') { + // If we get here, we have to recycle $currentGroupCustomFieldMap. + unset($this->currentGroupCustomFieldMap); + $this->currentGroupCustomFieldMap = array(); + // Custom field map is reconstructed after processing elements in XML. See below. + return; + } + + if ($name == 'CUSTOM_FIELD') { + // We get here when processing tags for the current group. + $custom_field_id = ttCustomFieldHelper::insertField(array( + 'group_id' => $this->current_group_id, + // 'org_id' => $this->org_id, TODO: add this when org_id field is added to the table. + 'type' => $attrs['TYPE'], + 'label' => $attrs['LABEL'], + 'required' => $attrs['REQUIRED'], + 'status' => $attrs['STATUS'])); + if ($custom_field_id) { + // Add a mapping. + $this->currentGroupCustomFieldMap[$attrs['ID']] = $custom_field_id; + } else $this->errors->add($i18n->get('error.db')); + return; + } + + if ($name == 'CUSTOM_FIELD_OPTIONS') { + // If we get here, we have to recycle $currentGroupCustomFieldOptionMap. + unset($this->currentGroupCustomFieldOptionMap); + $this->currentGroupCustomFieldOptionMap = array(); + // Custom field option map is reconstructed after processing elements in XML. See below. + return; + } + + if ($name == 'CUSTOM_FIELD_OPTION') { + // We get here when processing 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. + 'field_id' => $this->currentGroupCustomFieldMap[$attrs['FIELD_ID']], + 'value' => $attrs['VALUE'])); + if ($custom_field_option_id) { + // Add a mapping. + $this->currentGroupCustomFieldOptionMap[$attrs['ID']] = $custom_field_option_id; + } else $this->errors->add($i18n->get('error.db')); + return; + } + + if ($name == 'CUSTOM_FIELD_LOG_ENTRY') { + // We get here when processing 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. + 'log_id' => $this->currentGroupLogMap[$attrs['LOG_ID']], + 'field_id' => $this->currentGroupCustomFieldMap[$attrs['FIELD_ID']], + 'option_id' => $this->currentGroupCustomFieldOptionMap[$attrs['OPTION_ID']], + 'value' => $attrs['VALUE'], + 'status' => $attrs['STATUS']))) { + $this->errors->add($i18n->get('error.db')); + } + return; + } + + if ($name == 'EXPENSE_ITEM') { + // We get here when processing tags for the current group. + $expense_item_id = ttExpenseHelper::insert(array( + 'date' => $attrs['DATE'], + 'user_id' => $this->currentGroupUserMap[$attrs['USER_ID']], + 'group_id' => $this->current_group_id, + // 'org_id' => $this->org_id, TODO: add this when org_id field is added to the table. + 'client_id' => $this->currentGroupClientMap[$attrs['CLIENT_ID']], + 'project_id' => $this->currentGroupProjectMap[$attrs['PROJECT_ID']], + 'name' => $attrs['NAME'], + 'cost' => $attrs['COST'], + 'invoice_id' => $this->currentGroupInvoiceMap[$attrs['INVOICE_ID']], + 'paid' => $attrs['PAID'], + 'status' => $attrs['STATUS'])); + if (!$expense_item_id) $this->errors->add($i18n->get('error.db')); + return; + } + + if ($name == 'MONTHLY_QUOTA') { + if (!$this->insertMonthlyQuota($this->current_group_id, + // 'org_id' => $this->org_id, TODO: add this when org_id field is added to the table. + $attrs['YEAR'], + $attrs['MONTH'], + $attrs['MINUTES'])) { + $this->errors->add($i18n->get('error.db')); + } + return; + } + + if ($name == 'FAV_REPORT') { + $user_list = ''; + if (strlen($attrs['USERS']) > 0) { + $arr = explode(',', $attrs['USERS']); + foreach ($arr as $v) + $user_list .= (strlen($user_list) == 0 ? '' : ',').$this->currentGroupUserMap[$v]; + } + $fav_report_id = ttFavReportHelper::insertReport(array( + 'name' => $attrs['NAME'], + 'user_id' => $this->currentGroupUserMap[$attrs['USER_ID']], + 'client' => $this->currentGroupClientMap[$attrs['CLIENT_ID']], + 'option' => $this->currentGroupCustomFieldOptionMap[$attrs['CF_1_OPTION_ID']], + 'project' => $this->currentGroupProjectMap[$attrs['PROJECT_ID']], + 'task' => $this->currentGroupTaskMap[$attrs['TASK_ID']], + 'billable' => $attrs['BILLABLE'], + 'users' => $user_list, + 'period' => $attrs['PERIOD'], + 'from' => $attrs['PERIOD_START'], + 'to' => $attrs['PERIOD_END'], + 'chclient' => (int) $attrs['SHOW_CLIENT'], + 'chinvoice' => (int) $attrs['SHOW_INVOICE'], + 'chpaid' => (int) $attrs['SHOW_PAID'], + 'chip' => (int) $attrs['SHOW_IP'], + 'chproject' => (int) $attrs['SHOW_PROJECT'], + 'chstart' => (int) $attrs['SHOW_START'], + 'chduration' => (int) $attrs['SHOW_DURATION'], + 'chcost' => (int) $attrs['SHOW_COST'], + 'chtask' => (int) $attrs['SHOW_TASK'], + 'chfinish' => (int) $attrs['SHOW_END'], + 'chnote' => (int) $attrs['SHOW_NOTE'], + 'chcf_1' => (int) $attrs['SHOW_CUSTOM_FIELD_1'], + 'chunits' => (int) $attrs['SHOW_WORK_UNITS'], + 'group_by1' => $attrs['GROUP_BY1'], + 'group_by2' => $attrs['GROUP_BY2'], + 'group_by3' => $attrs['GROUP_BY3'], + 'chtotalsonly' => (int) $attrs['SHOW_TOTALS_ONLY'])); + if (!$fav_report_id) $this->errors->add($i18n->get('error.db')); + return; } } } @@ -295,13 +520,10 @@ class ttOrgImportHelper { xml_set_element_handler($parser, 'startElement', false); // We need to parse the file 2 times: - // 1) First pass: determine if import is possible - there must be no login collisions. - // 2) Second pass: if we can import, then do import in a second pass. - // This is different from earlier approach for single group import, where we could - // do both things in one pass because user info was in the beginning of XML file. - // Now, with subgroups, users can be located anywhere in the file. + // 1) First pass: determine if import is possible. + // 2) Second pass: import data, one tag at a time. - // Read and parse the content of the file. During parsing, startElement, endElement, and dataElement functions are called. + // Read and parse the content of the file. During parsing, startElement is called back for each tag. $file = fopen($filename, 'r'); while ($data = fread($file, 4096)) { if (!xml_parse($parser, $data, feof($file))) { @@ -310,10 +532,10 @@ class ttOrgImportHelper { xml_error_string(xml_get_error_code($parser)))); } } - if ($this->conflicting_entities) { + if ($this->conflicting_logins) { $this->canImport = false; $this->errors->add($i18n->get('error.user_exists')); - $this->errors->add(sprintf($i18n->get('error.cannot_import'), $this->conflicting_entities)); + $this->errors->add(sprintf($i18n->get('error.cannot_import'), $this->conflicting_logins)); } $this->firstPass = false; // We are done with 1st pass. @@ -330,7 +552,7 @@ class ttOrgImportHelper { xml_set_object($parser, $this); xml_set_element_handler($parser, 'startElement', false); - // Read and parse the content of the file. During parsing, startElement, endElement, and dataElement functions are called. + // Read and parse the content of the file. During parsing, startElement is called back for each tag. $file = fopen($filename, 'r'); while ($data = fread($file, 4096)) { if (!xml_parse($parser, $data, feof($file))) { @@ -415,4 +637,12 @@ class ttOrgImportHelper { $group_id = $mdb2->lastInsertID('tt_groups', 'id'); return $group_id; } + + // insertMonthlyQuota - a helper function to insert a monthly quota. + private function insertMonthlyQuota($group_id, $year, $month, $minutes) { + $mdb2 = getConnection(); + $sql = "INSERT INTO tt_monthly_quotas (group_id, year, month, minutes) values ($group_id, $year, $month, $minutes)"; + $affected = $mdb2->exec($sql); + return (!is_a($affected, 'PEAR_Error')); + } }