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,55 @@
<?php
namespace Leantime\Domain\CsvImport\Controllers;
use League\Csv\Exception;
use Leantime\Core\Controller\Controller;
use Leantime\Domain\Auth\Models\Roles;
use Leantime\Domain\Auth\Services\Auth;
use Leantime\Domain\CsvImport\Services\CsvImport as CsvImportService;
use Symfony\Component\HttpFoundation\Response;
/**
* upload controller for csvImport plugin
*/
class Upload extends Controller
{
private CsvImportService $providerService;
/**
* constructor - initialize private variables
*/
public function init(CsvImportService $providerService): void
{
Auth::authOrRedirect([Roles::$owner, Roles::$admin, Roles::$manager, Roles::$editor]);
$this->providerService = $providerService;
}
/**
* get - display upload form
*
* @throws \Exception
* @throws \Exception
*/
public function get(): Response
{
return $this->tpl->displayPartial('csvImport.upload');
}
/**
* post - process uploaded file
*/
public function post(array $params): Response
{
$file = $this->incomingRequest->file('file');
try {
$id = $this->providerService->processUpload($file);
} catch (Exception $e) {
return $this->tpl->displayJson(json_encode(['error' => $e->getMessage()]), 500);
}
return $this->tpl->displayJson(json_encode(['id' => $id]));
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Leantime\Domain\CsvImport\Listeners;
use Leantime\Domain\CsvImport\Services;
/**
* Class AddCSVImportProvider
*
* The AddCSVImportProvider class is responsible for adding a CSV import provider to the given payload.
*/
class AddCSVImportProvider
{
/**
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function handle(mixed $payload): mixed
{
$provider = app()->make(Services\CsvImport::class);
$payload[$provider->id] = $provider;
return $payload;
}
}

View File

@@ -0,0 +1,171 @@
<?php
namespace Leantime\Domain\CsvImport\Services;
use League\Csv\Exception as CsvException;
use League\Csv\Reader;
use League\Csv\Statement;
use Leantime\Core\Controller\Frontcontroller;
use Leantime\Domain\Connector\Models\Entity;
use Leantime\Domain\Connector\Models\Integration;
use Leantime\Domain\Connector\Models\Provider;
use Leantime\Domain\Connector\Services\Integrations;
use Leantime\Domain\Connector\Services\ProviderIntegration;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Response;
class CsvImport extends Provider implements ProviderIntegration
{
/**
* @var array|array[]
*/
public array $entities;
public array $methods;
public array $steps = [
'connect',
'entity',
'fields',
'parse',
'import',
];
public array $button = [
'url' => '',
'text' => 'Import CSV',
];
/**
* Constructor - initializes provider metadata and dependencies.
*
* @param Integrations $integrationService Connector integrations service used to persist the integration.
*/
public function __construct(private Integrations $integrationService)
{
$this->id = 'csv_importer';
$this->name = 'CSV Import';
$this->image = '/dist/images/svg/csv-icon.svg';
$this->description = "Import data from a CSV file. To learn more about the CSV format, please visit our <a href='https://support.leantime.io/en/article/importing-data-via-csv-1v941gy' target='_blank'>documentation</a>";
$this->methods[] = 'import, update';
// CSVs can be anyting but are always one file.
$this->entities = [
'default' => [
'name' => 'Sheet',
'fields' => [],
],
];
$this->button['url'] = BASE_URL.'/connector/integration?provider='.$this->id.'#/csvImport/upload';
}
// Logic to connect to provider goes here.
// Needs to manage new connection as well as existing connections.
// Should return bool so we can drive logic in the frontend.
public function connect(): Response
{
// Connection done. Send to next step.
// May just want to add a nextStep() method to provider model or so.
return Frontcontroller::redirect(BASE_URL.'/connector/integration?provider='.$this->id.'#/csvImport/upload');
}
// Sync the entities from the db
/**
* @return true
*/
public function sync(Entity $Entity): bool
{
return true;
}
// Get available fields
/**
* @return array|mixed
*/
public function getFields(): mixed
{
return session('csvImporter.headers') ?? [];
}
public function setFields(array $fields): void {}
// Get available entities
public function getEntities(): array
{
return $this->entities;
}
/**
* @return array<int, mixed>|false
*/
public function getValues(Entity $Entity): mixed
{
$integrationMeta = session('csvImporter.meta', '');
if (empty($integrationMeta)) {
return false;
}
$rows = safe_unserialize($integrationMeta, []);
// Removing the first row if it contains headers
// can be returned or dealt with later on for field matching
if (count($rows) > 0) {
$headers = array_shift($rows);
}
return $rows;
}
public function geValues()
{
return session('csv_records') ?? [];
}
/**
* Parse an uploaded CSV file, store its records in the session and create a
* Connector integration record built from the CSV header.
*
* Reads the uploaded CSV (with the first row as header), materializes all
* records into the session under the `csv_records` key, builds an
* Integration model whose `fields` are the comma separated header columns
* and persists it via the Connector integrations service.
*
* @api
*
* @param UploadedFile $file The uploaded CSV file.
* @return int The id of the created integration record.
*
* @throws CsvException When the CSV cannot be parsed.
*/
public function processUpload(UploadedFile $file): int
{
$csv = Reader::createFromPath($file->getRealPath(), 'r');
$csv->setHeaderOffset(0);
// Will throw a League\Csv\Exception if the CSV is malformed.
$records = Statement::create()->process($csv);
$header = $records->getHeader();
$rows = [];
foreach ($records as $record) {
$rows[] = $record;
}
// Temporarily store the parsed records in the session for later steps.
session(['csv_records' => $rows]);
$integration = new Integration;
$integration->fields = implode(',', $header);
return (int) $this->integrationService->create($integration);
}
}

View File

@@ -0,0 +1,108 @@
@extends($layout)
@section('content')
@php
$maxSize = \Leantime\Core\Files\FileManager::getMaximumFileUploadSize();
@endphp
<div id="fileManager">
{!! $tpl->displayNotification() !!}
<h2>Upload CSV file</h2>
<p>You can upload CSVs to import or update Tasks, Projects, Goals. <a href="https://support.leantime.io/importing-data-via-csv" target="_blank">Check our documentation</a> to learn more about the formatting and to download templates</p>
<br /><br/>
<div class="uploadWrapper" style="width:100%">
<form id="upload-form">
<div class="extra" style="margin-top:5px;"></div>
<div class="fileUploadDrop">
<p><i>{!! __('text.drop_files') !!}</i></p>
<div class="file-upload-input" style="margin:auto; display:inline-block"></div>
</div>
<!-- Progress bar #1 -->
<div class="input-progress"></div>
<div class="input-error"></div>
</form>
</div>
</div>
@once
<script>
if (typeof uppy === 'undefined') {
const uppy = new Uppy.Uppy({
debug: false,
autoProceed: true,
restrictions: {
maxFileSize: {{ $maxSize }}
}
});
uppy.use(Uppy.DropTarget, { target: '#fileManager' });
uppy.use(Uppy.FileInput, {
target: '.file-upload-input',
pretty: true,
locale: {
strings: {
chooseFiles: ' Browse',
}
}
});
uppy.use(Uppy.XHRUpload, {
endpoint: '{{ BASE_URL }}/csvImport/upload',
formData: true,
fieldName: 'file'
});
uppy.use(Uppy.StatusBar, {
target: '.input-progress',
hideUploadButton: false,
hideAfterFinish: false,
});
uppy.use(Uppy.Form, { target: '#upload-form' });
// Upload
uppy.on("restriction-failed", (file, error) => {
jQuery(".input-error").html("<span class='label-important'>"+error+"</span>");
return false
});
uppy.on('upload-success', (file, response) => {
jQuery(".input-error").text('');
window.location.href = "{{ BASE_URL }}/connector/integration?provider=csv_importer&step=entity&integrationId="+response.body.id;
});
uppy.on('upload-error', (file, error, response) => {
jQuery(".input-error").html("<span class='label-important'>There is a problem with your CSV file: "+response.body.error+"</span>");
return false
});
}
</script>
@endonce
@endsection

View File

@@ -0,0 +1,15 @@
<?php
use Leantime\Core\Events\EventDispatcher;
// Register event listener
EventDispatcher::add_filter_listener(
'leantime.domain.connector.services.providers.loadProviders.providerList',
function (mixed $payload) {
$provider = app()->make(\Leantime\Domain\CsvImport\Services\CsvImport::class);
$payload[$provider->id] = $provider;
return $payload;
}
);