$sourceField, 'leantimeField' => $leantimeField]; } } return $fields; } public function parseValues($fields, $values, $entity) { if ($entity == 'tickets') { return $this->parseTickets($fields, $values); } elseif ($entity == 'projects') { return $this->parseProjects($fields, $values); } elseif ($entity == 'users') { return $this->parseUsers($fields, $values); } elseif ($entity == 'ideas') { return $this->parseIdeas($fields, $values); } elseif ($entity == 'goals') { return $this->parseGoals($fields, $values); } elseif ($entity == 'milestones') { return $this->parseMilestones($fields, $values); } return false; } private function parseTickets($fields, $values) { $matchingSourceField = ''; $flags = []; foreach ($fields as $item) { if ($item['leantimeField'] === 'editorId') { $matchingSourceField = $item['sourceField']; break; } } $headlineFlag = true; foreach ($fields as $item) { if ($item['leantimeField'] === 'headline') { $headlineFlag = false; break; } } if ($headlineFlag) { $flags[] = 'You must have a headline column'; } if ($matchingSourceField) { foreach ($values as &$row) { if (strpos($row[$matchingSourceField], '@') !== false) { $id = false; if (isset($row[$matchingSourceField])) { $id = $this->userService->getUserByEmail(trim($row[$matchingSourceField]))['id'] ?? false; } if ($id) { $row['editorId'] = $id; } else { $flags[] = $row[$matchingSourceField].' '.'is not a valid User'; } } else { $flags[] = 'The Author/userId column must contain only valid User emails'; break; } } } else { $id = session('userdata.id'); foreach ($values as &$row) { $row['editorId'] = $id; } } // PROJECT Name Check $matchingProjectSourceField = ''; foreach ($fields as $item) { if ($item['leantimeField'] === 'projectName') { $matchingProjectSourceField = $item['sourceField']; break; } } if ($matchingProjectSourceField) { $allProjects = $this->projectService->getAllProjects(); foreach ($values as &$row) { $projectId = $this->projectService->getProjectIdByName( $allProjects, $row[$matchingProjectSourceField] ); if (! $projectId) { $flags[] = $row[$matchingProjectSourceField].' '.'is not a valid Project'; } else { $row['projectId'] = $projectId; $statusLabels = $this->ticketService->getStatusLabels($projectId); } } } else { $flags[] = 'You must have a column matching to a valid Project.'; } // Status Field Mapping $matchingStatusField = ''; foreach ($fields as $item) { if ($item['leantimeField'] === 'status') { $matchingStatusField = $item['sourceField']; break; } } if ($matchingStatusField) { foreach ($values as &$row) { $getStatus = $this->ticketRepository->getStatusIdByName($row[$matchingStatusField], $row['projectId'] ?? null); if ($getStatus !== false) { $row['status'] = $getStatus; } else { $row['status'] = 3; } } } // Status Field Mapping $matchingDateField = ''; $dateArray = ['date', 'dateToFinish', 'editFrom', 'editTo']; foreach ($fields as $item) { if (in_array($item['leantimeField'], $dateArray)) { $matchingDateField = $item['sourceField']; break; } } if ($matchingDateField) { foreach ($values as &$row) { if ($row[$matchingDateField] !== '') { try { dtHelper()->parseUserDateTime($row[$matchingDateField]); } catch (\Exception $e) { $flags[] = $matchingDateField.': '.$row[$matchingDateField].' '.'is not a valid date. Please use the date format defined in your profile or iso8601'; } } } } // Storypoints/Effort Field Validation // Only a fixed set of values is renderable (see Tickets repository efforts). // An out-of-range value (e.g. "4") imports fine but later crashes ticket/dashboard views. $matchingStorypointsField = ''; foreach ($fields as $item) { if ($item['leantimeField'] === 'storypoints') { $matchingStorypointsField = $item['sourceField']; break; } } if ($matchingStorypointsField) { // Cast keys to strings: PHP coerces numeric-string array keys (e.g. '1') // to ints, which would break the strict comparison below against the // string CSV value. $validStorypoints = array_map('strval', array_keys($this->ticketService->getEffortLabels())); foreach ($values as &$row) { $storypoints = trim((string) ($row[$matchingStorypointsField] ?? '')); if ($storypoints !== '' && ! in_array($storypoints, $validStorypoints, true)) { $flags[] = $matchingStorypointsField.': '.$storypoints.' '.'is not a valid story point value. Use one of: '.implode(', ', $validStorypoints); } } } $this->cacheSerializedFieldValues($fields, $values); return $flags; } private function parseProjects($fields, $values) { $matchingProjectNameSourceField = ''; $matchingClientIdSourceField = ''; $matchingUsersSourceField = ''; $flags = []; foreach ($fields as $item) { if ($item['leantimeField'] === 'name') { $matchingProjectNameSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'clientId') { $matchingClientIdSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'assignedUsers') { $matchingUsersSourceField = $item['sourceField']; } } if ($matchingUsersSourceField) { foreach ($values as $row) { $emails = explode(',', $row[$matchingUsersSourceField]); $users = []; foreach ($emails as $email) { $user = $this->userService->getUserByEmail(trim($email)); if ($user) { $users[] = $user; } else { $flags[] = $email.' is not a valid user.'; } } $row[$matchingUsersSourceField] = $users; } } if (! $matchingProjectNameSourceField) { $flags[] = 'You must have a column with Project Names'; } if (! $matchingClientIdSourceField) { $flags[] = 'You must have a column with ClientId'; } $this->cacheSerializedFieldValues($fields, $values); return $flags; } private function parseUsers($fields, $values) { $matchingUsernameSourceField = ''; $matchingRoleSourceField = ''; $matchingSendInviteSourceField = ''; $matchingFirstNameSourceField = ''; $flags = []; foreach ($fields as $item) { if ($item['leantimeField'] === 'firstname') { $matchingFirstNameSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'user') { $matchingUsernameSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'role') { $matchingRoleSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'sendInvite') { $matchingSendInviteSourceField = $item['sourceField']; } } if ($matchingSendInviteSourceField && $matchingUsernameSourceField && $matchingRoleSourceField && $matchingFirstNameSourceField) { foreach ($values as &$row) { if (strtolower($row[$matchingSendInviteSourceField]) == 'yes') { $row['sendInvite'] = true; } elseif (strtolower($row[$matchingSendInviteSourceField]) == 'no') { $row['sendInvite'] = false; } else { $flags[] = 'The sendInvite column must contain only yes or no'; } if (str_contains($row[$matchingUsernameSourceField], '@')) { $user = $this->userService->getUserByEmail( email: trim($row[$matchingUsernameSourceField]), status: '' ); if ($user) { $row['id'] = $user['id']; } } else { $flags[] = 'The Username column must contain only valid emails'; } $rolesKey = array_search( strtolower(trim($row[$matchingRoleSourceField])), Roles::getRoles() ); if (! $rolesKey) { $flags[] = $row[$matchingRoleSourceField].' is not a valid role.'; } else { $row[$matchingRoleSourceField] = $rolesKey; } } } if (! $matchingSendInviteSourceField) { $flags[] = 'You must have a column specifying if the user should receive an invite.'; } if (! $matchingUsernameSourceField) { $flags[] = 'You must have a column with Emails.'; } if (! $matchingRoleSourceField) { $flags[] = 'You must have a column with Roles.'; } if (! $matchingFirstNameSourceField) { $flags[] = 'You must have a column with First Names.'; } $this->cacheSerializedFieldValues($fields, $values); return $flags; } private function parseIdeas($fields, $values) { $matchingAuthorSourceField = ''; $matchingCanvasIdSourceField = ''; $matchingDataSourceField = ''; $matchingDescriptionSourceField = ''; $flags = []; foreach ($fields as $item) { if ($item['leantimeField'] === 'author') { $matchingAuthorSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'canvasId') { $matchingCanvasIdSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'description') { $matchingDescriptionSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'data') { $matchingDataSourceField = $item['sourceField']; } } if ($matchingAuthorSourceField) { foreach ($values as &$row) { if (str_contains($row[$matchingAuthorSourceField], '@')) { $id = $this->userService->getUserByEmail( $row[$matchingAuthorSourceField] )['id']; if ($id) { $row['author'] = $id; } else { $flags[] = $row[$matchingAuthorSourceField].' '.'is not a valid User'; } } else { $flags[] = 'The Author column must contain only valid User emails'; break; } } } else { $id = session('userdata.id'); foreach ($values as &$row) { $row['author'] = $id; } } if ($matchingCanvasIdSourceField) { foreach ($values as &$row) { if ( ! $this->ideaRepo->getSingleCanvas( $row[$matchingCanvasIdSourceField] ) ) { $flags[] = $row[$matchingCanvasIdSourceField].' '.'is not a valid Canvas.'; } } } if (! $matchingDataSourceField) { $flags[] = 'You must have a column matching to Data.'; } if (! $matchingDescriptionSourceField) { $flags[] = 'You must have a column matching to Description.'; } if (! $matchingCanvasIdSourceField) { $flags[] = 'You must have a column matching to CanvasId.'; } $this->cacheSerializedFieldValues($fields, $values); return $flags; } private function parseGoals($fields, $values) { // TODO add import logic and validation $matchingStartValueSourceField = ''; $matchingCurrentValueSourceField = ''; $matchingCanvasIdSourceField = ''; $matchingEndValueSourceField = ''; $matchingTitleSourceField = ''; $flags = []; foreach ($fields as $item) { if ($item['leantimeField'] === 'title') { $matchingTitleSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'canvasId') { $matchingCanvasIdSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'currentValue') { $matchingCurrentValueSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'startValue') { $matchingStartValueSourceField = $item['sourceField']; } if ($item['leantimeField'] === 'endValue') { $matchingEndValueSourceField = $item['sourceField']; } } if ($matchingCanvasIdSourceField) { foreach ($values as &$row) { if ( ! $this->goalCanvasRepo->getSingleCanvas( $row[$matchingCanvasIdSourceField] ) ) { $flags[] = $row[$matchingCanvasIdSourceField].' '.'is not a valid Canvas.'; } } } if (! $matchingTitleSourceField) { $flags[] = 'You must have a column matching to Title.'; } if (! $matchingCanvasIdSourceField) { $flags[] = 'You must have a column matching to CanvasId.'; } if (! $matchingCurrentValueSourceField) { $flags[] = 'You must have a column matching to CurrentValue.'; } if (! $matchingStartValueSourceField) { $flags[] = 'You must have a column matching to StartValue.'; } if (! $matchingEndValueSourceField) { $flags[] = 'You must have a column matching to EndValue.'; } $this->cacheSerializedFieldValues($fields, $values); return $flags; } private function parseMilestones($fields, $values) { $matchingSourceField = ''; $flags = []; foreach ($fields as $item) { if ($item['leantimeField'] === 'editorId') { $matchingSourceField = $item['sourceField']; break; } } $headlineFlag = true; foreach ($fields as $item) { if ($item['leantimeField'] === 'headline') { $headlineFlag = false; break; } } if ($headlineFlag) { $flags[] = 'You must have a headline column'; } if ($matchingSourceField) { foreach ($values as &$row) { if (str_contains($row[$matchingSourceField], '@')) { $id = $this->userService->getUserByEmail( $row[$matchingSourceField] )['id']; if ($id) { $row['editorId'] = $id; } else { $flags[] = $row[$matchingSourceField].' '.'is not a valid User'; } } else { $flags[] = 'The Author/userId column must contain only valid User emails'; break; } } } else { $id = session('userdata.id'); foreach ($values as &$row) { $row['editorId'] = $id; } } // PROJECT Name Check $matchingProjectSourceField = ''; foreach ($fields as $item) { if ($item['leantimeField'] === 'projectName') { $matchingProjectSourceField = $item['sourceField']; break; } } if ($matchingProjectSourceField) { $allProjects = $this->projectService->getAllProjects(); foreach ($values as &$row) { $projectId = $this->projectService->getProjectIdByName( $allProjects, $row[$matchingProjectSourceField] ); if (! $projectId) { $flags[] = $row[$matchingProjectSourceField].' '.'is not a valid Project'; } // else { $row['projectId'] = $projectId; } } } else { $flags[] = 'You must have a column matching to a valid Project.'; } $matchingEndDateSourceField = ''; $matchingStartDateSourceField = ''; foreach ($fields as $field) { if ($field['leantimeField'] === 'editFrom') { $matchingStartDateSourceField = $field['sourceField']; } if ($field['leantimeField'] === 'editTo') { $matchingEndDateSourceField = $field['sourceField']; } } if (! $matchingEndDateSourceField) { $flags[] = 'You must have a column matching to a valid Edit To.'; } if (! $matchingStartDateSourceField) { $flags[] = 'You must have a column matching to a valid Edit From.'; } $this->cacheSerializedFieldValues($fields, $values); return $flags; } public function importValues($fields, $values, $entity) { $finalMappings = []; foreach ($fields as $field) { array_push($finalMappings, $field['sourceField']); array_push($finalMappings, $field['leantimeField']); } if ($entity == 'tickets') { return $this->importTickets($finalMappings, $values); } elseif ($entity == 'projects') { return $this->importProjects($finalMappings, $values); } elseif ($entity == 'users') { return $this->importUsers($finalMappings, $values); } elseif ($entity == 'ideas') { return $this->importIdeas($finalMappings, $values); } elseif ($entity == 'goals') { return $this->importGoals($finalMappings, $values); } elseif ($entity == 'milestones') { return $this->importMilestones($finalMappings, $values); } return false; } private function importTickets($finalMappings, $finalValues) { foreach ($finalValues as $row) { $ticket = []; for ($i = 0; $i < count($finalMappings); $i = $i + 2) { $ticket[$finalMappings[$i + 1]] = $row[$finalMappings[$i]]; } $ticket['editorId'] = $row['editorId'] ?? ''; $ticket['projectId'] = $row['projectId'] ?? ''; $ticket['status'] = $row['status'] ?? 3; $ticket['type'] = $row['type'] ?? 'task'; try { $ticket['date'] = dtHelper()->parseUserDateTime(trim($ticket['date']))->formatDateForUser(); } catch (\Exception $e) { $ticket['date'] = dtHelper()->userNow()->formatDateForUser(); } try { $ticket['dateToFinish'] = dtHelper()->parseUserDateTime(trim($ticket['dateToFinish']))->formatDateForUser(); } catch (\Exception $e) { $ticket['dateToFinish'] = ''; } try { $ticket['editFrom'] = dtHelper()->parseUserDateTime(trim($ticket['dateToFinish']))->formatDateForUser(); } catch (\Exception $e) { $ticket['editFrom'] = ''; } try { $ticket['editTo'] = dtHelper()->parseUserDateTime(trim($ticket['dateToFinish']))->formatDateForUser(); } catch (\Exception $e) { $ticket['editTo'] = ''; } try { if (isset($ticket['id']) && is_numeric($ticket['id'])) { $this->ticketService->updateTicket($ticket); } else { $this->ticketService->addTicket($ticket); } } catch (\Exception $e) { Log::error($e); return $e->getMessage(); } } return true; } private function importProjects($finalMappings, $finalValues) { $psettings = ['all', 'clients', 'restricted']; foreach ($finalValues as $row) { $values = []; for ($i = 0; $i < count($finalMappings); $i = $i + 2) { if ($finalMappings[$i + 1] == 'psettings') { if (! in_array($row[$finalMappings[$i]], $psettings)) { $row[$finalMappings[$i]] = 'restricted'; } } $values[$finalMappings[$i + 1]] = $row[$finalMappings[$i]]; } try { if (isset($values['id']) && is_numeric($values['id'])) { $this->projectService->editProject($values, $values['id']); } else { $this->projectService->addProject($values); } } catch (\Exception $e) { Log::error($e); return $e->getMessage(); } } return true; } private function importUsers($finalMappings, $finalValues) { // TODO add users foreach ($finalValues as $row) { $values = []; for ($i = 0; $i < count($finalMappings); $i = $i + 2) { if ($finalMappings[$i + 1] != 'sendInvite' || $finalMappings[$i + 1] != 'id') { $values[$finalMappings[$i + 1]] = $row[$finalMappings[$i]]; } } $values['notifications'] = 1; $values['source'] = 'csvImport'; // TODO: will have to change when other integrations are added if (isset($row['id']) && $row['id'] > 0) { $currentUser = $this->userService->getUser($row['id']); $currentUser['user'] = $values['user']; foreach ($currentUser as $key => &$userValues) { if (isset($values[$key])) { $userValues = $values[$key]; } } $this->userService->editUser($currentUser, $row['id']); } elseif (isset($row['sendInvite']) && $row['sendInvite'] == true) { $this->userService->createUserInvite($values); } else { $values['status'] = 'a'; if (! isset($values['password'])) { $tempPasswordVar = Uuid::uuid4()->toString(); $values['password'] = $tempPasswordVar; } $this->userService->addUser($values); } } } private function importIdeas($finalMappings, $finalValues) { foreach ($finalValues as $row) { $values = []; for ($i = 0; $i < count($finalMappings); $i = $i + 2) { $values[$finalMappings[$i + 1]] = $row[$finalMappings[$i]]; } if (isset($values['itemId'])) { $this->ideaRepo->editCanvasItem($values); } else { $this->ideaRepo->addCanvasItem($values); } } } private function importGoals($finalMappings, $finalValues) { foreach ($finalValues as $row) { $values = []; for ($i = 0; $i < count($finalMappings); $i = $i + 2) { $values[$finalMappings[$i + 1]] = $row[$finalMappings[$i]]; } $values['box'] = 'goal'; if (! isset($values['author'])) { $values['author'] = session('userdata.id'); } if (isset($values['itemId'])) { $this->goalCanvasRepo->editCanvasItem($values); } else { $this->goalCanvasRepo->addCanvasItem($values); } } } private function importMilestones($finalMappings, $finalValues) { // TODO add milestones foreach ($finalValues as $row) { $ticket = []; for ($i = 0; $i < count($finalMappings); $i = $i + 2) { $ticket[$finalMappings[$i + 1]] = $row[$finalMappings[$i]]; } $ticket['editorId'] = $row['editorId']; $ticket['projectId'] = $row['projectId']; if (! isset($ticket['status'])) { $ticket['status'] = 3; } $ticket['type'] = 'milestone'; if (isset($ticket['id'])) { $this->ticketService->updateTicket($ticket); } else { $this->ticketService->addTicket($ticket); } } } private function cacheSerializedFieldValues($fields, $values) { $serializedFields = serialize($fields); $serializedValues = serialize($values); session(['serFields' => $serializedFields]); session(['serValues' => $serializedValues]); } }