OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)

This commit is contained in:
wangruiguo
2026-09-03 18:49:20 +08:00
commit d647428529
3501 changed files with 1988906 additions and 0 deletions

View 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]);
}
}

View 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'), []),
];
}
}

View 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;
}

View 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');
}
}
}