OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
820
app/Domain/Connector/Services/Connector.php
Normal file
820
app/Domain/Connector/Services/Connector.php
Normal file
@@ -0,0 +1,820 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Connector\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Leantime\Domain\Auth\Models\Roles;
|
||||
use Leantime\Domain\Goalcanvas\Repositories\Goalcanvas;
|
||||
use Leantime\Domain\Ideas\Repositories\Ideas;
|
||||
use Leantime\Domain\Projects\Services\Projects;
|
||||
use Leantime\Domain\Tickets\Services\Tickets;
|
||||
use Leantime\Domain\Users\Services\Users;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
class Connector
|
||||
{
|
||||
public function __construct(
|
||||
private Users $userService,
|
||||
private Projects $projectService,
|
||||
private Tickets $ticketService,
|
||||
private \Leantime\Domain\Tickets\Repositories\Tickets $ticketRepository,
|
||||
private Goalcanvas $goalCanvasRepo,
|
||||
private Ideas $ideaRepo,
|
||||
) {}
|
||||
|
||||
public function getEntityFlags($entity)
|
||||
{
|
||||
$flags = [];
|
||||
if ($entity == 'tickets') {
|
||||
$flags[] = 'If you do not have an Editor (User) email/ID field then all imported entities will be assigned to you.';
|
||||
$flags[] = 'Headline and Project are required fields.';
|
||||
} else {
|
||||
if ($entity == 'projects') {
|
||||
$flags[] = 'If there are no Assigned Users, the project will be assigned to you.';
|
||||
$flags[] = 'If there are more than one assigned users, ensure that it is a comma separated list.';
|
||||
$flags[] = 'If you decide to set permissions settings they have to be one of the following types: all, clients or restricted.';
|
||||
$flags[] = 'Project Name and Client Id re required fields';
|
||||
} else {
|
||||
if ($entity == 'users') {
|
||||
// TODO add flags for users
|
||||
$flags[] = 'First Name, Role, Email, and Send Invite are required fields';
|
||||
$flags[] = 'The Send Invite field should be either Yes/No or else the user will not be imported. If Send Invite is set to No the user will have to reset the password on their own.';
|
||||
$flags[] = "Roles have to be one of the following values 'readonly', 'commenter', 'editor', 'manager', 'admin', 'owner'.";
|
||||
} else {
|
||||
if ($entity == 'ideas') {
|
||||
// TODO add flags for ideas
|
||||
$flags[] = 'Description, Data, and CanvasId are required fields.';
|
||||
$flags[] = 'If you do not have an Author field then you will be assigned as the Author.';
|
||||
} else {
|
||||
if ($entity == 'goals') {
|
||||
// TODO add flags for goals
|
||||
$flags[] = 'Title, CanvasId, Start Value, Current Value, and End Value are required fields';
|
||||
} else {
|
||||
if ($entity == 'milestones') {
|
||||
// TODO add flags for milestones
|
||||
$flags[] = 'If you do not have an Editor (User) email/ID field then all imported entities will be assigned to you.';
|
||||
$flags[] = 'Headline, Edit From, Edit To and Project are required fields';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $flags;
|
||||
}
|
||||
|
||||
public function getFieldMappings($postParams)
|
||||
{
|
||||
$fields = [];
|
||||
foreach ($postParams as $fieldmapping) {
|
||||
// Checking if the field mapping is selected
|
||||
if (
|
||||
! empty($fieldmapping)
|
||||
&& strpos($fieldmapping, '|') !== false
|
||||
) {
|
||||
$mappingParts = explode('|', $fieldmapping);
|
||||
$sourceField = $mappingParts[0];
|
||||
$leantimeField = $mappingParts[1];
|
||||
|
||||
$fields[] = ['sourceField' => $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]);
|
||||
}
|
||||
}
|
||||
207
app/Domain/Connector/Services/Integrations.php
Normal file
207
app/Domain/Connector/Services/Integrations.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Connector\Services;
|
||||
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Leantime\Core\Auth\Permissions\RequiresPermission;
|
||||
use Leantime\Domain\Connector\Models\Integration as IntegrationModel;
|
||||
use Leantime\Domain\Connector\Permissions\ConnectorPermissions;
|
||||
use Leantime\Domain\Connector\Repositories\Integrations as IntegrationsRepo;
|
||||
use Leantime\Domain\Connector\Repositories\LeantimeEntities;
|
||||
|
||||
class Integrations
|
||||
{
|
||||
private IntegrationsRepo $integrationRepo;
|
||||
|
||||
private LeantimeEntities $leantimeEntities;
|
||||
|
||||
/**
|
||||
* Initializes the service dependencies.
|
||||
*/
|
||||
public function __construct(IntegrationsRepo $integrationRepo, LeantimeEntities $leantimeEntities)
|
||||
{
|
||||
$this->integrationRepo = $integrationRepo;
|
||||
$this->leantimeEntities = $leantimeEntities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single integration by id.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param int $id Integration id
|
||||
* @return object|array|false The integration record or false when not found
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function get(int $id): object|array|false
|
||||
{
|
||||
return $this->integrationRepo->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a ticket from an integration. Not yet implemented.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function updateTicket(object|array $object): bool
|
||||
{
|
||||
// TODO: Implement update() method.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new integration record.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function create(object|array $object): int|false
|
||||
{
|
||||
return $this->integrationRepo->insert($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an integration. Not yet implemented.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
// TODO: Implement delete() method.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all integrations matching the given search params. Not yet implemented.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function getAll(?array $searchparams = null): array|false
|
||||
{
|
||||
// TODO: Implement getAll() method.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches an integration record with the given values.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param int $id Integration id
|
||||
* @param array $params Column => value pairs to update
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function patch(int $id, array $params): bool
|
||||
{
|
||||
return $this->integrationRepo->patch($id, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Leantime entities available as import targets.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @return array Map of entity key => entity definition
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function getAvailableEntities(): array
|
||||
{
|
||||
return $this->leantimeEntities->availableLeantimeEntities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available field definitions for a given Leantime entity.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $entity Entity key (e.g. tickets, projects, users)
|
||||
* @return array Map of field key => field definition
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function getEntityFields(string $entity): array
|
||||
{
|
||||
return $this->leantimeEntities->availableLeantimeEntities[$entity]['fields'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the import entity for the fields-mapping step.
|
||||
*
|
||||
* Encapsulates the request/session fallback chain: prefers the submitted
|
||||
* leantimeEntities value (persisting it to the session), otherwise falls
|
||||
* back to the previously stored session entity. When an entity is resolved
|
||||
* it hydrates the given integration model and patches the integration record.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param array $request Request parameters (expects optional 'leantimeEntities')
|
||||
* @param IntegrationModel $currentIntegration Integration model to hydrate with the resolved entity
|
||||
* @return string|null The resolved entity key, or null when no entity could be determined
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function resolveImportEntity(array $request, IntegrationModel $currentIntegration): ?string
|
||||
{
|
||||
if (isset($request['leantimeEntities'])) {
|
||||
$entity = $request['leantimeEntities'];
|
||||
session(['currentImportEntity' => $entity]);
|
||||
} elseif (session()->exists('currentImportEntity') && session('currentImportEntity') != '') {
|
||||
$entity = session('currentImportEntity');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$currentIntegration->entity = $entity;
|
||||
|
||||
$this->patch($currentIntegration->id, ['entity' => $entity]);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the provider fields used in the fields-mapping step.
|
||||
*
|
||||
* Uses the persisted integration fields when present, otherwise falls back
|
||||
* to the live provider fields.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param IntegrationModel $currentIntegration Integration model that may carry stored fields
|
||||
* @param object $provider Provider instance exposing getFields()
|
||||
* @return array List of provider field identifiers
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function resolveProviderFields(IntegrationModel $currentIntegration, object $provider): array
|
||||
{
|
||||
if (isset($currentIntegration->fields) && $currentIntegration->fields != '') {
|
||||
return explode(',', $currentIntegration->fields);
|
||||
}
|
||||
|
||||
return $provider->getFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the cached, serialized import payload (fields + values) from the session.
|
||||
*
|
||||
* Pairs with Connector::cacheSerializedFieldValues() which writes the keys.
|
||||
* Uses safe_unserialize() to avoid object injection.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @return array{values: array, fields: array} The decoded values and field mappings
|
||||
*/
|
||||
#[RequiresPermission(ConnectorPermissions::MANAGE, global: true)]
|
||||
public function getCachedImportPayload(): array
|
||||
{
|
||||
return [
|
||||
'values' => safe_unserialize(session('serValues'), []),
|
||||
'fields' => safe_unserialize(session('serFields'), []),
|
||||
];
|
||||
}
|
||||
}
|
||||
18
app/Domain/Connector/Services/ProviderIntegration.php
Normal file
18
app/Domain/Connector/Services/ProviderIntegration.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Connector\Services;
|
||||
|
||||
use Leantime\Domain\Connector\Models\Entity;
|
||||
|
||||
interface ProviderIntegration
|
||||
{
|
||||
public function connect(): mixed;
|
||||
|
||||
public function sync(Entity $entity): mixed;
|
||||
|
||||
public function getFields(): mixed;
|
||||
|
||||
public function getEntities(): mixed;
|
||||
|
||||
public function getValues(Entity $entity): mixed;
|
||||
}
|
||||
46
app/Domain/Connector/Services/Providers.php
Normal file
46
app/Domain/Connector/Services/Providers.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Connector\Services;
|
||||
|
||||
use Leantime\Core\Events\DispatchesEvents;
|
||||
use Leantime\Domain\Connector\Models\Provider;
|
||||
|
||||
class Providers
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
private array $providers = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->loadProviders();
|
||||
}
|
||||
|
||||
public function loadProviders(): void
|
||||
{
|
||||
|
||||
// Default Providers
|
||||
$provider = app()->make(\Leantime\Domain\CsvImport\Services\CsvImport::class);
|
||||
$this->providers[$provider->id] = $provider;
|
||||
|
||||
// providerId => provider
|
||||
$this->providers = self::dispatch_filter('providerList', $this->providers);
|
||||
}
|
||||
|
||||
public function getProviders(): array
|
||||
{
|
||||
return $this->providers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getProvider($providerId): object
|
||||
{
|
||||
if (isset($this->providers[$providerId])) {
|
||||
return $this->providers[$providerId];
|
||||
} else {
|
||||
throw new \Exception('Provider does not exist');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user