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,159 @@
<?php
namespace Leantime\Domain\Wiki\Controllers;
use Illuminate\Contracts\Container\BindingResolutionException;
use Leantime\Core\Auth\Permissions\RequiresPermission;
use Leantime\Core\Controller\Controller;
use Leantime\Core\Controller\Frontcontroller;
use Leantime\Domain\Tickets\Services\Tickets as TicketService;
use Leantime\Domain\Wiki\Models\Article;
use Leantime\Domain\Wiki\Permissions\WikiPermissions;
use Leantime\Domain\Wiki\Services\Wiki as WikiService;
use Symfony\Component\HttpFoundation\Response;
class ArticleDialog extends Controller
{
private WikiService $wikiService;
private TicketService $ticketService;
public function init(WikiService $wikiService, TicketService $ticketService): void
{
$this->wikiService = $wikiService;
$this->ticketService = $ticketService;
}
/**
* @throws BindingResolutionException
*/
#[RequiresPermission(WikiPermissions::VIEW)]
public function get($params): Response
{
$article = app()->make(Article::class);
$article->data = 'far fa-file-alt';
if (isset($params['id'])) {
$article = $this->wikiService->getArticle($params['id'], session('currentProject'));
}
// Delete milestone relationship
if (isset($params['removeMilestone']) === true) {
$article->milestoneId = '';
$results = $this->wikiService->updateArticle($article);
if ($results) {
$this->tpl->setNotification($this->language->__('notifications.milestone_detached'), 'success', 'articlemilestone_unlinked');
return Frontcontroller::redirect(BASE_URL.'/wiki/articleDialog/'.$article->id);
}
}
if (session('currentWiki') != '') {
$wikiHeadlines = $this->wikiService->getAllWikiHeadlines(session('currentWiki'), session('userdata.id'));
} else {
$wikiHeadlines = [];
}
$allProjectMilestones = $this->ticketService->getAllMilestones(['sprint' => '', 'type' => 'milestone', 'currentProject' => session('currentProject')]);
$this->tpl->assign('milestones', $allProjectMilestones);
$this->tpl->assign('wikiHeadlines', $wikiHeadlines);
$this->tpl->assign('article', $article);
return $this->tpl->displayPartial('wiki.articleDialog');
}
/**
* Creates or updates an article. A real dispatch-time VIEW gate guards the handler (entityScoped
* is a no-op at dispatch, which would leave this action — and its internal getArticle/
* getAllProjectWikis reads — ungated); the precise CREATE/EDIT enforcement is done in the
* service's createArticle/updateArticle against the article's real project.
*
* @throws BindingResolutionException
*/
#[RequiresPermission(WikiPermissions::VIEW)]
public function post($params): Response
{
$article = app()->make(Article::class);
if (isset($_GET['id'])) {
$id = $_GET['id'];
$article = $this->wikiService->getArticle($id, session('currentProject'));
$article->title = $params['title'];
$article->data = $params['articleIcon'];
$article->tags = $params['tags'];
$article->status = $params['status'];
$article->parent = $params['parent'];
$article->description = $params['description'];
$article->milestoneId = $params['milestoneId'] ?? $article->milestoneId;
if (isset($params['newMilestone']) && $params['newMilestone'] != '') {
$params['headline'] = $params['newMilestone'];
$params['tags'] = '#ccc';
$params['editFrom'] = dtHelper()->userNow()->formatDateForUser();
$params['editTo'] = dtHelper()->userNow()->addDays(7)->formatDateForUser();
$milestoneId = $this->ticketService->quickAddMilestone($params);
if ($milestoneId !== false) {
$article->milestoneId = $milestoneId;
}
}
if (isset($params['existingMilestone']) && $params['existingMilestone'] != '') {
$article->milestoneId = $params['existingMilestone'];
}
$results = $this->wikiService->updateArticle($article);
if ($results) {
$this->tpl->setNotification('notification.article_updated_successfully', 'success', 'article_updated');
}
} else {
// New
$article->title = $params['title'];
$article->author = session('userdata.id');
// Notes created from the "All Notes" grid have no active notebook, so
// canvasId was empty and the note saved into nothing — it appeared not
// to save. Fall back to the project's default notebook (auto-created by
// getAllProjectWikis when none exist yet). (#3216)
$canvasId = session('currentWiki');
if (empty($canvasId)) {
$projectWikis = $this->wikiService->getAllProjectWikis(session('currentProject'));
if (! empty($projectWikis)) {
$canvasId = $projectWikis[0]->id;
}
}
// If we still can't resolve a notebook, don't create an orphaned note
// with an empty canvasId (the original #3216 failure mode) — surface an
// error and send the user back to pick/create a notebook.
if (empty($canvasId)) {
$this->tpl->setNotification('notification.article_save_error_no_notebook', 'error');
return Frontcontroller::redirect(BASE_URL.'/wiki/articleDialog/');
}
$article->canvasId = $canvasId;
$article->data = $params['articleIcon'];
$article->tags = $params['tags'];
$article->status = $params['status'];
$article->parent = $params['parent'];
$article->description = $params['description'];
$id = $this->wikiService->createArticle($article);
if ($id) {
$this->tpl->setNotification('notification.article_created_successfully', 'success', 'article_created');
}
}
if (isset($params['saveAndCloseArticle']) === true && $params['saveAndCloseArticle'] == 1) {
return Frontcontroller::redirect(BASE_URL.'/wiki/articleDialog/'.$id.'?closeModal=1');
} else {
return Frontcontroller::redirect(BASE_URL.'/wiki/articleDialog/'.$id);
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Leantime\Domain\Wiki\Controllers;
use Leantime\Core\Auth\Permissions\RequiresPermission;
use Leantime\Core\Controller\Controller;
use Leantime\Core\Controller\Frontcontroller;
use Leantime\Domain\Wiki\Permissions\WikiPermissions;
use Leantime\Domain\Wiki\Services\Wiki as WikiService;
use Symfony\Component\HttpFoundation\Response;
class DelArticle extends Controller
{
private WikiService $wikiService;
/**
* Initializes dependencies.
*/
public function init(WikiService $wikiService): void
{
$this->wikiService = $wikiService;
}
/**
* Displays the delete article confirmation.
*
* @param array $params Request parameters
*/
#[RequiresPermission(WikiPermissions::DELETE)]
public function get(array $params): Response
{
return $this->tpl->displayPartial('wiki.delArticle');
}
/**
* Handles article deletion. The controller gate defers (entityScoped) to the service's
* deleteArticle(), which authorizes DELETE against the article's REAL project.
*
* @param array $params Request parameters
*/
#[RequiresPermission(WikiPermissions::DELETE, entityScoped: true)]
public function post(array $params): Response
{
$id = (int) ($params['id'] ?? $_GET['id'] ?? 0);
if (isset($_POST['del']) && $id > 0) {
$this->wikiService->deleteArticle($id);
$this->tpl->setNotification($this->language->__('notification.article_deleted'), 'success', 'article_deleted');
return Frontcontroller::redirect(BASE_URL.'/wiki/show');
}
return $this->tpl->displayPartial('wiki.delArticle');
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Leantime\Domain\Wiki\Controllers;
use Leantime\Core\Auth\Permissions\RequiresPermission;
use Leantime\Core\Controller\Controller;
use Leantime\Core\Controller\Frontcontroller;
use Leantime\Domain\Wiki\Permissions\WikiPermissions;
use Leantime\Domain\Wiki\Services\Wiki as WikiService;
use Symfony\Component\HttpFoundation\Response;
class DelWiki extends Controller
{
private WikiService $wikiService;
/**
* Initializes dependencies.
*/
public function init(WikiService $wikiService): void
{
$this->wikiService = $wikiService;
}
/**
* Displays the delete wiki confirmation.
*
* @param array $params Request parameters
*/
#[RequiresPermission(WikiPermissions::DELETE)]
public function get(array $params): Response
{
return $this->tpl->displayPartial('wiki.delWiki');
}
/**
* Handles wiki deletion. The controller gate defers (entityScoped) to the service's
* deleteWiki(), which authorizes DELETE against the wiki's REAL project.
*
* @param array $params Request parameters
*/
#[RequiresPermission(WikiPermissions::DELETE, entityScoped: true)]
public function post(array $params): Response
{
$id = (int) ($params['id'] ?? $_GET['id'] ?? 0);
if (isset($_POST['del']) && $id > 0) {
$this->wikiService->deleteWiki($id);
$this->tpl->setNotification($this->language->__('notification.wiki_deleted'), 'success', 'wiki_deleted');
return Frontcontroller::redirect(BASE_URL.'/wiki/show');
}
return $this->tpl->displayPartial('wiki.delWiki');
}
}

View File

@@ -0,0 +1,200 @@
<?php
namespace Leantime\Domain\Wiki\Controllers;
use Illuminate\Contracts\Container\BindingResolutionException;
use Leantime\Core\Auth\Permissions\RequiresPermission;
use Leantime\Core\Controller\Controller;
use Leantime\Core\Controller\Frontcontroller;
use Leantime\Domain\Comments\Services\Comments as CommentService;
use Leantime\Domain\Tickets\Services\Tickets as TicketService;
use Leantime\Domain\Wiki\Permissions\WikiPermissions;
use Leantime\Domain\Wiki\Services\Wiki as WikiService;
use Symfony\Component\HttpFoundation\Response;
class Show extends Controller
{
private WikiService $wikiService;
private CommentService $commentService;
private TicketService $ticketService;
public function init(WikiService $wikiService, CommentService $commentService, TicketService $ticketService): void
{
$this->wikiService = $wikiService;
$this->commentService = $commentService;
$this->ticketService = $ticketService;
}
/**
* @throws BindingResolutionException
*/
#[RequiresPermission(WikiPermissions::VIEW)]
public function get(array $params): Response
{
$currentArticle = '';
$wikiHeadlines = [];
// Get all project wikis, creates one if none exists
$wikis = $this->wikiService->getAllProjectWikis(session('currentProject'));
// Special case: Setting wiki (active action), set wiki, headlines and current Article
if (isset($_GET['setWiki'])) {
$wikiId = (int) $_GET['setWiki'];
return $this->setWikiAndRedirect($wikiId);
}
if (isset($params['id'])) {
$currentArticle = $this->wikiService->setCurrentArticle($params['id'], session('usersettings.id'));
if ($currentArticle === false) {
$this->wikiService->clearWikiCache();
return Frontcontroller::redirect(BASE_URL.'/errors/error404');
}
} elseif (
session()->exists('lastArticle') &&
session('lastArticle') != '' &&
! isset($params['id'])) {
$currentArticle = $this->wikiService->setCurrentArticle(session('lastArticle'), session('usersettings.id'));
if ($currentArticle) {
return Frontcontroller::redirect(BASE_URL.'/wiki/show/'.$currentArticle->id);
}
// If neither session is set nor the params id we are coming in fresh. Grab the article from wiki if there is one
} else {
// False is okay, just an empty wiki
$success = $this->wikiService->setCurrentWiki(session('currentWiki'));
if ($success === false) {
// Try getting the first wiki
$success = $this->wikiService->setCurrentWiki($wikis[0]->id);
if ($success === false) {
$this->wikiService->clearWikiCache();
return Frontcontroller::redirect(BASE_URL.'/errors/error404');
}
}
$defaultArticle = $this->wikiService->getDefaultArticleForWiki(session('currentWiki'), session('userdata.id'));
if ($defaultArticle !== false) {
session(['lastArticle' => $defaultArticle->id]);
return Frontcontroller::redirect(BASE_URL.'/wiki/show/'.$defaultArticle->id);
}
// If not it's really just empty.
}
// At this point we should have a currentWiki. Even if non exist
$wikiHeadlines = $this->wikiService->getAllWikiHeadlines(session('currentWiki'), session('userdata.id'));
if (! $wikiHeadlines) {
$wikiHeadlines = [];
}
// Get the actual wiki content
$currentWiki = $this->wikiService->getWiki(session('currentWiki'));
if (empty($currentWiki)) {
$this->wikiService->clearWikiCache();
// If we can't find a current wiki at this point something went wrong
return Frontcontroller::redirect(BASE_URL.'/errors/error404');
}
// Delete comment
if (isset($_GET['delComment']) === true) {
$commentId = (int) ($_GET['delComment']);
$this->commentService->deleteComment($commentId);
$this->tpl->setNotification($this->language->__('notifications.comment_deleted'), 'success', 'wikicomment_deleted');
}
if (isset($currentArticle->id)) {
$comment = $this->commentService->getComments('article', $currentArticle->id, 0);
} else {
$comment = [];
}
// Get all milestones for the project
$allProjectMilestones = $this->ticketService->getAllMilestones([
'sprint' => '',
'type' => 'milestone',
'currentProject' => session('currentProject'),
]);
$this->tpl->assign('comments', $comment);
$this->tpl->assign('numComments', count($comment));
$this->tpl->assign('currentArticle', $currentArticle);
$this->tpl->assign('currentWiki', $currentWiki);
$this->tpl->assign('wikis', $wikis);
$this->tpl->assign('wikiHeadlines', $wikiHeadlines);
$this->tpl->assign('milestones', $allProjectMilestones);
return $this->tpl->display('wiki.show');
}
/**
* @throws BindingResolutionException
*/
#[RequiresPermission(WikiPermissions::VIEW)]
public function post(array $params): Response
{
if (isset($_GET['id']) === true) {
$id = (int) ($_GET['id']);
$currentArticle = $this->wikiService->getArticle($id, session('currentProject'));
if (isset($_POST['comment']) === true) {
if ($this->commentService->addComment($_POST, 'article', $id, $currentArticle)) {
$this->tpl->setNotification(
$this->language->__('notifications.comment_create_success'),
'success',
'wikicomment_created'
);
} else {
$this->tpl->setNotification($this->language->__('notifications.comment_create_error'), 'error');
}
}
return Frontcontroller::redirect(BASE_URL.'/wiki/show/'.$id);
}
return Frontcontroller::redirect(BASE_URL.'/wiki/show/');
}
protected function setWikiAndRedirect($id): Response
{
$this->wikiService->clearWikiCache();
$success = $this->wikiService->setCurrentWiki($id);
if ($success === false) {
$this->wikiService->clearWikiCache();
return Frontcontroller::redirect(BASE_URL.'/errors/error404');
}
$defaultArticle = $this->wikiService->getDefaultArticleForWiki($id, session('userdata.id'));
if ($defaultArticle !== false) {
session(['lastArticle' => $defaultArticle->id]);
return Frontcontroller::redirect(BASE_URL.'/wiki/show/'.$defaultArticle->id);
}
return Frontcontroller::redirect(BASE_URL.'/wiki/show/');
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Leantime\Domain\Wiki\Controllers;
use Leantime\Core\Auth\Permissions\RequiresPermission;
use Leantime\Core\Controller\Controller;
use Leantime\Domain\Wiki\Permissions\WikiPermissions;
use Symfony\Component\HttpFoundation\Response;
class Templates extends Controller
{
public function init(): void {}
/**
* @throws \Exception
*/
#[RequiresPermission(WikiPermissions::VIEW)]
public function get($params): Response
{
return $this->tpl->displayPartial('wiki.templates');
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Leantime\Domain\Wiki\Controllers;
use Illuminate\Contracts\Container\BindingResolutionException;
use Leantime\Core\Auth\Permissions\RequiresPermission;
use Leantime\Core\Controller\Controller;
use Leantime\Core\Controller\Frontcontroller;
use Leantime\Domain\Wiki\Models\Wiki;
use Leantime\Domain\Wiki\Permissions\WikiPermissions;
use Leantime\Domain\Wiki\Services\Wiki as WikiService;
use Symfony\Component\HttpFoundation\Response;
class WikiModal extends Controller
{
private WikiService $wikiService;
public function init(WikiService $wikiService): void
{
$this->wikiService = $wikiService;
}
/**
* @throws BindingResolutionException
*/
#[RequiresPermission(WikiPermissions::VIEW)]
public function get($params): Response
{
$wiki = app()->make(Wiki::class);
if (isset($_GET['id'])) {
$wiki = $this->wikiService->getWiki($_GET['id']);
}
$this->tpl->assign('wiki', $wiki);
return $this->tpl->displayPartial('wiki.wikiDialog');
}
/**
* Creates or updates a wiki (notebook). The controller gate defers (entityScoped) to the
* service's createWiki/updateWiki, which authorize CREATE/EDIT against the wiki's real project.
*
* @throws BindingResolutionException
*/
#[RequiresPermission(WikiPermissions::EDIT, entityScoped: true)]
public function post($params): Response
{
$wiki = app()->make(Wiki::class);
if (isset($_GET['id'])) {
$id = (int) $_GET['id'];
// Update
$wiki->title = $params['title'];
$this->wikiService->updateWiki($wiki, $id);
$this->tpl->setNotification('notification.wiki_updated_successfully', 'success', 'wiki_updated');
return Frontcontroller::redirect(BASE_URL.'/wiki/wikiModal/'.$id);
} else {
// New
$wiki->title = $params['title'];
$wiki->projectId = session('currentProject');
$wiki->author = session('userdata.id');
$id = $this->wikiService->createWiki($wiki);
// session(["currentWiki" => $id]);
if ($id) {
$this->tpl->setNotification('notification.wiki_created_successfully', 'success', 'wiki_created');
return Frontcontroller::redirect(BASE_URL.'/wiki/wikiModal/'.$id.'?closeModal=1');
}
return Frontcontroller::redirect(BASE_URL.'/wiki/wikiModal/'.$id.'');
}
}
}