OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
211
app/Domain/Canvas/Controllers/BoardDialog.php
Normal file
211
app/Domain/Canvas/Controllers/BoardDialog.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Canvas\Controllers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Leantime\Core\Auth\Permissions\RequiresPermission;
|
||||
use Leantime\Core\Controller\Controller;
|
||||
use Leantime\Core\Controller\Frontcontroller;
|
||||
use Leantime\Core\Mailer as MailerCore;
|
||||
use Leantime\Domain\Blueprints\Permissions\BlueprintsPermissions;
|
||||
use Leantime\Domain\Blueprints\Services\Blueprints as BlueprintsService;
|
||||
use Leantime\Domain\Projects\Services\Projects as ProjectService;
|
||||
use Leantime\Domain\Queue\Repositories\Queue as QueueRepository;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Handles canvas board create/edit dialog.
|
||||
*/
|
||||
class BoardDialog extends Controller
|
||||
{
|
||||
/**
|
||||
* Constant that must be redefined by subclasses.
|
||||
*/
|
||||
protected const CANVAS_NAME = '??';
|
||||
|
||||
private ProjectService $projectService;
|
||||
|
||||
private BlueprintsService $blueprintsService;
|
||||
|
||||
private object $canvasRepo;
|
||||
|
||||
/**
|
||||
* Initializes dependencies.
|
||||
*/
|
||||
public function init(ProjectService $projectService): void
|
||||
{
|
||||
$this->projectService = $projectService;
|
||||
$this->blueprintsService = app()->make(BlueprintsService::class);
|
||||
$canvasName = Str::studly(static::CANVAS_NAME).'canvas';
|
||||
$repoName = app()->getNamespace()."Domain\\$canvasName\\Repositories\\$canvasName";
|
||||
$this->canvasRepo = app()->make($repoName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the board dialog form.
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::VIEW, entityScoped: true)]
|
||||
public function get(array $params): Response
|
||||
{
|
||||
$currentCanvasId = '';
|
||||
$canvasTitle = '';
|
||||
|
||||
if (isset($params['id'])) {
|
||||
// getBoard authorizes VIEW against the board's real project; false = missing/foreign/
|
||||
// unauthorized — don't expose the title or switch the active board (no session poison).
|
||||
$singleCanvas = $this->blueprintsService->getBoard((int) $params['id'], static::CANVAS_NAME.'canvas');
|
||||
if ($singleCanvas !== false) {
|
||||
$currentCanvasId = (int) $params['id'];
|
||||
$canvasTitle = $singleCanvas[0]['title'] ?? '';
|
||||
session(['current'.strtoupper(static::CANVAS_NAME).'Canvas' => $currentCanvasId]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->assignTemplateVars($currentCanvasId, $canvasTitle);
|
||||
|
||||
if (! isset($_GET['raw'])) {
|
||||
return $this->tpl->displayPartial('canvas.boardDialog');
|
||||
}
|
||||
|
||||
return new Response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles board creation and editing.
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::EDIT, entityScoped: true)]
|
||||
public function post(array $params): Response
|
||||
{
|
||||
$currentCanvasId = '';
|
||||
$canvasTitle = '';
|
||||
|
||||
if (isset($params['id'])) {
|
||||
$singleCanvas = $this->blueprintsService->getBoard((int) $params['id'], static::CANVAS_NAME.'canvas');
|
||||
if ($singleCanvas !== false) {
|
||||
$currentCanvasId = (int) $params['id'];
|
||||
$canvasTitle = $singleCanvas[0]['title'] ?? '';
|
||||
session(['current'.strtoupper(static::CANVAS_NAME).'Canvas' => $currentCanvasId]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['newCanvas'])) {
|
||||
$result = $this->handleNewCanvas($currentCanvasId);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['editCanvas']) && $currentCanvasId > 0) {
|
||||
$result = $this->handleEditCanvas($currentCanvasId);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assignTemplateVars($currentCanvasId, $canvasTitle);
|
||||
|
||||
if (! isset($_GET['raw'])) {
|
||||
return $this->tpl->displayPartial('canvas.boardDialog');
|
||||
}
|
||||
|
||||
return new Response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles creating a new canvas board.
|
||||
*/
|
||||
private function handleNewCanvas(int|string &$currentCanvasId): ?Response
|
||||
{
|
||||
if (! isset($_POST['canvastitle']) || empty($_POST['canvastitle'])) {
|
||||
$this->tpl->setNotification($this->language->__('notification.please_enter_title'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->canvasRepo->existCanvas(session('currentProject'), $_POST['canvastitle'])) {
|
||||
$this->tpl->setNotification($this->language->__('notification.board_exists'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$values = [
|
||||
'title' => $_POST['canvastitle'],
|
||||
'author' => session('userdata.id'),
|
||||
'projectId' => session('currentProject'),
|
||||
];
|
||||
// createBoard authorizes CREATE against the target (current) project.
|
||||
$currentCanvasId = $this->blueprintsService->createBoard($values, static::CANVAS_NAME.'canvas');
|
||||
|
||||
$this->notifyBoardCreated($values['title']);
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.board_created'), 'success', static::CANVAS_NAME.'board_created');
|
||||
session(['current'.strtoupper(static::CANVAS_NAME).'Canvas' => $currentCanvasId]);
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas/boardDialog/'.$currentCanvasId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles editing a canvas board title.
|
||||
*/
|
||||
private function handleEditCanvas(int $currentCanvasId): ?Response
|
||||
{
|
||||
if (! isset($_POST['canvastitle']) || empty($_POST['canvastitle'])) {
|
||||
$this->tpl->setNotification($this->language->__('notification.please_enter_title'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->canvasRepo->existCanvas(session('currentProject'), $_POST['canvastitle'])) {
|
||||
$this->tpl->setNotification($this->language->__('notification.board_exists'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// renameBoard authorizes EDIT against the board's real project.
|
||||
$this->blueprintsService->renameBoard($currentCanvasId, $_POST['canvastitle'], static::CANVAS_NAME.'canvas');
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.board_edited'), 'success');
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas/boardDialog/'.$currentCanvasId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends board creation notifications to project users.
|
||||
*/
|
||||
private function notifyBoardCreated(string $title): void
|
||||
{
|
||||
$mailer = app()->make(MailerCore::class);
|
||||
$users = $this->projectService->getUsersToNotify(session('currentProject'));
|
||||
|
||||
$mailer->setSubject($this->language->__('notification.board_created'));
|
||||
$message = sprintf(
|
||||
$this->language->__('email_notifications.canvas_created_message'),
|
||||
session('userdata.name'),
|
||||
"<a href='".CURRENT_URL."'>".strip_tags($title).'</a>'
|
||||
);
|
||||
$mailer->setHtml($message);
|
||||
|
||||
$queue = app()->make(QueueRepository::class);
|
||||
$queue->queueMessageToUsers(
|
||||
$users,
|
||||
$message,
|
||||
$this->language->__('notification.board_created'),
|
||||
session('currentProject')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns common template variables.
|
||||
*/
|
||||
private function assignTemplateVars(int|string $currentCanvasId, string $canvasTitle): void
|
||||
{
|
||||
$this->tpl->assign('currentCanvas', $currentCanvasId);
|
||||
$this->tpl->assign('canvasName', static::CANVAS_NAME);
|
||||
$this->tpl->assign('canvasTitle', $canvasTitle);
|
||||
$this->tpl->assign('users', $this->projectService->getUsersAssignedToProject(session('currentProject')));
|
||||
}
|
||||
}
|
||||
83
app/Domain/Canvas/Controllers/DelCanvas.php
Normal file
83
app/Domain/Canvas/Controllers/DelCanvas.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Canvas\Controllers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Leantime\Core\Auth\Permissions\RequiresPermission;
|
||||
use Leantime\Core\Controller\Controller;
|
||||
use Leantime\Core\Controller\Frontcontroller;
|
||||
use Leantime\Domain\Blueprints\Permissions\BlueprintsPermissions;
|
||||
use Leantime\Domain\Blueprints\Services\Blueprints as BlueprintsService;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Handles deletion of a canvas board.
|
||||
*/
|
||||
class DelCanvas extends Controller
|
||||
{
|
||||
/**
|
||||
* Constant that must be redefined by subclasses.
|
||||
*/
|
||||
protected const CANVAS_NAME = '??';
|
||||
|
||||
private mixed $canvasRepo;
|
||||
|
||||
private BlueprintsService $blueprintsService;
|
||||
|
||||
/**
|
||||
* Initializes dependencies.
|
||||
*
|
||||
* Note: no `void` return type so plugin subclasses that override init()
|
||||
* without a return type (this class is the deprecated plugin shim) stay
|
||||
* signature-compatible.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->blueprintsService = app()->make(BlueprintsService::class);
|
||||
$canvasName = Str::studly(static::CANVAS_NAME).'canvas';
|
||||
$repoName = app()->getNamespace()."Domain\\$canvasName\\Repositories\\$canvasName";
|
||||
$this->canvasRepo = app()->make($repoName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the delete canvas confirmation.
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::DELETE)]
|
||||
public function get(array $params): Response
|
||||
{
|
||||
return $this->tpl->displayPartial(static::CANVAS_NAME.'canvas.delCanvas');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles canvas board deletion.
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::DELETE, entityScoped: true)]
|
||||
public function post(array $params): Response
|
||||
{
|
||||
$id = (int) ($params['id'] ?? $_GET['id'] ?? 0);
|
||||
|
||||
if (isset($_POST['del']) && $id > 0) {
|
||||
// The service resolves the board's REAL project and authorizes DELETE against it
|
||||
// (throwing for a missing/foreign board) — closing the by-id board-delete IDOR the
|
||||
// previous role-only Auth::authOrRedirect left open.
|
||||
$this->blueprintsService->deleteBoard($id, static::CANVAS_NAME.'canvas');
|
||||
|
||||
$allCanvas = $this->canvasRepo->getAllCanvas(session('currentProject'));
|
||||
session(['current'.strtoupper(static::CANVAS_NAME).'Canvas' => $allCanvas[0]['id'] ?? -1]);
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.board_deleted'), 'success', strtoupper(static::CANVAS_NAME).'canvas_deleted');
|
||||
|
||||
if (! $allCanvas || count($allCanvas) == 0) {
|
||||
return Frontcontroller::redirect(BASE_URL.'/blueprints/showBoards');
|
||||
}
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas/showCanvas');
|
||||
}
|
||||
|
||||
return $this->tpl->displayPartial(static::CANVAS_NAME.'canvas.delCanvas');
|
||||
}
|
||||
}
|
||||
65
app/Domain/Canvas/Controllers/DelCanvasItem.php
Normal file
65
app/Domain/Canvas/Controllers/DelCanvasItem.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Canvas\Controllers;
|
||||
|
||||
use Leantime\Core\Auth\Permissions\RequiresPermission;
|
||||
use Leantime\Core\Controller\Controller;
|
||||
use Leantime\Core\Controller\Frontcontroller;
|
||||
use Leantime\Domain\Blueprints\Permissions\BlueprintsPermissions;
|
||||
use Leantime\Domain\Blueprints\Services\Blueprints as BlueprintsService;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Handles deletion of a canvas item.
|
||||
*/
|
||||
class DelCanvasItem extends Controller
|
||||
{
|
||||
/**
|
||||
* Constant that must be redefined by subclasses.
|
||||
*/
|
||||
protected const CANVAS_NAME = '??';
|
||||
|
||||
private BlueprintsService $blueprintsService;
|
||||
|
||||
/**
|
||||
* Initializes dependencies.
|
||||
*/
|
||||
public function init(): void
|
||||
{
|
||||
$this->blueprintsService = app()->make(BlueprintsService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the delete canvas item confirmation.
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::DELETE)]
|
||||
public function get(array $params): Response
|
||||
{
|
||||
return $this->tpl->displayPartial(static::CANVAS_NAME.'canvas.delCanvasItem');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles canvas item deletion.
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::DELETE, entityScoped: true)]
|
||||
public function post(array $params): Response
|
||||
{
|
||||
$id = (int) ($params['id'] ?? $_GET['id'] ?? 0);
|
||||
|
||||
if (isset($_POST['del']) && $id > 0) {
|
||||
// The service resolves the item's REAL project and authorizes DELETE against it
|
||||
// (throwing for a missing/foreign item) — closing the by-id item-delete IDOR.
|
||||
$this->blueprintsService->deleteCanvasItem($id, static::CANVAS_NAME.'canvas');
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.element_deleted'), 'success', strtoupper(static::CANVAS_NAME).'canvasitem_deleted');
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas/showCanvas');
|
||||
}
|
||||
|
||||
return $this->tpl->displayPartial(static::CANVAS_NAME.'canvas.delCanvasItem');
|
||||
}
|
||||
}
|
||||
291
app/Domain/Canvas/Controllers/EditCanvasComment.php
Normal file
291
app/Domain/Canvas/Controllers/EditCanvasComment.php
Normal file
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* editCanvasComment class - Generic canvas controller / Edit Comments
|
||||
*/
|
||||
|
||||
namespace Leantime\Domain\Canvas\Controllers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Leantime\Core\Auth\Permissions\RequiresPermission;
|
||||
use Leantime\Core\Controller\Controller;
|
||||
use Leantime\Core\Controller\Frontcontroller;
|
||||
use Leantime\Domain\Blueprints\Permissions\BlueprintsPermissions;
|
||||
use Leantime\Domain\Blueprints\Services\Blueprints as BlueprintsService;
|
||||
use Leantime\Domain\Comments\Repositories\Comments as CommentRepository;
|
||||
use Leantime\Domain\Notifications\Models\Notification as NotificationModel;
|
||||
use Leantime\Domain\Projects\Services\Projects as ProjectService;
|
||||
|
||||
class EditCanvasComment extends Controller
|
||||
{
|
||||
/**
|
||||
* Constant that must be redefined
|
||||
*/
|
||||
protected const CANVAS_NAME = '??';
|
||||
|
||||
private CommentRepository $commentsRepo;
|
||||
|
||||
private ProjectService $projectService;
|
||||
|
||||
private BlueprintsService $blueprintsService;
|
||||
|
||||
private object $canvasRepo;
|
||||
|
||||
/**
|
||||
* init - initialize private variables
|
||||
*/
|
||||
public function init(
|
||||
CommentRepository $commentsRepo,
|
||||
ProjectService $projectService
|
||||
) {
|
||||
$this->commentsRepo = $commentsRepo;
|
||||
$this->projectService = $projectService;
|
||||
$this->blueprintsService = app()->make(BlueprintsService::class);
|
||||
|
||||
$canvasName = Str::studly(static::CANVAS_NAME).'canvas';
|
||||
$repoName = app()->getNamespace()."Domain\\$canvasName\\Repositories\\$canvasName";
|
||||
$this->canvasRepo = app()->make($repoName);
|
||||
}
|
||||
|
||||
/**
|
||||
* get - handle get requests
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::VIEW, entityScoped: true)]
|
||||
public function get($params)
|
||||
{
|
||||
$canvasType = static::CANVAS_NAME.'canvas';
|
||||
|
||||
$canvasTypes = $this->canvasRepo->getCanvasTypes();
|
||||
if (isset($params['id'])) {
|
||||
// Resolve + VIEW-authorize the item against its real project first.
|
||||
$canvasItem = $this->blueprintsService->getCanvasItem((int) $params['id'], $canvasType);
|
||||
if (! $canvasItem) {
|
||||
return $this->tpl->displayPartial('errors.error404');
|
||||
}
|
||||
|
||||
// Delete comment — only when it belongs to THIS gated item (module + moduleId).
|
||||
if (isset($params['delComment']) === true) {
|
||||
$commentId = (int) ($params['delComment']);
|
||||
$comment = $this->commentsRepo->getComment($commentId);
|
||||
if ($comment !== false
|
||||
&& (string) $comment['module'] === static::CANVAS_NAME.'canvasitem'
|
||||
&& (int) $comment['moduleId'] === (int) $canvasItem['id']) {
|
||||
$this->commentsRepo->deleteComment($commentId);
|
||||
$this->tpl->setNotification($this->language->__('notifications.comment_deleted'), 'success', strtoupper(static::CANVAS_NAME).'canvascomment_deleted');
|
||||
}
|
||||
}
|
||||
|
||||
$comments = $this->commentsRepo->getComments(static::CANVAS_NAME.'canvasitem', $canvasItem['id']);
|
||||
$this->tpl->assign('numComments', $this->commentsRepo->countComments(static::CANVAS_NAME.'canvasitem', $canvasItem['id']));
|
||||
} else {
|
||||
if (isset($params['type'])) {
|
||||
$type = strip_tags($params['type']);
|
||||
} else {
|
||||
$type = array_key_first($canvasTypes);
|
||||
}
|
||||
|
||||
$canvasItem = [
|
||||
'id' => '',
|
||||
'box' => $type,
|
||||
'description' => '',
|
||||
'status' => array_key_first($this->canvasRepo->getStatusLabels()),
|
||||
'relates' => array_key_first($this->canvasRepo->getRelatesLabels()),
|
||||
'assumptions' => '',
|
||||
'data' => '',
|
||||
'conclusion' => '',
|
||||
'milestoneHeadline' => '',
|
||||
'milestoneId' => '',
|
||||
];
|
||||
|
||||
$comments = [];
|
||||
}
|
||||
|
||||
$this->tpl->assign('comments', $comments);
|
||||
|
||||
$this->tpl->assign('canvasTypes', $canvasTypes);
|
||||
$this->tpl->assign('canvasItem', $canvasItem);
|
||||
|
||||
return $this->tpl->displayPartial(static::CANVAS_NAME.'canvas.canvasComment');
|
||||
}
|
||||
|
||||
/**
|
||||
* post - handle post requests
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::EDIT, entityScoped: true)]
|
||||
public function post($params)
|
||||
{
|
||||
$canvasType = static::CANVAS_NAME.'canvas';
|
||||
|
||||
if (isset($params['changeItem'])) {
|
||||
if (isset($params['itemId']) && $params['itemId'] != '') {
|
||||
if (isset($params['description']) && ! empty($params['description'])) {
|
||||
$currentCanvasId = (int) session('current'.strtoupper(static::CANVAS_NAME).'Canvas');
|
||||
|
||||
$canvasItem = [
|
||||
'box' => $params['box'],
|
||||
'author' => session('userdata.id'),
|
||||
'description' => $params['description'],
|
||||
'status' => $params['status'],
|
||||
'relates' => $params['relates'],
|
||||
'assumptions' => $params['assumptions'],
|
||||
'data' => $params['data'],
|
||||
'conclusion' => $params['conclusion'],
|
||||
'itemId' => $params['itemId'],
|
||||
'id' => $params['itemId'],
|
||||
'canvasId' => $currentCanvasId,
|
||||
'milestoneId' => $params['milestoneId'],
|
||||
'dependentMilstone' => '',
|
||||
];
|
||||
|
||||
// Resolves the item's real project from itemId and authorizes EDIT there.
|
||||
$this->blueprintsService->updateCanvasItem($canvasItem, $canvasType);
|
||||
|
||||
$comments = $this->commentsRepo->getComments(static::CANVAS_NAME.'canvasitem', $params['itemId']);
|
||||
$this->tpl->assign('numComments', $this->commentsRepo->countComments(
|
||||
static::CANVAS_NAME.'canvasitem',
|
||||
$params['itemId']
|
||||
));
|
||||
$this->tpl->assign('comments', $comments);
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notifications.canvas_item_updates'), 'success', strtoupper(static::CANVAS_NAME).'canvasitem_updated');
|
||||
|
||||
$notification = app()->make(NotificationModel::class);
|
||||
$notification->url = [
|
||||
'url' => BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'/editCanvasComment/'.(int) $params['itemId'],
|
||||
'text' => $this->language->__('email_notifications.canvas_item_update_cta'),
|
||||
];
|
||||
$notification->entity = $canvasItem;
|
||||
$notification->module = static::CANVAS_NAME.'canvas';
|
||||
$notification->action = 'updated';
|
||||
$notification->projectId = session('currentProject');
|
||||
$notification->subject = $this->language->__('email_notifications.canvas_board_edited');
|
||||
$notification->authorId = session('userdata.id');
|
||||
$notification->message = sprintf(
|
||||
$this->language->__('email_notifications.canvas_item_update_message'),
|
||||
session('userdata.name'),
|
||||
$canvasItem['description']
|
||||
);
|
||||
|
||||
$this->projectService->notifyProjectUsers($notification);
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'/editCanvasComment/'.$params['itemId']);
|
||||
} else {
|
||||
$this->tpl->setNotification($this->language->__('notification.please_enter_element_title'), 'error');
|
||||
}
|
||||
} else {
|
||||
if (isset($_POST['description']) && ! empty($_POST['description'])) {
|
||||
$currentCanvasId = (int) session('current'.strtoupper(static::CANVAS_NAME).'Canvas');
|
||||
|
||||
$canvasItem = [
|
||||
'box' => $params['box'],
|
||||
'author' => session('userdata.id'),
|
||||
'description' => $params['description'],
|
||||
'status' => $params['status'],
|
||||
'relates' => $params['relates'],
|
||||
'assumptions' => $params['assumptions'],
|
||||
'data' => $params['data'],
|
||||
'conclusion' => $params['conclusion'],
|
||||
'canvasId' => $currentCanvasId,
|
||||
];
|
||||
|
||||
// Resolves the target board's real project from canvasId and authorizes CREATE.
|
||||
$id = $this->blueprintsService->createCanvasItem($canvasItem, $canvasType);
|
||||
|
||||
$canvasItem['id'] = $id;
|
||||
|
||||
$canvasTypes = $this->canvasRepo->getCanvasTypes();
|
||||
|
||||
$this->tpl->setNotification($canvasTypes[$params['box']].' successfully created', 'success', strtoupper(static::CANVAS_NAME).'canvasitem_created');
|
||||
|
||||
$notification = app()->make(NotificationModel::class);
|
||||
$notification->url = [
|
||||
'url' => BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'/editCanvasComment/'.(int) ($params['itemId'] ?? $id),
|
||||
'text' => $this->language->__('email_notifications.canvas_item_update_cta'),
|
||||
];
|
||||
$notification->entity = $canvasItem;
|
||||
$notification->module = static::CANVAS_NAME.'canvas';
|
||||
$notification->action = 'created';
|
||||
$notification->projectId = session('currentProject');
|
||||
$notification->subject = $this->language->__('email_notifications.canvas_board_item_created');
|
||||
$notification->authorId = session('userdata.id');
|
||||
$notification->message = sprintf(
|
||||
$this->language->__('email_notifications.canvas_item_created_message'),
|
||||
session('userdata.name'),
|
||||
$canvasItem['description']
|
||||
);
|
||||
|
||||
$this->projectService->notifyProjectUsers($notification);
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.element_created'), 'success', strtoupper(static::CANVAS_NAME).'canvasitem_created');
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'/editCanvasComment/'.$id);
|
||||
} else {
|
||||
$this->tpl->setNotification($this->language->__('notification.please_enter_element_title'), 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['comment']) === true) {
|
||||
$itemId = (int) ($_GET['id'] ?? 0);
|
||||
|
||||
// Only allow commenting on an item the user can view in their project.
|
||||
if (! $this->blueprintsService->getCanvasItem($itemId, $canvasType)) {
|
||||
return $this->tpl->displayPartial('errors.error404');
|
||||
}
|
||||
|
||||
$values = [
|
||||
'text' => $params['text'],
|
||||
'date' => date('Y-m-d H:i:s'),
|
||||
'userId' => (session('userdata.id')),
|
||||
'moduleId' => $itemId,
|
||||
'commentParent' => ($params['father']),
|
||||
];
|
||||
|
||||
$message = $this->commentsRepo->addComment($values, static::CANVAS_NAME.'canvasitem');
|
||||
$this->tpl->setNotification($this->language->__('notifications.comment_create_success'), 'success', strtoupper(static::CANVAS_NAME).'canvasitemcomment_created');
|
||||
|
||||
$notification = app()->make(NotificationModel::class);
|
||||
$notification->url = [
|
||||
'url' => BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'/editCanvasComment/'.$itemId,
|
||||
'text' => $this->language->__('email_notifications.canvas_item_update_cta'),
|
||||
];
|
||||
$notification->entity = $values;
|
||||
$notification->module = static::CANVAS_NAME.'canvas';
|
||||
$notification->action = 'commented';
|
||||
$notification->projectId = session('currentProject');
|
||||
$notification->subject = $this->language->__('email_notifications.canvas_board_comment_created');
|
||||
$notification->authorId = session('userdata.id');
|
||||
$notification->message = sprintf(
|
||||
$this->language->__('email_notifications.canvas_item__comment_created_message'),
|
||||
session('userdata.name')
|
||||
);
|
||||
|
||||
$this->projectService->notifyProjectUsers($notification);
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'/editCanvasComment/'.$itemId);
|
||||
}
|
||||
|
||||
// Fallback re-display: VIEW-authorize the item; false = missing/foreign/unauthorized -> 404.
|
||||
$itemId = (int) ($_GET['id'] ?? 0);
|
||||
$canvasItem = $this->blueprintsService->getCanvasItem($itemId, $canvasType);
|
||||
if ($itemId > 0 && ! $canvasItem) {
|
||||
return $this->tpl->displayPartial('errors.error404');
|
||||
}
|
||||
|
||||
$this->tpl->assign('id', $itemId);
|
||||
$this->tpl->assign('canvasTypes', $this->canvasRepo->getCanvasTypes());
|
||||
$this->tpl->assign('canvasItem', $canvasItem ?: []);
|
||||
|
||||
return $this->tpl->displayPartial(static::CANVAS_NAME.'canvas.canvasComment');
|
||||
}
|
||||
|
||||
/**
|
||||
* put - handle put requests
|
||||
*/
|
||||
public function put($params) {}
|
||||
|
||||
/**
|
||||
* delete - handle delete requests
|
||||
*/
|
||||
public function delete($params) {}
|
||||
}
|
||||
387
app/Domain/Canvas/Controllers/EditCanvasItem.php
Normal file
387
app/Domain/Canvas/Controllers/EditCanvasItem.php
Normal file
@@ -0,0 +1,387 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Canvas\Controllers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Leantime\Core\Auth\Permissions\RequiresPermission;
|
||||
use Leantime\Core\Controller\Controller;
|
||||
use Leantime\Core\Controller\Frontcontroller;
|
||||
use Leantime\Core\Http\IncomingRequest;
|
||||
use Leantime\Core\Language;
|
||||
use Leantime\Core\UI\Template;
|
||||
use Leantime\Domain\Blueprints\Permissions\BlueprintsPermissions;
|
||||
use Leantime\Domain\Blueprints\Services\Blueprints as BlueprintsService;
|
||||
use Leantime\Domain\Comments\Repositories\Comments as CommentRepository;
|
||||
use Leantime\Domain\Notifications\Models\Notification as NotificationModel;
|
||||
use Leantime\Domain\Projects\Services\Projects as ProjectService;
|
||||
use Leantime\Domain\Tickets\Services\Tickets as TicketService;
|
||||
|
||||
/**
|
||||
* editCanvasItem class - Generic canvas controller / Edit Canvas Item.
|
||||
*
|
||||
* By-id canvas item access routes through the Blueprints service (scoped to this canvas type,
|
||||
* static::CANVAS_NAME.'canvas') so it is authorized against the item's real project; the
|
||||
* per-variant repo is used only for label/config reads. The base canvas type ('??canvas') has
|
||||
* no data — these controllers are reached via subclasses (e.g. Logicmodelcanvas).
|
||||
*/
|
||||
class EditCanvasItem extends Controller
|
||||
{
|
||||
/**
|
||||
* Constant that must be redefined
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected const CANVAS_NAME = '??';
|
||||
|
||||
private TicketService $ticketService;
|
||||
|
||||
private ProjectService $projectService;
|
||||
|
||||
private CommentRepository $commentsRepo;
|
||||
|
||||
private BlueprintsService $blueprintsService;
|
||||
|
||||
private object $canvasRepo;
|
||||
|
||||
/**
|
||||
* __construct - constructor
|
||||
*/
|
||||
public function __construct(
|
||||
IncomingRequest $incomingRequest,
|
||||
Template $tpl,
|
||||
Language $language
|
||||
) {
|
||||
$this->ticketService = app()->make(TicketService::class);
|
||||
$this->projectService = app()->make(ProjectService::class);
|
||||
$this->commentsRepo = app()->make(CommentRepository::class);
|
||||
$this->blueprintsService = app()->make(BlueprintsService::class);
|
||||
$canvasName = Str::studly(static::CANVAS_NAME).'canvas';
|
||||
$repoName = app()->getNamespace()."Domain\\$canvasName\\Repositories\\$canvasName";
|
||||
$this->canvasRepo = app()->make($repoName);
|
||||
|
||||
parent::__construct($incomingRequest, $tpl, $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* get - handle get requests
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::VIEW, entityScoped: true)]
|
||||
public function get($params)
|
||||
{
|
||||
$canvasType = static::CANVAS_NAME.'canvas';
|
||||
$commentModule = static::CANVAS_NAME.'canvas'.'item';
|
||||
|
||||
if (isset($params['id'])) {
|
||||
// Resolve + VIEW-authorize the item against its real project BEFORE any mutation.
|
||||
$canvasItem = $this->blueprintsService->getCanvasItem((int) $params['id'], $canvasType);
|
||||
if (! $canvasItem) {
|
||||
return $this->tpl->displayPartial('errors.error404');
|
||||
}
|
||||
|
||||
// Delete comment — only when it belongs to THIS gated item (module + moduleId);
|
||||
// deleteComment() filters on the comment id alone, so the bind prevents deleting a
|
||||
// foreign item's / project's comment.
|
||||
if (isset($params['delComment'])) {
|
||||
$commentId = (int) ($params['delComment']);
|
||||
$comment = $this->commentsRepo->getComment($commentId);
|
||||
if ($comment !== false
|
||||
&& (string) $comment['module'] === $commentModule
|
||||
&& (int) $comment['moduleId'] === (int) $canvasItem['id']) {
|
||||
$this->commentsRepo->deleteComment($commentId);
|
||||
$this->tpl->setNotification($this->language->__('notifications.comment_deleted'), 'success');
|
||||
}
|
||||
}
|
||||
|
||||
// Delete milestone relationship — an EDIT, authorized by the service.
|
||||
if (isset($params['removeMilestone'])) {
|
||||
$this->blueprintsService->patchCanvasItem((int) $params['id'], ['milestoneId' => ''], $canvasType);
|
||||
$canvasItem = $this->blueprintsService->getCanvasItem((int) $params['id'], $canvasType);
|
||||
$this->tpl->setNotification($this->language->__('notifications.milestone_detached'), 'success');
|
||||
}
|
||||
|
||||
$comments = $this->commentsRepo->getComments($commentModule, $canvasItem['id']);
|
||||
$this->tpl->assign(
|
||||
'numComments',
|
||||
$this->commentsRepo->countComments($commentModule, $canvasItem['id'])
|
||||
);
|
||||
} else {
|
||||
if (isset($params['type'])) {
|
||||
$type = strip_tags($params['type']);
|
||||
} else {
|
||||
$type = array_key_first($this->canvasRepo->elementLabels);
|
||||
}
|
||||
|
||||
$canvasItem = [
|
||||
'id' => '',
|
||||
'box' => $type,
|
||||
'description' => '',
|
||||
'status' => array_key_first($this->canvasRepo->getStatusLabels()),
|
||||
'relates' => array_key_first($this->canvasRepo->getRelatesLabels()),
|
||||
'assumptions' => '',
|
||||
'data' => '',
|
||||
'conclusion' => '',
|
||||
'milestoneHeadline' => '',
|
||||
'milestoneId' => '',
|
||||
];
|
||||
|
||||
$comments = [];
|
||||
}
|
||||
|
||||
$this->tpl->assign('comments', $comments);
|
||||
|
||||
$allProjectMilestones = $this->ticketService->getAllMilestones(['sprint' => '', 'type' => 'milestone', 'currentProject' => session('currentProject')]);
|
||||
$this->tpl->assign('milestones', $allProjectMilestones);
|
||||
$this->tpl->assign('canvasItem', $canvasItem);
|
||||
$this->tpl->assign('canvasIcon', $this->canvasRepo->getIcon());
|
||||
$this->tpl->assign('relatesLabels', $this->canvasRepo->getRelatesLabels());
|
||||
$this->tpl->assign('canvasTypes', $this->canvasRepo->getCanvasTypes());
|
||||
$this->tpl->assign('statusLabels', $this->canvasRepo->getStatusLabels());
|
||||
$this->tpl->assign('dataLabels', $this->canvasRepo->getDataLabels());
|
||||
$this->tpl->assign('currentCanvas', (int) session('current'.strtoupper(static::CANVAS_NAME).'Canvas'));
|
||||
|
||||
return $this->tpl->displayPartial(static::CANVAS_NAME.'canvas'.'.canvasDialog');
|
||||
}
|
||||
|
||||
/**
|
||||
* post - handle post requests
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::EDIT, entityScoped: true)]
|
||||
public function post($params)
|
||||
{
|
||||
$canvasType = static::CANVAS_NAME.'canvas';
|
||||
|
||||
if (isset($params['changeItem'])) {
|
||||
|
||||
if (isset($params['itemId']) && ! empty($params['itemId'])) {
|
||||
if (isset($params['description']) && ! empty($params['description'])) {
|
||||
$currentCanvasId = (int) session('current'.strtoupper(static::CANVAS_NAME).'Canvas');
|
||||
|
||||
$canvasItem = [
|
||||
'box' => $params['box'],
|
||||
'author' => session('userdata.id'),
|
||||
'description' => $params['description'],
|
||||
'status' => $params['status'],
|
||||
'relates' => $params['relates'],
|
||||
'assumptions' => $params['assumptions'],
|
||||
'data' => $params['data'],
|
||||
'conclusion' => $params['conclusion'],
|
||||
'itemId' => $params['itemId'],
|
||||
'canvasId' => $currentCanvasId,
|
||||
'milestoneId' => $params['milestoneId'],
|
||||
'dependentMilstone' => '',
|
||||
'id' => $params['itemId'],
|
||||
];
|
||||
|
||||
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();
|
||||
$params['dependentMilestone'] = '';
|
||||
$id = $this->ticketService->quickAddMilestone($params);
|
||||
|
||||
if ($id !== false) {
|
||||
$canvasItem['milestoneId'] = $id;
|
||||
}
|
||||
}
|
||||
if (isset($params['existingMilestone']) && $params['existingMilestone'] != '') {
|
||||
$canvasItem['milestoneId'] = $params['existingMilestone'];
|
||||
}
|
||||
|
||||
// Resolves the item's real project from itemId and authorizes EDIT there.
|
||||
$this->blueprintsService->updateCanvasItem($canvasItem, $canvasType);
|
||||
|
||||
$comments = $this->commentsRepo->getComments(static::CANVAS_NAME.'canvas'.'item', $params['itemId']);
|
||||
$this->tpl->assign('numComments', $this->commentsRepo->countComments(
|
||||
static::CANVAS_NAME.'canvas'.'item',
|
||||
$params['itemId']
|
||||
));
|
||||
$this->tpl->assign('comments', $comments);
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notifications.canvas_item_updates'), 'success');
|
||||
|
||||
$subject = $this->language->__('email_notifications.canvas_board_edited');
|
||||
$actual_link = BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'#/editCanvasItem/'.(int) $params['itemId'];
|
||||
$message = sprintf(
|
||||
$this->language->__('email_notifications.canvas_item_update_message'),
|
||||
session('userdata.name'),
|
||||
strip_tags($canvasItem['description'])
|
||||
);
|
||||
|
||||
$notification = app()->make(NotificationModel::class);
|
||||
$notification->url = [
|
||||
'url' => $actual_link,
|
||||
'text' => $this->language->__('email_notifications.canvas_item_update_cta'),
|
||||
];
|
||||
$notification->entity = $canvasItem;
|
||||
$notification->module = static::CANVAS_NAME.'canvas';
|
||||
$notification->action = 'updated';
|
||||
$notification->projectId = session('currentProject');
|
||||
$notification->subject = $subject;
|
||||
$notification->authorId = session('userdata.id');
|
||||
$notification->message = $message;
|
||||
|
||||
$this->projectService->notifyProjectUsers($notification);
|
||||
|
||||
$closeModal = '';
|
||||
if (isset($_POST['submitAction']) && $_POST['submitAction'] == 'closeModal') {
|
||||
$closeModal = '?closeModal=true';
|
||||
}
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'/editCanvasItem/'.$params['itemId'].$closeModal);
|
||||
} else {
|
||||
$this->tpl->setNotification($this->language->__('notification.please_enter_title'), 'error');
|
||||
}
|
||||
} else {
|
||||
if (isset($_POST['description']) && ! empty($_POST['description'])) {
|
||||
$currentCanvasId = (int) session('current'.strtoupper(static::CANVAS_NAME).'Canvas');
|
||||
|
||||
$canvasItem = [
|
||||
'box' => $params['box'],
|
||||
'author' => session('userdata.id'),
|
||||
'description' => $params['description'],
|
||||
'status' => $params['status'],
|
||||
'relates' => $params['relates'],
|
||||
'assumptions' => $params['assumptions'],
|
||||
'data' => $params['data'],
|
||||
'conclusion' => $params['conclusion'],
|
||||
'canvasId' => $currentCanvasId,
|
||||
];
|
||||
|
||||
// Resolves the target board's real project from canvasId and authorizes CREATE.
|
||||
$id = $this->blueprintsService->createCanvasItem($canvasItem, $canvasType);
|
||||
$canvasTypes = $this->canvasRepo->getCanvasTypes();
|
||||
|
||||
$this->tpl->setNotification($canvasTypes[$params['box']]['title'].' successfully created', 'success', ''.$params['box'].'_item_created');
|
||||
|
||||
$subject = $this->language->__('email_notifications.canvas_board_item_created');
|
||||
$actual_link = BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'#/editCanvasItem/'.(int) ($params['itemId'] ?? $id);
|
||||
$message = sprintf(
|
||||
$this->language->__('email_notifications.canvas_item_created_message'),
|
||||
session('userdata.name'),
|
||||
strip_tags($canvasItem['description'])
|
||||
);
|
||||
|
||||
$notification = app()->make(NotificationModel::class);
|
||||
$notification->url = [
|
||||
'url' => $actual_link,
|
||||
'text' => $this->language->__('email_notifications.canvas_item_update_cta'),
|
||||
];
|
||||
|
||||
$notification->entity = $canvasItem;
|
||||
$notification->module = static::CANVAS_NAME.'canvas';
|
||||
$notification->action = 'created';
|
||||
$notification->projectId = session('currentProject');
|
||||
$notification->subject = $subject;
|
||||
$notification->authorId = session('userdata.id');
|
||||
$notification->message = $message;
|
||||
|
||||
$this->projectService->notifyProjectUsers($notification);
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.element_created'), 'success');
|
||||
|
||||
$closeModal = '';
|
||||
if (isset($_POST['submitAction']) && $_POST['submitAction'] == 'closeModal') {
|
||||
$closeModal = '?closeModal=true';
|
||||
}
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'/editCanvasItem/'.$id.$closeModal);
|
||||
} else {
|
||||
$this->tpl->setNotification($this->language->__('notification.please_enter_title'), 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['comment']) && isset($params['id'])) {
|
||||
$itemId = (int) $params['id'];
|
||||
|
||||
// Only allow commenting on an item the user can view in their project.
|
||||
if (! $this->blueprintsService->getCanvasItem($itemId, $canvasType)) {
|
||||
return $this->tpl->displayPartial('errors.error404');
|
||||
}
|
||||
|
||||
$values = [
|
||||
'text' => $params['text'],
|
||||
'date' => date('Y-m-d H:i:s'),
|
||||
'userId' => (session('userdata.id')),
|
||||
'moduleId' => $itemId,
|
||||
'commentParent' => ($params['father']),
|
||||
];
|
||||
|
||||
$commentId = $this->commentsRepo->addComment($values, static::CANVAS_NAME.'canvas'.'item');
|
||||
$this->tpl->setNotification($this->language->__('notifications.comment_create_success'), 'success');
|
||||
$values['id'] = $commentId;
|
||||
|
||||
$subject = $this->language->__('email_notifications.canvas_board_comment_created');
|
||||
$actual_link = BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'#/editCanvasItem/'.$itemId;
|
||||
$message = sprintf(
|
||||
$this->language->__('email_notifications.canvas_item__comment_created_message'),
|
||||
session('userdata.name')
|
||||
);
|
||||
|
||||
$notification = app()->make(NotificationModel::class);
|
||||
$notification->url = [
|
||||
'url' => $actual_link,
|
||||
'text' => $this->language->__('email_notifications.canvas_item_update_cta'),
|
||||
];
|
||||
$notification->entity = $values;
|
||||
$notification->module = static::CANVAS_NAME.'canvas';
|
||||
$notification->action = 'commented';
|
||||
$notification->projectId = session('currentProject');
|
||||
$notification->subject = $subject;
|
||||
$notification->authorId = session('userdata.id');
|
||||
$notification->message = $message;
|
||||
|
||||
$this->projectService->notifyProjectUsers($notification);
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas'.'/editCanvasItem/'.$itemId);
|
||||
}
|
||||
|
||||
$allProjectMilestones = $this->ticketService->getAllMilestones(['sprint' => '', 'type' => 'milestone', 'currentProject' => session('currentProject')]);
|
||||
$this->tpl->assign('milestones', $allProjectMilestones);
|
||||
$this->tpl->assign('canvasTypes', $this->canvasRepo->getCanvasTypes());
|
||||
$this->tpl->assign('statusLabels', $this->canvasRepo->getStatusLabels());
|
||||
$this->tpl->assign('relatesLabels', $this->canvasRepo->getRelatesLabels());
|
||||
$this->tpl->assign('dataLabels', $this->canvasRepo->getDataLabels());
|
||||
if (isset($params['id'])) {
|
||||
$canvasItemId = (int) $params['id'];
|
||||
// VIEW-authorize before re-displaying; false = missing/foreign/unauthorized -> 404.
|
||||
$canvasItem = $this->blueprintsService->getCanvasItem($canvasItemId, $canvasType);
|
||||
if (! $canvasItem) {
|
||||
return $this->tpl->displayPartial('errors.error404');
|
||||
}
|
||||
$comments = $this->commentsRepo->getComments(static::CANVAS_NAME.'canvas'.'item', $canvasItemId);
|
||||
$this->tpl->assign('canvasItem', $canvasItem);
|
||||
} else {
|
||||
$value = [
|
||||
'id' => '',
|
||||
'box' => $params['box'],
|
||||
'author' => session('userdata.id'),
|
||||
'description' => '',
|
||||
'status' => array_key_first($this->canvasRepo->getStatusLabels()),
|
||||
'relates' => array_key_first($this->canvasRepo->getRelatesLabels()),
|
||||
'assumptions' => '',
|
||||
'data' => '',
|
||||
'conclusion' => '',
|
||||
'milestoneHeadline' => '',
|
||||
'milestoneId' => '',
|
||||
];
|
||||
$comments = [];
|
||||
$this->tpl->assign('canvasItem', $value);
|
||||
}
|
||||
$this->tpl->assign('comments', $comments);
|
||||
$this->tpl->assign('currentCanvas', (int) session('current'.strtoupper(static::CANVAS_NAME).'Canvas'));
|
||||
|
||||
return $this->tpl->displayPartial(static::CANVAS_NAME.'canvas'.'.canvasDialog');
|
||||
}
|
||||
|
||||
/**
|
||||
* put - handle put requests
|
||||
*/
|
||||
public function put($params) {}
|
||||
|
||||
/**
|
||||
* delete - handle delete requests
|
||||
*/
|
||||
public function delete($params) {}
|
||||
}
|
||||
170
app/Domain/Canvas/Controllers/Export.php
Normal file
170
app/Domain/Canvas/Controllers/Export.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Canvas\Controllers;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Support\Str;
|
||||
use Leantime\Core\Auth\Permissions\RequiresPermission;
|
||||
use Leantime\Core\Configuration\Environment as EnvironmentCore;
|
||||
use Leantime\Core\Controller\Controller;
|
||||
use Leantime\Core\Language as LanguageCore;
|
||||
use Leantime\Domain\Blueprints\Permissions\BlueprintsPermissions;
|
||||
use Leantime\Domain\Blueprints\Services\Blueprints as BlueprintsService;
|
||||
use Leantime\Domain\Projects\Services\Projects as ProjectService;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Exports a canvas board as an XML file.
|
||||
*/
|
||||
class Export extends Controller
|
||||
{
|
||||
/**
|
||||
* Constant that must be redefined by subclasses.
|
||||
*/
|
||||
protected const CANVAS_NAME = '??';
|
||||
|
||||
protected const CANVAS_TYPE = 'canvas';
|
||||
|
||||
protected EnvironmentCore $config;
|
||||
|
||||
protected LanguageCore $language;
|
||||
|
||||
protected mixed $canvasRepo;
|
||||
|
||||
protected BlueprintsService $blueprintsService;
|
||||
|
||||
protected array $canvasTypes;
|
||||
|
||||
protected array $statusLabels;
|
||||
|
||||
protected array $relatesLabels;
|
||||
|
||||
protected array $dataLabels;
|
||||
|
||||
/**
|
||||
* Initializes dependencies.
|
||||
*/
|
||||
public function init(
|
||||
EnvironmentCore $config,
|
||||
LanguageCore $language,
|
||||
): void {
|
||||
$this->config = $config;
|
||||
$this->language = $language;
|
||||
$this->blueprintsService = app()->make(BlueprintsService::class);
|
||||
|
||||
$canvasName = Str::studly(static::CANVAS_NAME).static::CANVAS_TYPE;
|
||||
$repoName = app()->getNamespace()."Domain\\$canvasName\\Repositories\\$canvasName";
|
||||
$this->canvasRepo = app()->make($repoName);
|
||||
|
||||
$this->canvasTypes = $this->canvasRepo->getCanvasTypes();
|
||||
$this->statusLabels = $this->canvasRepo->getStatusLabels();
|
||||
$this->relatesLabels = $this->canvasRepo->getRelatesLabels();
|
||||
$this->dataLabels = $this->canvasRepo->getDataLabels();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and serves an XML export of the canvas.
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::VIEW, entityScoped: true)]
|
||||
public function get(array $params): Response
|
||||
{
|
||||
$canvasId = (int) ($params['id'] ?? $_GET['id'] ?? 0);
|
||||
|
||||
if ($canvasId <= 0 && session()->exists('current'.strtoupper(static::CANVAS_NAME).'Canvas')) {
|
||||
$canvasId = (int) session('current'.strtoupper(static::CANVAS_NAME).'Canvas');
|
||||
}
|
||||
|
||||
if ($canvasId <= 0) {
|
||||
return new Response;
|
||||
}
|
||||
|
||||
$exportData = $this->export($canvasId);
|
||||
|
||||
clearstatcache();
|
||||
|
||||
$response = new Response($exportData);
|
||||
$response->headers->set('Content-type', 'application/xml');
|
||||
$response->headers->set('Content-Disposition', 'attachment; filename="'.static::CANVAS_NAME.static::CANVAS_TYPE.'-'.$canvasId.'.xml"');
|
||||
$response->headers->set('Cache-Control', 'no-cache');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates XML data for the given canvas.
|
||||
*
|
||||
* @param int $id Canvas identifier
|
||||
* @return string XML data
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function export(int $id): string
|
||||
{
|
||||
// getBoard authorizes VIEW against the board's real project and returns false for a
|
||||
// missing/foreign/unauthorized board (export is reachable by arbitrary board id).
|
||||
$canvasAry = $this->blueprintsService->getBoard($id, static::CANVAS_NAME.static::CANVAS_TYPE);
|
||||
! empty($canvasAry) || throw new Exception("Cannot find canvas with id '$id'");
|
||||
|
||||
$projectId = $canvasAry[0]['projectId'];
|
||||
$recordsAry = $this->blueprintsService->getBoardItems($id, static::CANVAS_NAME.static::CANVAS_TYPE, static::CANVAS_NAME.'canvasitem');
|
||||
|
||||
$projectService = app()->make(ProjectService::class);
|
||||
$projectAry = $projectService->getProject($projectId);
|
||||
! empty($projectAry) || throw new Exception("Cannot retrieve project id '$projectId'");
|
||||
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'.PHP_EOL.PHP_EOL;
|
||||
$xml .= $this->xmlExport(static::CANVAS_NAME.static::CANVAS_TYPE, $canvasAry[0]['title'], $recordsAry);
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates XML markup for canvas data.
|
||||
*
|
||||
* @param string $canvasKey Encoded canvas name
|
||||
* @param string $canvasTitle Canvas title
|
||||
* @param array $recordsAry Array of canvas entry records
|
||||
* @param int $indent Indent level to use
|
||||
* @return string XML data
|
||||
*/
|
||||
protected function xmlExport(string $canvasKey, string $canvasTitle, array $recordsAry, int $indent = 0): string
|
||||
{
|
||||
$is = str_repeat(' ', 4 * $indent);
|
||||
$tab = str_repeat(' ', 4);
|
||||
$xml = $is.'<canvas key="'.$canvasKey.'">'.PHP_EOL;
|
||||
$xml .= $is.$tab.'<title>'.$canvasTitle.'</title>'.PHP_EOL;
|
||||
$xml .= $is.$tab.'<content>'.PHP_EOL;
|
||||
|
||||
foreach ($this->canvasTypes as $key => $data) {
|
||||
$xml .= $is.$tab.$tab.'<element key="'.$key.'">'.PHP_EOL;
|
||||
|
||||
foreach ($recordsAry as $record) {
|
||||
if ($record['box'] === $key) {
|
||||
$xml .= $is.$tab.$tab.$tab.'<item>'.PHP_EOL;
|
||||
$xml .= $is.$tab.$tab.$tab.$tab.'<created>'.($record['created'] ?? '').'</created>'.PHP_EOL;
|
||||
$xml .= $is.$tab.$tab.$tab.$tab.'<modified>'.($record['modified'] ?? '').'</modified>'.PHP_EOL;
|
||||
$xml .= $is.$tab.$tab.$tab.$tab.'<author id="'.$record['author'].'" firstname="'.($record['authorFirstname'] ?? '').'" '.
|
||||
'lastname="'.($record['authorLastname'] ?? '').'"/>'.PHP_EOL;
|
||||
|
||||
$xml .= $is.$tab.$tab.$tab.$tab.'<description>'.($record['description'] ?? '').'</description>'.PHP_EOL;
|
||||
$xml .= $is.$tab.$tab.$tab.$tab.'<status key="'.($record['status'] ?? '').'" />'.PHP_EOL;
|
||||
$xml .= $is.$tab.$tab.$tab.$tab.'<relates key="'.($record['relates'] ?? '').'" />'.PHP_EOL;
|
||||
$xml .= $is.$tab.$tab.$tab.$tab.'<assumptions>'.($record['assumptions'] ?? '').'</assumptions>'.PHP_EOL;
|
||||
$xml .= $is.$tab.$tab.$tab.$tab.'<data>'.($record['data'] ?? '').'</data>'.PHP_EOL;
|
||||
$xml .= $is.$tab.$tab.$tab.$tab.'<conclusion>'.($record['conclusion'] ?? '').'</conclusion>'.PHP_EOL;
|
||||
$xml .= $is.$tab.$tab.$tab.'</item>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
$xml .= $is.$tab.$tab.'</element>'.PHP_EOL;
|
||||
}
|
||||
$xml .= $is.$tab.'</content>'.PHP_EOL;
|
||||
$xml .= $is.'</canvas>'.PHP_EOL;
|
||||
|
||||
return $xml;
|
||||
}
|
||||
}
|
||||
398
app/Domain/Canvas/Controllers/ShowCanvas.php
Normal file
398
app/Domain/Canvas/Controllers/ShowCanvas.php
Normal file
@@ -0,0 +1,398 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Canvas\Controllers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Leantime\Core\Auth\Permissions\RequiresPermission;
|
||||
use Leantime\Core\Controller\Controller;
|
||||
use Leantime\Core\Controller\Frontcontroller;
|
||||
use Leantime\Core\Mailer as MailerCore;
|
||||
use Leantime\Domain\Blueprints\Permissions\BlueprintsPermissions;
|
||||
use Leantime\Domain\Blueprints\Services\Blueprints as BlueprintsService;
|
||||
use Leantime\Domain\Canvas\Services\Canvas as CanvaService;
|
||||
use Leantime\Domain\Projects\Services\Projects as ProjectService;
|
||||
use Leantime\Domain\Queue\Repositories\Queue as QueueRepository;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ShowCanvas extends Controller
|
||||
{
|
||||
/**
|
||||
* Constant that must be redefined by subclasses.
|
||||
*/
|
||||
protected const CANVAS_NAME = '??';
|
||||
|
||||
private ProjectService $projectService;
|
||||
|
||||
private BlueprintsService $blueprintsService;
|
||||
|
||||
private object $canvasRepo;
|
||||
|
||||
/**
|
||||
* Initializes dependencies.
|
||||
*/
|
||||
public function init(ProjectService $projectService): void
|
||||
{
|
||||
$this->projectService = $projectService;
|
||||
$this->blueprintsService = app()->make(BlueprintsService::class);
|
||||
$canvasName = Str::studly(static::CANVAS_NAME).'canvas';
|
||||
$repoName = app()->getNamespace()."Domain\\$canvasName\\Repositories\\$canvasName";
|
||||
$this->canvasRepo = app()->make($repoName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the canvas board view.
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::VIEW)]
|
||||
public function get(array $params): Response
|
||||
{
|
||||
$allCanvas = $this->canvasRepo->getAllCanvas(session('currentProject'));
|
||||
$currentCanvasId = $this->resolveCurrentCanvasId($allCanvas, $params);
|
||||
|
||||
if (isset($_REQUEST['searchCanvas'])) {
|
||||
$currentCanvasId = (int) $_REQUEST['searchCanvas'];
|
||||
session(['current'.strtoupper(static::CANVAS_NAME).'Canvas' => $currentCanvasId]);
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas/showCanvas/');
|
||||
}
|
||||
|
||||
$this->assignTemplateVars($currentCanvasId, $allCanvas);
|
||||
|
||||
if (! isset($_GET['raw'])) {
|
||||
return $this->tpl->display(static::CANVAS_NAME.'canvas.showCanvas');
|
||||
}
|
||||
|
||||
return new Response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles canvas mutations (create, edit, clone, merge, import).
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::EDIT)]
|
||||
public function post(array $params): Response
|
||||
{
|
||||
$allCanvas = $this->canvasRepo->getAllCanvas(session('currentProject'));
|
||||
$currentCanvasId = $this->resolveCurrentCanvasId($allCanvas, $params);
|
||||
|
||||
if (isset($_POST['newCanvas'])) {
|
||||
$result = $this->handleNewCanvas($currentCanvasId, $allCanvas);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['editCanvas']) && $currentCanvasId > 0) {
|
||||
$result = $this->handleEditCanvas($currentCanvasId);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['cloneCanvas']) && $currentCanvasId > 0) {
|
||||
$result = $this->handleCloneCanvas($currentCanvasId, $allCanvas);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['mergeCanvas']) && $currentCanvasId > 0) {
|
||||
$result = $this->handleMergeCanvas($currentCanvasId);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['importCanvas'])) {
|
||||
$result = $this->handleImportCanvas($currentCanvasId, $allCanvas);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
$allCanvas = $this->canvasRepo->getAllCanvas(session('currentProject'));
|
||||
$this->assignTemplateVars($currentCanvasId, $allCanvas);
|
||||
|
||||
if (! isset($_GET['raw'])) {
|
||||
return $this->tpl->display(static::CANVAS_NAME.'canvas.showCanvas');
|
||||
}
|
||||
|
||||
return new Response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the current canvas ID from session, GET params, or creates a default.
|
||||
*/
|
||||
private function resolveCurrentCanvasId(array &$allCanvas, array $params): int
|
||||
{
|
||||
$sessionKey = 'current'.strtoupper(static::CANVAS_NAME).'Canvas';
|
||||
|
||||
if (! $allCanvas) {
|
||||
$values = [
|
||||
'title' => $this->language->__('label.board'),
|
||||
'author' => session('userdata.id'),
|
||||
'projectId' => session('currentProject'),
|
||||
];
|
||||
$currentCanvasId = $this->canvasRepo->addCanvas($values);
|
||||
$allCanvas = $this->canvasRepo->getAllCanvas(session('currentProject'));
|
||||
|
||||
return $currentCanvasId;
|
||||
}
|
||||
|
||||
$currentCanvasId = -1;
|
||||
|
||||
if (session()->exists($sessionKey)) {
|
||||
$currentCanvasId = session($sessionKey);
|
||||
|
||||
$found = false;
|
||||
foreach ($allCanvas as $row) {
|
||||
if ($currentCanvasId == $row['id']) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $found) {
|
||||
$currentCanvasId = -1;
|
||||
session([$sessionKey => '']);
|
||||
}
|
||||
} else {
|
||||
session([$sessionKey => '']);
|
||||
}
|
||||
|
||||
if (session($sessionKey) == '') {
|
||||
$currentCanvasId = $allCanvas[0]['id'];
|
||||
session([$sessionKey => $currentCanvasId]);
|
||||
}
|
||||
|
||||
if (isset($params['id'])) {
|
||||
// Only honor an explicit board id that belongs to the CURRENT project's boards
|
||||
// ($allCanvas is project-scoped). A foreign/unknown id must not become the active
|
||||
// board — otherwise assignTemplateVars would read another project's items.
|
||||
$requestedId = (int) $params['id'];
|
||||
$projectBoardIds = array_map(static fn ($row) => (int) $row['id'], $allCanvas);
|
||||
if (in_array($requestedId, $projectBoardIds, true)) {
|
||||
$currentCanvasId = $requestedId;
|
||||
session([$sessionKey => $currentCanvasId]);
|
||||
}
|
||||
}
|
||||
|
||||
return $currentCanvasId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles creating a new canvas board.
|
||||
*/
|
||||
private function handleNewCanvas(int &$currentCanvasId, array &$allCanvas): ?Response
|
||||
{
|
||||
if (! isset($_POST['canvastitle']) || empty($_POST['canvastitle'])) {
|
||||
$this->tpl->setNotification($this->language->__('notification.please_enter_title'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->canvasRepo->existCanvas(session('currentProject'), $_POST['canvastitle'])) {
|
||||
$this->tpl->setNotification($this->language->__('notification.board_exists'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$values = [
|
||||
'title' => $_POST['canvastitle'],
|
||||
'author' => session('userdata.id'),
|
||||
'projectId' => session('currentProject'),
|
||||
];
|
||||
// createBoard authorizes CREATE against the target (current) project.
|
||||
$currentCanvasId = $this->blueprintsService->createBoard($values, static::CANVAS_NAME.'canvas');
|
||||
$allCanvas = $this->canvasRepo->getAllCanvas(session('currentProject'));
|
||||
|
||||
$this->notifyBoardCreated($values['title'], 'notification.board_created', 'email_notifications.canvas_created_message');
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.board_created'), 'success', static::CANVAS_NAME.'board_created');
|
||||
session(['current'.strtoupper(static::CANVAS_NAME).'Canvas' => $currentCanvasId]);
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas/showCanvas/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles editing a canvas board title.
|
||||
*/
|
||||
private function handleEditCanvas(int &$currentCanvasId): ?Response
|
||||
{
|
||||
if (! isset($_POST['canvastitle']) || empty($_POST['canvastitle'])) {
|
||||
$this->tpl->setNotification($this->language->__('notification.please_enter_title'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->canvasRepo->existCanvas(session('currentProject'), $_POST['canvastitle'])) {
|
||||
$this->tpl->setNotification($this->language->__('notification.board_exists'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// renameBoard authorizes EDIT against the board's real project.
|
||||
$currentCanvasId = $this->blueprintsService->renameBoard($currentCanvasId, $_POST['canvastitle'], static::CANVAS_NAME.'canvas');
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.board_edited'), 'success');
|
||||
|
||||
return $this->tpl->displayPartial('canvas.boardDialog');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles cloning a canvas board.
|
||||
*/
|
||||
private function handleCloneCanvas(int &$currentCanvasId, array &$allCanvas): ?Response
|
||||
{
|
||||
if (! isset($_POST['canvastitle']) || empty($_POST['canvastitle'])) {
|
||||
$this->tpl->setNotification($this->language->__('notification.please_enter_title'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->canvasRepo->existCanvas(session('currentProject'), $_POST['canvastitle'])) {
|
||||
$this->tpl->setNotification($this->language->__('notification.board_exists'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// copyBoard authorizes VIEW on the source board's project and CREATE on the target.
|
||||
$currentCanvasId = $this->blueprintsService->copyBoard(
|
||||
$currentCanvasId,
|
||||
(int) session('currentProject'),
|
||||
(int) session('userdata.id'),
|
||||
$_POST['canvastitle'],
|
||||
static::CANVAS_NAME.'canvas'
|
||||
);
|
||||
$allCanvas = $this->canvasRepo->getAllCanvas(session('currentProject'));
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.board_copied'), 'success');
|
||||
session(['current'.strtoupper(static::CANVAS_NAME).'Canvas' => $currentCanvasId]);
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas/showCanvas/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles merging two canvas boards.
|
||||
*/
|
||||
private function handleMergeCanvas(int $currentCanvasId): ?Response
|
||||
{
|
||||
if (! isset($_POST['canvasid']) || $_POST['canvasid'] <= 0) {
|
||||
$this->tpl->setNotification($this->language->__('notification.internal_error'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// mergeBoard authorizes EDIT on the target board's project and VIEW on the source's.
|
||||
$status = $this->blueprintsService->mergeBoard($currentCanvasId, (int) $_POST['canvasid'], static::CANVAS_NAME.'canvas');
|
||||
|
||||
if ($status) {
|
||||
$this->tpl->setNotification($this->language->__('notification.board_merged'), 'success');
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas/showCanvas/');
|
||||
}
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.merge_error'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles importing a canvas from an XML file.
|
||||
*/
|
||||
private function handleImportCanvas(int &$currentCanvasId, array &$allCanvas): ?Response
|
||||
{
|
||||
if (! isset($_FILES['canvasfile']) || $_FILES['canvasfile']['error'] !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$uploadfile = tempnam(sys_get_temp_dir(), 'leantime.').'.xml';
|
||||
|
||||
if (! move_uploaded_file($_FILES['canvasfile']['tmp_name'], $uploadfile)) {
|
||||
$this->tpl->setNotification($this->language->__('notification.board_import_failed'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$services = app()->make(CanvaService::class);
|
||||
$importCanvasId = $services->import(
|
||||
$uploadfile,
|
||||
static::CANVAS_NAME.'canvas',
|
||||
projectId: session('currentProject'),
|
||||
authorId: session('userdata.id')
|
||||
);
|
||||
unlink($uploadfile);
|
||||
|
||||
if ($importCanvasId === false) {
|
||||
$this->tpl->setNotification($this->language->__('notification.board_import_failed'), 'error');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$currentCanvasId = $importCanvasId;
|
||||
$allCanvas = $this->canvasRepo->getAllCanvas(session('currentProject'));
|
||||
session(['current'.strtoupper(static::CANVAS_NAME).'Canvas' => $currentCanvasId]);
|
||||
|
||||
$canvas = $this->blueprintsService->getBoard($currentCanvasId, static::CANVAS_NAME.'canvas');
|
||||
$this->notifyBoardCreated(
|
||||
strip_tags($canvas !== false ? ($canvas[0]['title'] ?? '') : ''),
|
||||
'notification.board_imported',
|
||||
'email_notifications.canvas_imported_message'
|
||||
);
|
||||
|
||||
$this->tpl->setNotification($this->language->__('notification.board_imported'), 'success');
|
||||
|
||||
return Frontcontroller::redirect(BASE_URL.'/'.static::CANVAS_NAME.'canvas/showCanvas/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends board creation/import notifications to project users.
|
||||
*/
|
||||
private function notifyBoardCreated(string $title, string $subjectKey, string $messageKey): void
|
||||
{
|
||||
$mailer = app()->make(MailerCore::class);
|
||||
$users = $this->projectService->getUsersToNotify(session('currentProject'));
|
||||
|
||||
$mailer->setSubject($this->language->__($subjectKey));
|
||||
$message = sprintf(
|
||||
$this->language->__($messageKey),
|
||||
session('userdata.name'),
|
||||
"<a href='".CURRENT_URL."'>".strip_tags($title).'</a>'
|
||||
);
|
||||
$mailer->setHtml($message);
|
||||
|
||||
$queue = app()->make(QueueRepository::class);
|
||||
$queue->queueMessageToUsers(
|
||||
$users,
|
||||
$message,
|
||||
$this->language->__($subjectKey),
|
||||
session('currentProject')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns common template variables.
|
||||
*/
|
||||
private function assignTemplateVars(int $currentCanvasId, array $allCanvas): void
|
||||
{
|
||||
$filter['status'] = $_GET['filter_status'] ?? (session('filter_status') ?? 'all');
|
||||
session(['filter_status' => $filter['status']]);
|
||||
$filter['relates'] = $_GET['filter_relates'] ?? (session('filter_relates') ?? 'all');
|
||||
session(['filter_relates' => $filter['relates']]);
|
||||
|
||||
$this->tpl->assign('filter', $filter);
|
||||
$this->tpl->assign('currentCanvas', $currentCanvasId);
|
||||
$this->tpl->assign('canvasIcon', $this->canvasRepo->getIcon());
|
||||
$this->tpl->assign('canvasTypes', $this->canvasRepo->getCanvasTypes());
|
||||
$this->tpl->assign('statusLabels', $this->canvasRepo->getStatusLabels());
|
||||
$this->tpl->assign('relatesLabels', $this->canvasRepo->getRelatesLabels());
|
||||
$this->tpl->assign('dataLabels', $this->canvasRepo->getDataLabels());
|
||||
$this->tpl->assign('disclaimer', $this->canvasRepo->getDisclaimer());
|
||||
$this->tpl->assign('allCanvas', $allCanvas);
|
||||
// getBoardItems authorizes VIEW against the board's real project; [] for foreign/unknown.
|
||||
$this->tpl->assign('canvasItems', $this->blueprintsService->getBoardItems($currentCanvasId, static::CANVAS_NAME.'canvas', static::CANVAS_NAME.'canvasitem'));
|
||||
$this->tpl->assign('users', $this->projectService->getUsersAssignedToProject(session('currentProject')));
|
||||
}
|
||||
}
|
||||
242
app/Domain/Canvas/Js/canvasController.js
Normal file
242
app/Domain/Canvas/Js/canvasController.js
Normal file
@@ -0,0 +1,242 @@
|
||||
leantime.canvasController = (function () {
|
||||
|
||||
var canvasName = '';
|
||||
|
||||
var setCanvasName = function (name) {
|
||||
canvasName = name;
|
||||
};
|
||||
|
||||
var initFilterBar = function () {
|
||||
|
||||
jQuery(window).bind("load", function () {
|
||||
jQuery(".loading").fadeOut();
|
||||
jQuery(".filterBar .row-fluid").css("opacity", "1");
|
||||
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
var initCanvasLinks = function () {
|
||||
|
||||
jQuery(".addCanvasLink").nyroModal();
|
||||
|
||||
jQuery(".editCanvasLink").click(function () {
|
||||
|
||||
jQuery('#editCanvas').modal('show');
|
||||
|
||||
});
|
||||
|
||||
jQuery(".cloneCanvasLink").click(function () {
|
||||
|
||||
jQuery('#cloneCanvas').modal('show');
|
||||
|
||||
});
|
||||
|
||||
jQuery(".mergeCanvasLink").click(function () {
|
||||
|
||||
jQuery('#mergeCanvas').modal('show');
|
||||
|
||||
});
|
||||
|
||||
jQuery(".importCanvasLink").click(function () {
|
||||
|
||||
jQuery('#importCanvas').modal('show');
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
var closeModal = false;
|
||||
|
||||
//Variables
|
||||
var canvasoptions = {
|
||||
sizes: {
|
||||
minW: 700,
|
||||
minH: 1000,
|
||||
},
|
||||
resizable: true,
|
||||
autoSizable: true,
|
||||
callbacks: {
|
||||
beforeShowCont: function () {
|
||||
jQuery(".showDialogOnLoad").show();
|
||||
if (closeModal == true) {
|
||||
closeModal = false;
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
afterShowCont: function () {
|
||||
window.htmx.process('.nyroModalCont');
|
||||
jQuery("." + canvasName + "CanvasModal, #commentForm, #commentForm .deleteComment, ." + canvasName + "CanvasMilestone .deleteMilestone").nyroModal(canvasoptions);
|
||||
|
||||
},
|
||||
beforeClose: function () {
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
titleFromIframe: true
|
||||
|
||||
};
|
||||
|
||||
//Functions
|
||||
|
||||
var _initModals = function () {
|
||||
jQuery("." + canvasName + "CanvasModal, #commentForm, #commentForm .deleteComment, ." + canvasName + "CanvasMilestone .deleteMilestone").nyroModal(canvasoptions);
|
||||
};
|
||||
|
||||
var openModalManually = function (url) {
|
||||
jQuery.nmManual(url, canvasoptions);
|
||||
};
|
||||
|
||||
var toggleMilestoneSelectors = function (trigger) {
|
||||
if (trigger == 'existing') {
|
||||
jQuery('#newMilestone, #milestoneSelectors').hide('fast');
|
||||
jQuery('#existingMilestone').show();
|
||||
_initModals();
|
||||
}
|
||||
if (trigger == 'new') {
|
||||
jQuery('#newMilestone').show();
|
||||
jQuery('#existingMilestone, #milestoneSelectors').hide('fast');
|
||||
_initModals();
|
||||
}
|
||||
|
||||
if (trigger == 'hide') {
|
||||
jQuery('#newMilestone, #existingMilestone').hide('fast');
|
||||
jQuery('#milestoneSelectors').show('fast');
|
||||
}
|
||||
};
|
||||
|
||||
var setCloseModal = function () {
|
||||
closeModal = true;
|
||||
};
|
||||
|
||||
var initUserDropdown = function () {
|
||||
|
||||
jQuery("body").on(
|
||||
"click",
|
||||
".userDropdown .dropdown-menu a",
|
||||
function () {
|
||||
|
||||
var dataValue = jQuery(this).attr("data-value").split("_");
|
||||
var dataLabel = jQuery(this).attr('data-label');
|
||||
|
||||
if (dataValue.length == 3) {
|
||||
var canvasId = dataValue[0];
|
||||
var userId = dataValue[1];
|
||||
var profileImageId = dataValue[2];
|
||||
|
||||
jQuery.ajax(
|
||||
{
|
||||
type: 'PATCH',
|
||||
url: leantime.appUrl + '/api/' + canvasName + 'canvas',
|
||||
data:
|
||||
{
|
||||
id : canvasId,
|
||||
author:userId
|
||||
}
|
||||
}
|
||||
).done(
|
||||
function () {
|
||||
jQuery("#userDropdownMenuLink" + canvasId + " span.text span#userImage" + canvasId + " img").attr("src", leantime.appUrl + "/users/profileImage/" + encodeURIComponent(userId));
|
||||
jQuery.growl({message: leantime.i18n.__("short_notifications.user_updated"), style: "success"});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var initStatusDropdown = function () {
|
||||
|
||||
jQuery("body").on(
|
||||
"click",
|
||||
".statusDropdown .dropdown-menu a",
|
||||
function () {
|
||||
|
||||
var dataValue = jQuery(this).attr("data-value").split("/");
|
||||
var dataLabel = jQuery(this).attr('data-label');
|
||||
|
||||
if (dataValue.length == 2) {
|
||||
var canvasItemId = dataValue[0];
|
||||
var status = dataValue[1];
|
||||
var statusClass = jQuery(this).attr('class');
|
||||
|
||||
|
||||
jQuery.ajax(
|
||||
{
|
||||
type: 'PATCH',
|
||||
url: leantime.appUrl + '/api/' + canvasName + 'canvas',
|
||||
data:
|
||||
{
|
||||
id : canvasItemId,
|
||||
status: status
|
||||
}
|
||||
}
|
||||
).done(
|
||||
function () {
|
||||
jQuery("#statusDropdownMenuLink" + canvasItemId + " span.text").text(dataLabel);
|
||||
jQuery("#statusDropdownMenuLink" + canvasItemId).removeClass().addClass(statusClass + " dropdown-toggle f-left status ");
|
||||
jQuery.growl({message: leantime.i18n.__("short_notifications.status_updated")});
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
var initRelatesDropdown = function () {
|
||||
|
||||
jQuery("body").on(
|
||||
"click",
|
||||
".relatesDropdown .dropdown-menu a",
|
||||
function () {
|
||||
|
||||
var dataValue = jQuery(this).attr("data-value").split("/");
|
||||
var dataLabel = jQuery(this).attr('data-label');
|
||||
|
||||
if (dataValue.length == 2) {
|
||||
var canvasItemId = dataValue[0];
|
||||
var relates = dataValue[1];
|
||||
var relatesClass = jQuery(this).attr('class');
|
||||
|
||||
|
||||
jQuery.ajax(
|
||||
{
|
||||
type: 'PATCH',
|
||||
url: leantime.appUrl + '/api/' + canvasName + 'canvas',
|
||||
data:
|
||||
{
|
||||
id : canvasItemId,
|
||||
relates: relates
|
||||
}
|
||||
}
|
||||
).done(
|
||||
function () {
|
||||
jQuery("#relatesDropdownMenuLink" + canvasItemId + " span.text").text(dataLabel);
|
||||
jQuery("#relatesDropdownMenuLink" + canvasItemId).removeClass().addClass(relatesClass + " dropdown-toggle f-left relates ");
|
||||
jQuery.growl({message: leantime.i18n.__("short_notifications.relates_updated")});
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
// Make public what you want to have public, everything else is private
|
||||
return {
|
||||
setCanvasName:setCanvasName,
|
||||
initFilterBar:initFilterBar,
|
||||
initCanvasLinks:initCanvasLinks,
|
||||
initUserDropdown:initUserDropdown,
|
||||
initStatusDropdown:initStatusDropdown,
|
||||
initRelatesDropdown:initRelatesDropdown,
|
||||
setCloseModal:setCloseModal,
|
||||
toggleMilestoneSelectors:toggleMilestoneSelectors,
|
||||
openModalManually:openModalManually
|
||||
};
|
||||
|
||||
})();
|
||||
463
app/Domain/Canvas/Repositories/Canvas.php
Normal file
463
app/Domain/Canvas/Repositories/Canvas.php
Normal file
@@ -0,0 +1,463 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* canvas class - DEPRECATED backwards-compatibility adapter.
|
||||
*/
|
||||
|
||||
namespace Leantime\Domain\Canvas\Repositories;
|
||||
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Leantime\Core\Db\DatabaseHelper;
|
||||
use Leantime\Core\Db\Db as DbCore;
|
||||
use Leantime\Core\Language as LanguageCore;
|
||||
use Leantime\Domain\Blueprints\Repositories\Blueprints as BlueprintsRepository;
|
||||
use Leantime\Domain\Tickets\Repositories\Tickets;
|
||||
|
||||
/**
|
||||
* Thin backwards-compatibility shim over the Blueprints repository.
|
||||
*
|
||||
* The canvas system was consolidated into Leantime\Domain\Blueprints. This class
|
||||
* is kept ONLY so plugins (and any code) that still extend or instantiate the old
|
||||
* canvas repository keep working: its data methods now delegate to the Blueprints
|
||||
* repository, deriving the canvas type from the subclass CANVAS_NAME constant.
|
||||
*
|
||||
* @deprecated Use Leantime\Domain\Blueprints\Repositories\Blueprints instead.
|
||||
* Do not build new features on this class.
|
||||
*/
|
||||
class Canvas extends BlueprintsRepository
|
||||
{
|
||||
/**
|
||||
* Constant that must be redefined
|
||||
*/
|
||||
protected const CANVAS_NAME = '??';
|
||||
|
||||
/***
|
||||
* icon - Icon associated with canvas (must be extended)
|
||||
*
|
||||
* @access protected
|
||||
* @var string Fontawesome icone
|
||||
*/
|
||||
protected string $icon = 'fa-x';
|
||||
|
||||
/***
|
||||
* disclaimer - Disclaimer (may be extended)
|
||||
*
|
||||
* @access protected
|
||||
* @var string Disclaimer (including href)
|
||||
*/
|
||||
protected string $disclaimer = '';
|
||||
|
||||
/**
|
||||
* canvasTypes - Canvas elements / boxes (must be extended)
|
||||
*
|
||||
* @acces protected
|
||||
*/
|
||||
protected array $canvasTypes = [
|
||||
// '??_' => [ 'icon' => 'fa-????', 'title' => 'box.??.????' ],
|
||||
];
|
||||
|
||||
/**
|
||||
* statusLabels - Status labels (may be extended)
|
||||
*
|
||||
* @acces protected
|
||||
*/
|
||||
protected array $statusLabels = [
|
||||
'status_draft' => ['icon' => 'fa-circle-question', 'color' => 'blue', 'title' => 'status.draft', 'dropdown' => 'info', 'active' => true],
|
||||
'status_review' => ['icon' => 'fa-circle-exclamation', 'color' => 'orange', 'title' => 'status.review', 'dropdown' => 'warning', 'active' => true],
|
||||
'status_valid' => ['icon' => 'fa-circle-check', 'color' => 'green', 'title' => 'status.valid', 'dropdown' => 'success', 'active' => true],
|
||||
'status_hold' => ['icon' => 'fa-circle-h', 'color' => 'red', 'title' => 'status.hold', 'dropdown' => 'danger', 'active' => true],
|
||||
'status_invalid' => ['icon' => 'fa-circle-xmark', 'color' => 'red', 'title' => 'status.invalid', 'dropdown' => 'danger', 'active' => true],
|
||||
];
|
||||
|
||||
/**
|
||||
* relatesLabels - Relates to label (same structure as `statusLabels`)
|
||||
*
|
||||
* @acces public
|
||||
*/
|
||||
protected array $relatesLabels = [
|
||||
'relates_none' => ['icon' => 'fa-border-none', 'color' => 'grey', 'title' => 'relates.none', 'dropdown' => 'default', 'active' => true],
|
||||
'relates_customers' => ['icon' => 'fa-users', 'color' => 'green', 'title' => 'relates.customers', 'dropdown' => 'success', 'active' => true],
|
||||
'relates_offerings' => ['icon' => 'fa-barcode', 'color' => 'red', 'title' => 'relates.offerings', 'dropdown' => 'danger', 'active' => true],
|
||||
'relates_capabilities' => ['icon' => 'fa-pen-ruler', 'color' => 'blue', 'title' => 'relates.capabilities', 'dropdown' => 'info', 'active' => true],
|
||||
'relates_financials' => ['icon' => 'fa-money-bill', 'color' => 'yellow', 'title' => 'relates.financials', 'dropdown' => 'warning', 'active' => true],
|
||||
'relates_markets' => ['icon' => 'fa-shop', 'color' => 'brown', 'title' => 'relates.markets', 'dropdown' => 'default', 'active' => true],
|
||||
'relates_environment' => ['icon' => 'fa-tree', 'color' => 'darkgreen', 'title' => 'relates.environment', 'dropdown' => 'default', 'active' => true],
|
||||
'relates_firm' => ['icon' => 'fa-building', 'color' => 'darkblue', 'title' => 'relates.firm', 'dropdown' => 'info', 'active' => true],
|
||||
];
|
||||
|
||||
/**
|
||||
* dataLabels - Data labels (may be extended)
|
||||
*
|
||||
* @acces protected
|
||||
*/
|
||||
protected array $dataLabels = [
|
||||
1 => ['title' => 'label.assumptions', 'field' => 'assumptions', 'active' => true],
|
||||
2 => ['title' => 'label.data', 'field' => 'data', 'active' => true],
|
||||
3 => ['title' => 'label.conclusion', 'field' => 'conclusion', 'active' => true],
|
||||
];
|
||||
|
||||
public ?object $result = null;
|
||||
|
||||
public ?object $tickets = null;
|
||||
|
||||
protected ?DbCore $db = null;
|
||||
|
||||
protected ConnectionInterface $connection;
|
||||
|
||||
protected DatabaseHelper $dbHelper;
|
||||
|
||||
private LanguageCore $language;
|
||||
|
||||
/**
|
||||
* __construct - get db connection and initialise the underlying Blueprints repository
|
||||
*/
|
||||
public function __construct(
|
||||
DbCore $db,
|
||||
LanguageCore $language,
|
||||
Tickets $ticketRepo,
|
||||
DatabaseHelper $dbHelper
|
||||
) {
|
||||
// Initialise the Blueprints parent so the delegated methods have a connection.
|
||||
parent::__construct($db, $ticketRepo, $dbHelper);
|
||||
|
||||
// Keep local copies for the config accessors and goal-specific queries below.
|
||||
$this->db = $db;
|
||||
$this->connection = $db->getConnection();
|
||||
$this->language = $language;
|
||||
$this->dbHelper = $dbHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* getIcon() - Retrieve canvas icon
|
||||
*
|
||||
* @return string Canvas icon
|
||||
*/
|
||||
public function getIcon(): string
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* getDisclaimer() - Retrieve disclaimer
|
||||
*
|
||||
* @return string Canvas disclaimer
|
||||
*/
|
||||
public function getDisclaimer(): string
|
||||
{
|
||||
if (empty($this->disclaimer)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->language->__($this->disclaimer);
|
||||
}
|
||||
|
||||
/**
|
||||
* getCanvasTypes() - Retrieve translated canvaas items
|
||||
*
|
||||
* @return array Array of data
|
||||
*/
|
||||
public function getCanvasTypes(): array
|
||||
{
|
||||
$canvasTypes = $this->canvasTypes;
|
||||
foreach ($canvasTypes as $key => $data) {
|
||||
if (isset($data['title'])) {
|
||||
$canvasTypes[$key]['title'] = $this->language->__($data['title']);
|
||||
}
|
||||
}
|
||||
|
||||
return $canvasTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* getStatusLabels() - Retrieve translated status labels
|
||||
*
|
||||
* @return array Array of data
|
||||
*/
|
||||
public function getStatusLabels(): array
|
||||
{
|
||||
$statusLabels = $this->statusLabels;
|
||||
|
||||
foreach ($statusLabels as $key => $data) {
|
||||
if (isset($data['title'])) {
|
||||
$statusLabels[$key]['title'] = $this->language->__($data['title']);
|
||||
}
|
||||
}
|
||||
|
||||
return $statusLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* getRelatesLabels() - Retrieve translated relates labels
|
||||
*
|
||||
* @return array Array of data
|
||||
*/
|
||||
public function getRelatesLabels(): array
|
||||
{
|
||||
$relatesLabels = $this->relatesLabels;
|
||||
foreach ($relatesLabels as $key => $data) {
|
||||
if (isset($data['title'])) {
|
||||
$relatesLabels[$key]['title'] = $this->language->__($data['title']);
|
||||
}
|
||||
}
|
||||
|
||||
return $relatesLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* getDataLabels() - Retrieve translated data labels
|
||||
*
|
||||
* @return array Array of data
|
||||
*/
|
||||
public function getDataLabels(): array
|
||||
{
|
||||
$dataLabels = $this->dataLabels;
|
||||
foreach ($dataLabels as $key => $data) {
|
||||
if (isset($data['title'])) {
|
||||
$dataLabels[$key]['title'] = $this->language->__($data['title']);
|
||||
}
|
||||
}
|
||||
|
||||
return $dataLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated delegate to Blueprints repository
|
||||
*/
|
||||
public function getAllCanvas($projectId, $type = null): false|array
|
||||
{
|
||||
$canvasType = ($type === null || $type === '') ? static::CANVAS_NAME.'canvas' : $type;
|
||||
|
||||
return parent::getAllCanvas((int) $projectId, $canvasType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated delegate to Blueprints repository
|
||||
*/
|
||||
public function getSingleCanvas($canvasId, $canvasType = null): false|array
|
||||
{
|
||||
return parent::getSingleCanvas((int) $canvasId, $canvasType ?: static::CANVAS_NAME.'canvas');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated delegate to Blueprints repository
|
||||
*/
|
||||
public function addCanvas($values, $type = null): false|string
|
||||
{
|
||||
$canvasType = ($type === null || $type === '') ? static::CANVAS_NAME.'canvas' : $type;
|
||||
|
||||
return parent::addCanvas($values, $canvasType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated delegate to Blueprints repository
|
||||
*/
|
||||
public function getCanvasItemsById($id, $commentModule = null): false|array
|
||||
{
|
||||
return parent::getCanvasItemsById((int) $id, $commentModule ?: static::CANVAS_NAME.'canvasitem');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated delegate to Blueprints repository
|
||||
*/
|
||||
public function getNumberOfCanvasItems($projectId = null, $canvasType = null): mixed
|
||||
{
|
||||
return parent::getNumberOfCanvasItems($projectId !== null ? (int) $projectId : null, $canvasType ?: static::CANVAS_NAME.'canvas');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated delegate to Blueprints repository
|
||||
*/
|
||||
public function getNumberOfBoards($projectId = null, $canvasType = null): mixed
|
||||
{
|
||||
return parent::getNumberOfBoards($projectId !== null ? (int) $projectId : null, $canvasType ?: static::CANVAS_NAME.'canvas');
|
||||
}
|
||||
|
||||
/**
|
||||
* existCanvas - return if a canvas exists with a given title in the specified project
|
||||
*
|
||||
* @param int $projectId Project identifier
|
||||
* @param string $canvasTitle Canvas title
|
||||
* @return bool True if canvas exists
|
||||
*
|
||||
* @deprecated delegate to Blueprints repository
|
||||
*/
|
||||
public function existCanvas(int $projectId, string $canvasTitle, $canvasType = null): bool
|
||||
{
|
||||
return parent::existCanvas($projectId, $canvasTitle, $canvasType ?: static::CANVAS_NAME.'canvas');
|
||||
}
|
||||
|
||||
/***
|
||||
* copyCanvas - create a copy of an existing canvas
|
||||
*
|
||||
* @deprecated delegate to Blueprints repository
|
||||
*/
|
||||
public function copyCanvas(int $projectId, int $canvasId, int $authorId, string $canvasTitle, $canvasType = null): int
|
||||
{
|
||||
return parent::copyCanvas($projectId, $canvasId, $authorId, $canvasTitle, $canvasType ?: static::CANVAS_NAME.'canvas');
|
||||
}
|
||||
|
||||
/**
|
||||
* getCanvasItemsByKPI - goal-specific KPI hierarchy query (not part of Blueprints).
|
||||
*/
|
||||
public function getCanvasItemsByKPI($id): false|array
|
||||
{
|
||||
$results = $this->connection->table('zp_canvas_items')
|
||||
->select([
|
||||
'zp_canvas_items.id',
|
||||
'zp_canvas_items.title',
|
||||
'zp_canvas_items.kpi',
|
||||
'zp_canvas_items.startDate',
|
||||
'zp_canvas_items.endDate',
|
||||
'zp_canvas_items.setting',
|
||||
'zp_canvas_items.metricType',
|
||||
'zp_canvas_items.startValue',
|
||||
'zp_canvas_items.currentValue',
|
||||
'zp_canvas_items.endValue',
|
||||
'zp_canvas_items.canvasId',
|
||||
'zp_canvas.title as boardTitle',
|
||||
'zp_canvas.projectId as projectId',
|
||||
'zp_projects.name as projectName',
|
||||
'childrenLvl1.id as childId',
|
||||
'childrenLvl1.title as childTitle',
|
||||
'childrenLvl1.kpi as childKpi',
|
||||
'childrenLvl1.startDate as childStartDate',
|
||||
'childrenLvl1.endDate as childEndDate',
|
||||
'childrenLvl1.setting as childSetting',
|
||||
'childrenLvl1.metricType as childMetricType',
|
||||
'childrenLvl1.startValue as childStartValue',
|
||||
'childrenLvl1.currentValue as childCurrentValue',
|
||||
'childrenLvl1.endValue as childEndValue',
|
||||
'childrenLvl1.canvasId as childCanvasId',
|
||||
'childrenLvl1Board.title as childBoardTitle',
|
||||
'childrenLvl1Project.name as childProjectName',
|
||||
])
|
||||
->leftJoin('zp_canvas', 'zp_canvas_items.canvasId', '=', 'zp_canvas.id')
|
||||
->leftJoin('zp_projects', 'zp_canvas.projectId', '=', 'zp_projects.id')
|
||||
->leftJoin('zp_canvas_items as childrenLvl1', 'childrenLvl1.kpi', '=', 'zp_canvas_items.id')
|
||||
->leftJoin('zp_canvas as childrenLvl1Board', 'childrenLvl1.canvasId', '=', 'childrenLvl1Board.id')
|
||||
->leftJoin('zp_projects as childrenLvl1Project', 'childrenLvl1Board.projectId', '=', 'childrenLvl1Project.id')
|
||||
->where('zp_canvas_items.box', 'goal')
|
||||
->where('zp_canvas_items.kpi', $id)
|
||||
->get();
|
||||
|
||||
return array_map(fn ($item) => (array) $item, $results->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* getAllAvailableParents - goal-specific parent lookup (not part of Blueprints).
|
||||
*/
|
||||
public function getAllAvailableParents($projectId): false|array
|
||||
{
|
||||
$results = $this->connection->table('zp_canvas_items')
|
||||
->select([
|
||||
'zp_canvas_items.id',
|
||||
'zp_canvas_items.description',
|
||||
'zp_canvas_items.title',
|
||||
'zp_canvas_items.assumptions',
|
||||
'zp_canvas_items.data',
|
||||
'zp_canvas_items.conclusion',
|
||||
'zp_canvas_items.box',
|
||||
'zp_canvas_items.author',
|
||||
'zp_canvas_items.created',
|
||||
'zp_canvas_items.modified',
|
||||
'zp_canvas_items.canvasId',
|
||||
'zp_canvas_items.sortindex',
|
||||
'zp_canvas_items.status',
|
||||
'zp_canvas_items.relates',
|
||||
'zp_canvas_items.milestoneId',
|
||||
'zp_canvas_items.kpi',
|
||||
'zp_canvas_items.data1',
|
||||
'zp_canvas_items.data2',
|
||||
'zp_canvas_items.data3',
|
||||
'zp_canvas_items.data4',
|
||||
'zp_canvas_items.data5',
|
||||
'zp_canvas_items.startDate',
|
||||
'zp_canvas_items.endDate',
|
||||
'zp_canvas_items.setting',
|
||||
'zp_canvas_items.metricType',
|
||||
'zp_canvas_items.startValue',
|
||||
'zp_canvas_items.currentValue',
|
||||
'zp_canvas_items.endValue',
|
||||
'zp_canvas_items.impact',
|
||||
'zp_canvas_items.effort',
|
||||
'zp_canvas_items.probability',
|
||||
'zp_canvas_items.action',
|
||||
'zp_canvas_items.assignedTo',
|
||||
'zp_canvas_items.parent',
|
||||
'zp_canvas_items.tags',
|
||||
'board.title as boardTitle',
|
||||
'parentKPI.description as parentKPIDescription',
|
||||
'parentGoal.description as parentGoalDescription',
|
||||
't1.firstname as authorFirstname',
|
||||
't1.lastname as authorLastname',
|
||||
'milestone.headline as milestoneHeadline',
|
||||
'milestone.editTo as milestoneEditTo',
|
||||
])
|
||||
->leftJoin('zp_canvas as board', 'board.id', '=', 'zp_canvas_items.canvasId')
|
||||
->leftJoin('zp_canvas_items as parentKPI', 'zp_canvas_items.kpi', '=', 'parentKPI.id')
|
||||
->leftJoin('zp_canvas_items as parentGoal', 'zp_canvas_items.parent', '=', 'parentGoal.id')
|
||||
->leftJoin('zp_tickets as milestone', function ($join) {
|
||||
$join->on('zp_canvas_items.milestoneId', '=', $this->connection->raw($this->dbHelper->castAs($this->dbHelper->wrapColumn('milestone.id'), 'text')));
|
||||
})
|
||||
->leftJoin('zp_user as t1', 'zp_canvas_items.author', '=', 't1.id')
|
||||
->where('board.projectId', $projectId)
|
||||
->groupBy(['id', 'board.id', 'board.title', 'parentKPI.description', 'parentGoal.description', 't1.firstname', 't1.lastname', 'milestone.headline', 'milestone.editTo'])
|
||||
->orderBy('board.id')
|
||||
->get();
|
||||
|
||||
return array_map(fn ($item) => (array) $item, $results->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* getAllAvailableKPIs - goal-specific KPI lookup across parent projects (not part of Blueprints).
|
||||
*/
|
||||
public function getAllAvailableKPIs($projectId): false|array
|
||||
{
|
||||
// First, get parent project IDs (cross-database compatible approach)
|
||||
$parentProjectIds = [];
|
||||
|
||||
$parentProject = $this->connection->table('zp_projects')
|
||||
->select('parent')
|
||||
->where('id', $projectId)
|
||||
->first();
|
||||
|
||||
if ($parentProject && $parentProject->parent) {
|
||||
$parentProjectIds[] = $parentProject->parent;
|
||||
|
||||
// Check for grandparent
|
||||
$grandparent = $this->connection->table('zp_projects')
|
||||
->select('parent')
|
||||
->where('id', $parentProject->parent)
|
||||
->first();
|
||||
|
||||
if ($grandparent && $grandparent->parent) {
|
||||
$parentProjectIds[] = $grandparent->parent;
|
||||
}
|
||||
}
|
||||
|
||||
// If no parent projects found, return empty array
|
||||
if (empty($parentProjectIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Now query canvas items from parent projects
|
||||
$results = $this->connection->table('zp_canvas_items')
|
||||
->select([
|
||||
'zp_canvas_items.id',
|
||||
'zp_canvas_items.description',
|
||||
'project.name as projectName',
|
||||
'zp_canvas.title as boardTitle',
|
||||
])
|
||||
->leftJoin('zp_canvas', 'zp_canvas.id', '=', 'zp_canvas_items.canvasId')
|
||||
->leftJoin('zp_projects as project', 'zp_canvas.projectId', '=', 'project.id')
|
||||
->whereIn('zp_canvas.projectId', $parentProjectIds)
|
||||
->where(function ($query) {
|
||||
$query->where('zp_canvas_items.setting', 'linkAndReport')
|
||||
->orWhere('zp_canvas_items.setting', 'linkonly');
|
||||
})
|
||||
->orderBy('zp_canvas.id')
|
||||
->get();
|
||||
|
||||
return array_map(fn ($item) => (array) $item, $results->toArray());
|
||||
}
|
||||
}
|
||||
80
app/Domain/Canvas/Services/Canvas.php
Normal file
80
app/Domain/Canvas/Services/Canvas.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Canvas\Services;
|
||||
|
||||
use Leantime\Core\Auth\Permissions\RequiresPermission;
|
||||
use Leantime\Domain\Blueprints\Permissions\BlueprintsPermissions;
|
||||
use Leantime\Domain\Blueprints\Services\Blueprints as BlueprintsService;
|
||||
|
||||
/**
|
||||
* Thin backwards-compatibility shim over the Blueprints service.
|
||||
*
|
||||
* The canvas system was consolidated into Leantime\Domain\Blueprints. This class
|
||||
* is kept ONLY so plugins (and any code) that still call the old canvas service
|
||||
* keep working: every method delegates to the Blueprints service.
|
||||
*
|
||||
* @deprecated Use Leantime\Domain\Blueprints\Services\Blueprints instead.
|
||||
* Do not build new features on this class.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class Canvas
|
||||
{
|
||||
/**
|
||||
* import - Import canvas from XML file (delegates to Blueprints).
|
||||
*
|
||||
* @param string $filename File to import
|
||||
* @param string $canvasName Legacy canvas type (e.g. "swotcanvas") or slug
|
||||
* @param int $projectId Project identifier
|
||||
* @param int $authorId Author identifier
|
||||
* @return bool|int False if import failed, otherwise the new canvas id
|
||||
*
|
||||
* @deprecated use Blueprints service
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::CREATE, projectIdParam: 'projectId')]
|
||||
public function import(string $filename, string $canvasName, int $projectId, int $authorId): bool|int
|
||||
{
|
||||
// Old callers pass the full type ("swotcanvas"); Blueprints works on the slug ("swot").
|
||||
$canvasSlug = str_ends_with($canvasName, 'canvas')
|
||||
? substr($canvasName, 0, -strlen('canvas'))
|
||||
: $canvasName;
|
||||
|
||||
return app(BlueprintsService::class)->import($filename, $canvasSlug, $projectId, $authorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* getBoardProgress - completion percentage per canvas type (delegates to Blueprints).
|
||||
*
|
||||
* @param string $projectId projectId (optional)
|
||||
* @param array $boards Array of project board types
|
||||
* @return array List of boards with a progress percentage
|
||||
*
|
||||
* @deprecated use Blueprints service
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::VIEW, projectIdParam: 'projectId')]
|
||||
public function getBoardProgress(string $projectId = '', array $boards = []): array
|
||||
{
|
||||
return app(BlueprintsService::class)->getBoardProgress($projectId, $boards);
|
||||
}
|
||||
|
||||
/**
|
||||
* getLastUpdatedCanvas - canvas boards ordered by last updated item (delegates to Blueprints).
|
||||
*
|
||||
* @param int|null $projectId projectId (optional)
|
||||
* @param array $boards Array of project board types
|
||||
* @return array List of boards
|
||||
*
|
||||
* @deprecated use Blueprints service
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::VIEW, projectIdParam: 'projectId')]
|
||||
public function getLastUpdatedCanvas(?int $projectId = null, array $boards = []): array
|
||||
{
|
||||
return app(BlueprintsService::class)->getLastUpdatedCanvas($projectId, $boards);
|
||||
}
|
||||
}
|
||||
25
app/Domain/Canvas/Templates/boardDialog.blade.php
Normal file
25
app/Domain/Canvas/Templates/boardDialog.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
@php
|
||||
$canvasTitle = $canvasTitle ?? '';
|
||||
$canvasName = $canvasName ?? '';
|
||||
@endphp
|
||||
|
||||
<form action="{{ BASE_URL }}/{{ $canvasName }}canvas/boardDialog{{ isset($_GET['id']) ? '/' . (int) $_GET['id'] : '' }}" method="post" class="formModal">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title"><i class='fa fa-plus'></i> {!! __('subtitles.create_new_board') !!}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label>{!! __('label.title_new') !!}</label><br />
|
||||
<x-global::forms.text-input name="canvastitle" value="{{ $canvasTitle }}" placeholder="{{ __('input.placeholders.enter_title_for_board') }}"
|
||||
style="width: 100%" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if(isset($_GET['id']))
|
||||
<x-global::forms.button tag="input" inputType="submit" contentRole="primary" :labelText="__('buttons.save_board')" name="newCanvas" />
|
||||
<input type="hidden" name="editCanvas" value="{{ (int) $_GET['id'] }}">
|
||||
@else
|
||||
<input type="hidden" name="newCanvas" value="true">
|
||||
<x-global::forms.button tag="input" inputType="submit" contentRole="primary" :labelText="__('buttons.create_board')" name="newCanvas" />
|
||||
@endif
|
||||
<x-global::forms.button inputType="button" contentRole="tertiary" onclick="jQuery.nmTop().close();">{!! __('buttons.close') !!}</x-global::forms.button>
|
||||
</div>
|
||||
</form>
|
||||
54
app/Domain/Canvas/Templates/canvasComment.blade.php
Normal file
54
app/Domain/Canvas/Templates/canvasComment.blade.php
Normal file
@@ -0,0 +1,54 @@
|
||||
@php
|
||||
$canvasName = $canvasName ?? '';
|
||||
$canvasItem = $canvasItem ?? ['id' => '', 'box' => '', 'description' => ''];
|
||||
$canvasTypes = $canvasTypes ?? [];
|
||||
|
||||
$id = '';
|
||||
if (isset($canvasItem['id']) && $canvasItem['id'] != '') {
|
||||
$id = $canvasItem['id'];
|
||||
}
|
||||
@endphp
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
if (!window.jQuery) {
|
||||
//It's not a modal
|
||||
location.href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas?showModal={{ $canvasItem['id'] }}";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="showDialogOnLoad" style="display:none;">
|
||||
|
||||
<h4 class="widgettitle title-light" style="padding-bottom: 0"><i class="fas {{ $canvasTypes[$canvasItem['box']]['icon'] ?? '' }}"></i> {{ $canvasTypes[$canvasItem['box']]['title'] ?? '' }}</h4>
|
||||
<hr style="margin-top: 5px; margin-bottom: 15px;">
|
||||
|
||||
{!! $tpl->displayNotification() !!}
|
||||
|
||||
<h5 style="padding-left: 40px"><strong>{{ $canvasItem['description'] }}</strong></h5>
|
||||
|
||||
@if($id !== '')
|
||||
<br />
|
||||
<input type="hidden" name="comment" value="1" />
|
||||
<h4 class="widgettitle title-light"><span class="fa fa-comments"></span>{!! __('subtitles.discussion') !!}</h4>
|
||||
@include('comments::submodules.generalComment', ['formUrl' => '/' . $canvasName . 'canvas/editCanvasComment/' . $id])
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function(){
|
||||
|
||||
if (window.leantime && window.leantime.tiptapController) {
|
||||
leantime.tiptapController.initSimpleEditor();
|
||||
}
|
||||
|
||||
@if(! $login::userIsAtLeast($roles::$editor))
|
||||
leantime.authController.makeInputReadonly(".nyroModalCont");
|
||||
@endif
|
||||
|
||||
@if($login::userHasRole([$roles::$commenter]))
|
||||
leantime.commentsController.enableCommenterForms();
|
||||
@endif
|
||||
|
||||
})
|
||||
</script>
|
||||
227
app/Domain/Canvas/Templates/canvasDialog.blade.php
Normal file
227
app/Domain/Canvas/Templates/canvasDialog.blade.php
Normal file
@@ -0,0 +1,227 @@
|
||||
@php
|
||||
$canvasName = $canvasName ?? '';
|
||||
$currentCanvas = $currentCanvas ?? '';
|
||||
$canvasItem = $canvasItem ?? ['id' => '', 'box' => '', 'description' => '', 'status' => '', 'relates' => '', 'milestoneId' => '', 'milestoneHeadline' => ''];
|
||||
$canvasTypes = $canvasTypes ?? [];
|
||||
$hiddenStatusLabels = $statusLabels ?? [];
|
||||
$statusLabels = $statusLabels ?? [];
|
||||
$hiddenRelatesLabels = $relatesLabels ?? [];
|
||||
$relatesLabels = $relatesLabels ?? [];
|
||||
$dataLabels = $dataLabels ?? [1 => ['active' => false, 'field' => '', 'title' => ''], 2 => ['active' => false, 'field' => '', 'title' => ''], 3 => ['active' => false, 'field' => '', 'title' => '']];
|
||||
$milestones = $milestones ?? [];
|
||||
$users = $users ?? [];
|
||||
$searchCriteria = $searchCriteria ?? [];
|
||||
|
||||
$id = '';
|
||||
if (isset($canvasItem['id']) && $canvasItem['id'] != '') {
|
||||
$id = $canvasItem['id'];
|
||||
}
|
||||
@endphp
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
if (!window.jQuery) {
|
||||
//It's not a modal
|
||||
location.href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas?showModal={{ $canvasItem['id'] }}";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="" style="width:900px;">
|
||||
|
||||
<h4 class="widgettitle title-light" style="padding-bottom: 0"><i class="fas {{ $canvasTypes[$canvasItem['box']]['icon'] }}"></i> {{ $canvasTypes[$canvasItem['box']]['title'] }}</h4>
|
||||
<hr style="margin-top: 5px; margin-bottom: 15px;">
|
||||
{!! $tpl->displayNotification() !!}
|
||||
|
||||
<form class="formModal" method="post" action="{{ BASE_URL }}/{{ $canvasName }}canvas/editCanvasItem/{{ $id }}">
|
||||
|
||||
<input type="hidden" value="{{ $currentCanvas }}" name="canvasId" />
|
||||
<input type="hidden" value="{{ $canvasItem['box'] }}" name="box" id="box"/>
|
||||
<input type="hidden" value="{{ $id }}" name="itemId" id="itemId"/>
|
||||
|
||||
<label>{!! __('label.description') !!}</label>
|
||||
<x-global::forms.text-input name="description" value="{{ $canvasItem['description'] }}" style="width:100%" /><br />
|
||||
|
||||
@if(! empty($statusLabels))
|
||||
<label>{!! __('label.status') !!}</label>
|
||||
<select name="status" style="width: 50%" id="statusCanvas">
|
||||
</select><br /><br />
|
||||
@else
|
||||
<input type="hidden" name="status" value="{{ $canvasItem['status'] ?? array_key_first($hiddenStatusLabels) }}" />
|
||||
@endif
|
||||
|
||||
@if(! empty($relatesLabels))
|
||||
<label>{!! __('label.relates') !!}</label>
|
||||
<select name="relates" style="width: 50%" id="relatesCanvas">
|
||||
</select><br />
|
||||
@else
|
||||
<input type="hidden" name="relates" value="{{ $canvasItem['relates'] ?? array_key_first($hiddenRelatesLabels) }}" />
|
||||
@endif
|
||||
|
||||
@if($dataLabels[1]['active'])
|
||||
<label>{!! __($dataLabels[1]['title']) !!}</label>
|
||||
@if(isset($dataLabels[1]['type']) && $dataLabels[1]['type'] == 'int')
|
||||
<x-global::forms.text-input type="number" name="{{ $dataLabels[1]['field'] }}" value="{{ $canvasItem[$dataLabels[1]['field']] }}" /><br />
|
||||
@elseif(isset($dataLabels[1]['type']) && $dataLabels[1]['type'] == 'string')
|
||||
<x-global::forms.text-input name="{{ $dataLabels[1]['field'] }}" value="{{ $canvasItem[$dataLabels[1]['field']] }}" style="width:100%" /><br />
|
||||
@else
|
||||
<textarea style="width:100%" rows="3" cols="10" name="{{ $dataLabels[1]['field'] }}" class="modalTextArea tiptapSimple">{{ $canvasItem[$dataLabels[1]['field']] }}</textarea><br />
|
||||
@endif
|
||||
@else
|
||||
<input type="hidden" name="{{ $dataLabels[1]['field'] }}" value="" />
|
||||
@endif
|
||||
|
||||
@if($dataLabels[2]['active'])
|
||||
<label>{!! __($dataLabels[2]['title']) !!}</label>
|
||||
@if(isset($dataLabels[2]['type']) && $dataLabels[2]['type'] == 'int')
|
||||
<x-global::forms.text-input type="number" name="{{ $dataLabels[2]['field'] }}" value="{{ $canvasItem[$dataLabels[2]['field']] }}" /><br />
|
||||
@elseif(isset($dataLabels[2]['type']) && $dataLabels[2]['type'] == 'string')
|
||||
<x-global::forms.text-input name="{{ $dataLabels[2]['field'] }}" value="{{ $canvasItem[$dataLabels[2]['field']] }}" style="width:100%" /><br />
|
||||
@else
|
||||
<textarea style="width:100%" rows="3" cols="10" name="{{ $dataLabels[2]['field'] }}" class="modalTextArea tiptapSimple">{{ $canvasItem[$dataLabels[2]['field']] }}</textarea><br />
|
||||
@endif
|
||||
@else
|
||||
<input type="hidden" name="{{ $dataLabels[2]['field'] }}" value="" />
|
||||
@endif
|
||||
|
||||
@if($dataLabels[3]['active'])
|
||||
<label>{!! __($dataLabels[3]['title']) !!}</label>
|
||||
@if(isset($dataLabels[3]['type']) && $dataLabels[3]['type'] == 'int')
|
||||
<x-global::forms.text-input type="number" name="{{ $dataLabels[3]['field'] }}" value="{{ $canvasItem[$dataLabels[3]['field']] }}" /><br />
|
||||
@elseif(isset($dataLabels[3]['type']) && $dataLabels[3]['type'] == 'string')
|
||||
<x-global::forms.text-input name="{{ $dataLabels[3]['field'] }}" value="{{ $canvasItem[$dataLabels[3]['field']] }}" /><br />
|
||||
@else
|
||||
<textarea style="width:100%" rows="3" cols="10" name="{{ $dataLabels[3]['field'] }}" class="modalTextArea tiptapSimple">{{ $canvasItem[$dataLabels[3]['field']] }}</textarea><br />
|
||||
@endif
|
||||
@else
|
||||
<input type="hidden" name="{{ $dataLabels[3]['field'] }}" value="" />
|
||||
@endif
|
||||
|
||||
<input type="hidden" name="milestoneId" value="{{ $canvasItem['milestoneId'] }}" />
|
||||
<input type="hidden" name="changeItem" value="1" />
|
||||
|
||||
@if($id != '')
|
||||
<x-global::forms.button tag="a" link="{{ BASE_URL }}/{{ $canvasName }}canvas/delCanvasItem/{{ $id }}" class="{{ $canvasName }}CanvasModal delete right" state="danger" variant="outline"><i class='fa fa-trash-can'></i> {!! __('links.delete') !!}</x-global::forms.button>
|
||||
@endif
|
||||
|
||||
@if($login::userIsAtLeast($roles::$editor))
|
||||
<x-global::forms.button tag="input" inputType="submit" contentRole="primary" :labelText="__('buttons.save')" id="primaryCanvasSubmitButton" />
|
||||
<x-global::forms.button inputType="submit" contentRole="secondary" value="closeModal" id="saveAndClose" onclick="leantime.{{ $canvasName }}CanvasController.setCloseModal();">{!! __('buttons.save_and_close') !!}</x-global::forms.button>
|
||||
@endif
|
||||
|
||||
@if($id !== '')
|
||||
<br /><br />
|
||||
<h4 class="widgettitle title-light"><span class="fa fa-link"></span> {!! __('headlines.linked_milestone') !!} <i class="fa fa-question-circle-o helperTooltip" data-tippy-content="{{ __('tooltip.link_milestones_tooltip') }}"></i></h4>
|
||||
|
||||
@if($canvasItem['milestoneId'] == '')
|
||||
<center>
|
||||
<h4>{!! __('headlines.no_milestone_link') !!}</h4>
|
||||
|
||||
<div class="row" id="milestoneSelectors">
|
||||
@if($login::userIsAtLeast($roles::$editor))
|
||||
<div class="col-md-12">
|
||||
<a href="javascript:void(0);" onclick="leantime.{{ $canvasName }}CanvasController.toggleMilestoneSelectors('new');">{!! __('links.create_link_milestone') !!}</a>
|
||||
@if(count($milestones) > 0)
|
||||
| <a href="javascript:void(0);" onclick="leantime.{{ $canvasName }}CanvasController.toggleMilestoneSelectors('existing');">{!! __('links.link_existing_milestone') !!}</a>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="row" id="newMilestone" style="display:none;">
|
||||
<div class="col-md-12">
|
||||
<x-global::forms.text-input width="50%" name="newMilestone" /><br />
|
||||
<input type="hidden" name="type" value="milestone" />
|
||||
<input type="hidden" name="{{ $canvasName }}canvasitemid" value="{{ $id }} " />
|
||||
<x-global::forms.button tag="input" inputType="button" :labelText="__('buttons.save')" onclick="jQuery('#primaryCanvasSubmitButton').click()" contentRole="primary" />
|
||||
<x-global::forms.button tag="input" inputType="button" :labelText="__('buttons.cancel')" onclick="leantime.{{ $canvasName }}CanvasController.toggleMilestoneSelectors('hide')" contentRole="tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" id="existingMilestone" style="display:none;">
|
||||
<div class="col-md-12">
|
||||
<select data-placeholder="{{ __('input.placeholders.filter_by_milestone') }}" name="existingMilestone" class="user-select">
|
||||
<option value=""></option>
|
||||
@foreach($milestones as $milestoneRow)
|
||||
<option value="{{ $milestoneRow->id }}"
|
||||
@if(isset($searchCriteria['milestone']) && $searchCriteria['milestone'] == $milestoneRow->id) selected='selected' @endif
|
||||
>{{ $milestoneRow->headline }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input type="hidden" name="type" value="milestone" />
|
||||
<input type="hidden" name="{{ $canvasName }}canvasitemid" value="{{ $id }} " />
|
||||
<x-global::forms.button tag="input" inputType="button" :labelText="__('buttons.save')" onclick="jQuery('#primaryCanvasSubmitButton').click()" contentRole="primary" />
|
||||
<x-global::forms.button tag="input" inputType="button" :labelText="__('buttons.cancel')" onclick="leantime.{{ $canvasName }}CanvasController.toggleMilestoneSelectors('hide')" contentRole="tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
@else
|
||||
<div hx-trigger="load"
|
||||
hx-indicator=".htmx-indicator"
|
||||
hx-get="{{ BASE_URL }}/hx/tickets/milestones/showCard?milestoneId={{ $canvasItem['milestoneId'] }}">
|
||||
<div class="htmx-indicator">
|
||||
{!! __('label.loading_milestone') !!}
|
||||
</div>
|
||||
</div>
|
||||
<x-global::forms.button tag="a" link="{{ CURRENT_URL }}?removeMilestone={{ $canvasItem['milestoneId'] }}" class="{{ $canvasName }}CanvasModal delete formModal" state="danger" variant="outline"><i class="fa fa-close"></i> {!! __('links.remove') !!}</x-global::forms.button>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
</form>
|
||||
|
||||
@if($id !== '')
|
||||
<br />
|
||||
<input type="hidden" name="comment" value="1" />
|
||||
<h4 class="widgettitle title-light"><span class="fa fa-comments"></span>{!! __('subtitles.discussion') !!}</h4>
|
||||
@include('comments::submodules.generalComment', ['formUrl' => '/' . $canvasName . 'canvas/editCanvasItem/' . $id])
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function(){
|
||||
|
||||
@if(! empty($statusLabels))
|
||||
new SlimSelect({
|
||||
select: '#statusCanvas',
|
||||
showSearch: false,
|
||||
valuesUseText: false,
|
||||
data: [
|
||||
@foreach($statusLabels as $key => $data)
|
||||
@if($data['active'])
|
||||
{ innerHTML: '<i class="fas fa-fw {{ $data['icon'] }}"></i> {{ $data['title'] }}',
|
||||
text: "{{ $data['title'] }}", value: "{{ $key }}", selected: {{ $canvasItem['status'] == $key ? 'true' : 'false' }}},
|
||||
@endif
|
||||
@endforeach
|
||||
]
|
||||
});
|
||||
@endif
|
||||
|
||||
@if(! empty($relatesLabels))
|
||||
new SlimSelect({
|
||||
select: '#relatesCanvas',
|
||||
showSearch: false,
|
||||
valuesUseText: false,
|
||||
data: [
|
||||
@foreach($relatesLabels as $key => $data)
|
||||
@if($data['active'])
|
||||
{ innerHTML: '<i class="fas fa-fw {{ $data['icon'] }}"></i> {{ $data['title'] }}',
|
||||
text: "{{ $data['title'] }}", value: "{{ $key }}", selected: {{ $canvasItem['relates'] == $key ? 'true' : 'false' }}},
|
||||
@endif
|
||||
@endforeach
|
||||
]
|
||||
});
|
||||
@endif
|
||||
|
||||
if (window.leantime && window.leantime.tiptapController) {
|
||||
leantime.tiptapController.initSimpleEditor();
|
||||
}
|
||||
|
||||
@if(! $login::userIsAtLeast($roles::$editor))
|
||||
leantime.authController.makeInputReadonly(".nyroModalCont");
|
||||
@endif
|
||||
|
||||
@if($login::userHasRole([$roles::$commenter]))
|
||||
leantime.commentsController.enableCommenterForms();
|
||||
@endif
|
||||
|
||||
})
|
||||
</script>
|
||||
12
app/Domain/Canvas/Templates/delCanvas.blade.php
Normal file
12
app/Domain/Canvas/Templates/delCanvas.blade.php
Normal file
@@ -0,0 +1,12 @@
|
||||
@php
|
||||
$id = filter_var($_GET['id'], FILTER_SANITIZE_NUMBER_INT);
|
||||
@endphp
|
||||
|
||||
<h4 class="widgettitle title-light">{!! __('subtitles.delete') !!}</h4>
|
||||
|
||||
<form method="post" action="{{ BASE_URL }}/{{ $canvasName }}canvas/delCanvas/{{ $id }}">
|
||||
<p>{!! __('text.confirm_board_deletion') !!}</p><br />
|
||||
<x-global::forms.button tag="input" inputType="submit" contentRole="primary" :labelText="__('buttons.yes_delete')" name="del" />
|
||||
<x-global::forms.button tag="a" contentRole="tertiary"
|
||||
link="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas">{!! __('buttons.back') !!}</x-global::forms.button>
|
||||
</form>
|
||||
12
app/Domain/Canvas/Templates/delCanvasItem.blade.php
Normal file
12
app/Domain/Canvas/Templates/delCanvasItem.blade.php
Normal file
@@ -0,0 +1,12 @@
|
||||
@php
|
||||
$id = filter_var($_GET['id'], FILTER_SANITIZE_NUMBER_INT);
|
||||
@endphp
|
||||
|
||||
<h4 class="widgettitle title-light">{!! __('subtitles.delete') !!}</h4>
|
||||
<hr style="margin-top: 5px; margin-bottom: 15px;">
|
||||
|
||||
<form method="post" action="{{ BASE_URL }}/{{ $canvasName }}canvas/delCanvasItem/{{ $id }}">
|
||||
<p>{!! __('text.confirm_board_item_deletion') !!}</p><br />
|
||||
<x-global::forms.button tag="input" inputType="submit" contentRole="primary" :labelText="__('buttons.yes_delete')" name="del" />
|
||||
<x-global::forms.button tag="a" contentRole="tertiary" link="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas">{!! __('buttons.back') !!}</x-global::forms.button>
|
||||
</form>
|
||||
150
app/Domain/Canvas/Templates/element.blade.php
Normal file
150
app/Domain/Canvas/Templates/element.blade.php
Normal file
@@ -0,0 +1,150 @@
|
||||
@php
|
||||
use Leantime\Domain\Comments\Repositories\Comments;
|
||||
@endphp
|
||||
|
||||
<h4 class="widgettitle title-primary">
|
||||
@if(isset($canvasTypes[$elementName]['icon']))
|
||||
<i class="fas {{ $canvasTypes[$elementName]['icon'] }}"></i>
|
||||
@endif
|
||||
{{ $canvasTypes[$elementName]['title'] }}
|
||||
</h4>
|
||||
<div class="contentInner even status_{{ $elementName }}"
|
||||
{!! isset($canvasTypes[$elementName]['color']) ? 'style="background: ' . $canvasTypes[$elementName]['color'] . ';"' : '' !!}>
|
||||
|
||||
@foreach($canvasItems as $row)
|
||||
@php
|
||||
$filterStatus = $filter['status'] ?? 'all';
|
||||
$filterRelates = $filter['relates'] ?? 'all';
|
||||
@endphp
|
||||
|
||||
@if($row['box'] === $elementName && ($filterStatus == 'all' || $filterStatus == $row['status']) && ($filterRelates == 'all' || $filterRelates == $row['relates']))
|
||||
@php
|
||||
$comments = app()->make(Comments::class);
|
||||
$nbcomments = $comments->countComments(moduleId: $row['id']);
|
||||
@endphp
|
||||
|
||||
<div class="ticketBox" id="item_{{ $row['id'] }}">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="inlineDropDownContainer" style="float:right;">
|
||||
|
||||
@if($login::userIsAtLeast($roles::$editor))
|
||||
<a href="javascript:void(0)" class="dropdown-toggle ticketDropDown" data-toggle="dropdown">
|
||||
<i class="fa fa-ellipsis-v" aria-hidden="true"></i>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if($login::userIsAtLeast($roles::$editor))
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<li class="nav-header">{!! __('subtitles.edit') !!}</li>
|
||||
<li><a href="#/{{ $canvasName }}canvas/editCanvasItem/{{ $row['id'] }}"
|
||||
data="item_{{ $row['id'] }}"> {!! __('links.edit_canvas_item') !!}</a></li>
|
||||
<li><a href="#/{{ $canvasName }}canvas/delCanvasItem/{{ $row['id'] }}"
|
||||
class="delete"
|
||||
data="item_{{ $row['id'] }}"> {!! __('links.delete_canvas_item') !!}</a></li>
|
||||
</ul>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<h4><a href="#/{{ $canvasName }}canvas/editCanvasItem/{{ $row['id'] }}"
|
||||
data="item_{{ $row['id'] }}">{{ $row['description'] }}</a></h4>
|
||||
|
||||
@if($row['conclusion'] != '')
|
||||
<small>{!! $tpl->escapeMinimal($row['conclusion']) !!}</small>
|
||||
@endif
|
||||
|
||||
<div class="clearfix" style="padding-bottom: 8px;"></div>
|
||||
|
||||
@if(! empty($statusLabels))
|
||||
<div class="dropdown ticketDropdown statusDropdown colorized show firstDropdown">
|
||||
<a class="dropdown-toggle f-left status label-{{ $statusLabels[$row['status']]['dropdown'] }}"
|
||||
href="javascript:void(0);" role="button"
|
||||
id="statusDropdownMenuLink{{ $row['id'] }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="text">{{ $statusLabels[$row['status']]['title'] }}</span> <i class="fa fa-caret-down" aria-hidden="true"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="statusDropdownMenuLink{{ $row['id'] }}">
|
||||
<li class="nav-header border">{!! __('dropdown.choose_status') !!}</li>
|
||||
@foreach($statusLabels as $key => $data)
|
||||
@if($data['active'] || true)
|
||||
<li class='dropdown-item'>
|
||||
<a href="javascript:void(0);" class="label-{{ $data['dropdown'] }}"
|
||||
data-label='{{ $data['title'] }}' data-value="{{ $row['id'] . '/' . $key }}"
|
||||
id="ticketStatusChange{{ $row['id'] }}{{ $key }}">{{ $data['title'] }}</a>
|
||||
</li>
|
||||
@endif
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(! empty($relatesLabels))
|
||||
<div class="dropdown ticketDropdown relatesDropdown colorized show firstDropdown">
|
||||
<a class="dropdown-toggle f-left relates label-{{ $relatesLabels[$row['relates']]['dropdown'] }}"
|
||||
href="javascript:void(0);" role="button"
|
||||
id="relatesDropdownMenuLink{{ $row['id'] }}" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<span class="text">{{ $relatesLabels[$row['relates']]['title'] }}</span> <i class="fa fa-caret-down" aria-hidden="true"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="relatesDropdownMenuLink{{ $row['id'] }}">
|
||||
<li class="nav-header border">{!! __('dropdown.choose_relates') !!}</li>
|
||||
@foreach($relatesLabels as $key => $data)
|
||||
@if($data['active'] || true)
|
||||
<li class='dropdown-item'>
|
||||
<a href="javascript:void(0);" class="label-{{ $data['dropdown'] }}"
|
||||
data-label='{{ $data['title'] }}'
|
||||
data-value="{{ $row['id'] . '/' . $key }}"
|
||||
id="ticketRelatesChange{{ $row['id'] }}{{ $key }}">{{ $data['title'] }}</a>
|
||||
</li>
|
||||
@endif
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="dropdown ticketDropdown userDropdown noBg show right lastDropdown dropRight">
|
||||
<a class="dropdown-toggle f-left" href="javascript:void(0);" role="button" id="userDropdownMenuLink{{ $row['id'] }}"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="text">
|
||||
@if($row['authorFirstname'] != '')
|
||||
<span id='userImage{{ $row['id'] }}'><img src='{{ BASE_URL }}/api/users?profileImage={{ $row['author'] }}' width='25' style='vertical-align: middle;'/></span><span id='user{{ $row['id'] }}'></span>
|
||||
@else
|
||||
<span id='userImage{{ $row['id'] }}'><img src='{{ BASE_URL }}/api/users?profileImage=false' width='25' style='vertical-align: middle;'/></span><span id='user{{ $row['id'] }}'></span>
|
||||
@endif
|
||||
</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="userDropdownMenuLink{{ $row['id'] }}">
|
||||
<li class="nav-header border">{!! __('dropdown.choose_user') !!}</li>
|
||||
@foreach($users as $user)
|
||||
<li class='dropdown-item'>
|
||||
<a href='javascript:void(0);' data-label='{{ sprintf(__('text.full_name'), e($user['firstname']), e($user['lastname'])) }}' data-value='{{ $row['id'] }}_{{ $user['id'] }}_{{ $user['profileId'] }}' id='userStatusChange{{ $row['id'] }}{{ $user['id'] }}'><img src='{{ BASE_URL }}/api/users?profileImage={{ $user['id'] }}&v={{ $user['modified'] }}' width='25' style='vertical-align: middle; margin-right:5px;'/>{{ sprintf(__('text.full_name'), e($user['firstname']), e($user['lastname'])) }}</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pull-right" style="margin-right:10px;">
|
||||
<span class="fas fa-comments"></span> <small>{{ $nbcomments }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($row['milestoneHeadline'] != '')
|
||||
<br/>
|
||||
<div hx-trigger="load"
|
||||
hx-indicator=".htmx-indicator"
|
||||
hx-get="{{ BASE_URL }}/hx/tickets/milestones/showCard?milestoneId={{ $row['milestoneId'] }}">
|
||||
<div class="htmx-indicator">
|
||||
{!! __('label.loading_milestone') !!}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
<br />
|
||||
@if($login::userIsAtLeast($roles::$editor))
|
||||
<a href="#/{{ $canvasName }}canvas/editCanvasItem?type={{ $elementName }}"
|
||||
class="" id="{{ $elementName }}"
|
||||
style="padding-bottom: 10px;">{!! __('links.add_new_canvas_item') !!}</a>
|
||||
@endif
|
||||
</div>
|
||||
22
app/Domain/Canvas/Templates/helper.blade.php
Normal file
22
app/Domain/Canvas/Templates/helper.blade.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<div class="center padding-lg">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div style='width:300px' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_design_data_khdb.svg') !!}
|
||||
</div>
|
||||
<br />
|
||||
<h1>{!! __("headlines.$canvasName.welcome_to_board") !!}</h1><br />
|
||||
{!! __("text.$canvasName.helper_content") !!}
|
||||
<br /><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p></p>
|
||||
<x-global::forms.button tag="a" link="javascript:void(0);" onclick="jQuery.nmTop().close()" contentRole="tertiary">{!! __('links.close') !!}</x-global::forms.button><br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
3
app/Domain/Canvas/Templates/modals.blade.php
Normal file
3
app/Domain/Canvas/Templates/modals.blade.php
Normal file
@@ -0,0 +1,3 @@
|
||||
{{-- modals.blade.php - Generic template for canvas modals --}}
|
||||
{{-- Note: The modal content was previously commented out in the legacy template. --}}
|
||||
{{-- Kept as an empty partial for compatibility. The actual board dialog is boardDialog.blade.php --}}
|
||||
70
app/Domain/Canvas/Templates/showCanvasBottom.blade.php
Normal file
70
app/Domain/Canvas/Templates/showCanvasBottom.blade.php
Normal file
@@ -0,0 +1,70 @@
|
||||
@if(count($allCanvas) > 0)
|
||||
@else
|
||||
<br /><br />
|
||||
<div class='center'>
|
||||
<div class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_design_data_khdb.svg') !!}
|
||||
</div>
|
||||
|
||||
<h3>{!! __("headlines.$canvasName.analysis") !!}</h3>
|
||||
<br />{!! __("text.$canvasName.helper_content") !!}
|
||||
|
||||
@if($login::userIsAtLeast($roles::$editor))
|
||||
<br /><br />
|
||||
<a href='javascript:void(0)' class='addCanvasLink btn btn-primary'>
|
||||
{!! __('links.icon.create_new_board') !!}</a>.
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(! empty($disclaimer) && count($allCanvas) > 0)
|
||||
<small class="align-center">{{ $disclaimer }}</small>
|
||||
@endif
|
||||
|
||||
@include('canvas::modals')
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@once @push('scripts')
|
||||
<script type="text/javascript">
|
||||
|
||||
jQuery(document).ready(function() {
|
||||
|
||||
if(jQuery('#searchCanvas').length > 0) {
|
||||
new SlimSelect({ select: '#searchCanvas' });
|
||||
}
|
||||
|
||||
@if(isset($_GET['closeModal']))
|
||||
jQuery.nmTop().close();
|
||||
@endif
|
||||
|
||||
leantime.{{ $canvasName }}CanvasController.setRowHeights();
|
||||
leantime.canvasController.setCanvasName('{{ $canvasName }}');
|
||||
leantime.canvasController.initFilterBar();
|
||||
|
||||
@if($login::userIsAtLeast($roles::$editor))
|
||||
leantime.canvasController.initCanvasLinks();
|
||||
leantime.canvasController.initUserDropdown();
|
||||
leantime.canvasController.initStatusDropdown();
|
||||
leantime.canvasController.initRelatesDropdown();
|
||||
@else
|
||||
leantime.authController.makeInputReadonly(".maincontentinner");
|
||||
@endif
|
||||
|
||||
@if(isset($_GET['showModal']))
|
||||
@php
|
||||
if ($_GET['showModal'] == '') {
|
||||
$modalUrl = '&type=' . array_key_first($canvasTypes);
|
||||
} else {
|
||||
$modalUrl = '/' . (int) $_GET['showModal'];
|
||||
}
|
||||
@endphp
|
||||
leantime.canvasController.openModalManually("{{ BASE_URL }}/{{ $canvasName }}canvas/editCanvasItem{{ $modalUrl }}");
|
||||
window.history.pushState({},document.title, '{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas/');
|
||||
@endif
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
@endpush @endonce
|
||||
131
app/Domain/Canvas/Templates/showCanvasTop.blade.php
Normal file
131
app/Domain/Canvas/Templates/showCanvasTop.blade.php
Normal file
@@ -0,0 +1,131 @@
|
||||
@php
|
||||
$canvasTitle = '';
|
||||
$allCanvas = $allCanvas ?? [];
|
||||
$canvasIcon = $canvasIcon ?? '';
|
||||
$canvasTypes = $canvasTypes ?? [];
|
||||
$statusLabels = $statusLabels ?? [];
|
||||
$relatesLabels = $relatesLabels ?? [];
|
||||
$dataLabels = $dataLabels ?? [];
|
||||
$disclaimer = $disclaimer ?? '';
|
||||
$canvasItems = $canvasItems ?? [];
|
||||
|
||||
$filter['status'] = $_GET['filter_status'] ?? (session('filter_status') ?? 'all');
|
||||
session(['filter_status' => $filter['status']]);
|
||||
$filter['relates'] = $_GET['filter_relates'] ?? (session('filter_relates') ?? 'all');
|
||||
session(['filter_relates' => $filter['relates']]);
|
||||
|
||||
// get canvas title
|
||||
foreach ($allCanvas as $canvasRow) {
|
||||
if ($canvasRow['id'] == ($currentCanvas ?? '')) {
|
||||
$canvasTitle = $canvasRow['title'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@endphp
|
||||
|
||||
<style>
|
||||
.canvas-row { margin-left: 0px; margin-right: 0px;}
|
||||
.canvas-title-only { border-radius: var(--box-radius-small); }
|
||||
h4.canvas-element-title-empty { background: white !important; border-color: white !important; }
|
||||
div.canvas-element-center-middle { text-align: center; }
|
||||
</style>
|
||||
|
||||
<div class="pageheader">
|
||||
<div class="pageicon"><span class='fa {{ $canvasIcon }}'></span></div>
|
||||
<div class="pagetitle">
|
||||
@if(count($allCanvas) > 0)
|
||||
<x-global::subjectSwitcher
|
||||
:parent="__('headline.' . $canvasName . '.board')"
|
||||
:current="$canvasTitle">
|
||||
@if($login::userIsAtLeast($roles::$editor))
|
||||
<li><a href="#/{{ $canvasName }}canvas/boardDialog">{!! __('links.icon.create_new_board') !!}</a></li>
|
||||
@endif
|
||||
<li class="border"></li>
|
||||
@foreach($allCanvas as $canvasRow)
|
||||
<li><a href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas/{{ $canvasRow['id'] }}">{{ e($canvasRow['title']) }}</a></li>
|
||||
@endforeach
|
||||
</x-global::subjectSwitcher>
|
||||
@else
|
||||
<h1>{!! __("headline.$canvasName.board") !!}</h1>
|
||||
@endif
|
||||
</div>
|
||||
@if(count($allCanvas) > 0)
|
||||
<div class="pageheader-right">
|
||||
<span class="dropdown dropdownWrapper headerEditDropdown">
|
||||
<a href="javascript:void(0)" class="dropdown-toggle btn btn-transparent" data-toggle="dropdown"><i class="fa-solid fa-ellipsis-v"></i></a>
|
||||
<ul class="dropdown-menu editCanvasDropdown">
|
||||
@if($login::userIsAtLeast($roles::$editor))
|
||||
<li><a href="#/{{ $canvasName }}canvas/boardDialog/{{ $currentCanvas }}" class="editCanvasLink ">{!! __('links.icon.edit') !!}</a></li>
|
||||
@endif
|
||||
<li><a href="{{ BASE_URL }}/{{ $canvasName }}canvas/export/{{ $currentCanvas }}">{!! __('links.icon.export') !!}</a></li>
|
||||
<li><a href="javascript:window.print();">{!! __('links.icon.print') !!}</a></li>
|
||||
@if($login::userIsAtLeast($roles::$editor))
|
||||
<li><a href="#/{{ $canvasName }}canvas/delCanvas/{{ $currentCanvas }}" class="delete">{!! __('links.icon.delete') !!}</a></li>
|
||||
@endif
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div><!--pageheader-->
|
||||
|
||||
<div class="maincontent">
|
||||
<div class="maincontentinner">
|
||||
|
||||
{!! $tpl->displayNotification() !!}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
|
||||
@if($login::userIsAtLeast($roles::$editor) && count($canvasTypes) == 1 && count($allCanvas) > 0)
|
||||
<x-global::forms.button tag="a" link="#/{{ $canvasName }}canvas/editCanvasItem?type={{ $elementName }}"
|
||||
contentRole="primary" id="{{ $elementName }}">{!! __('links.add_new_canvas_item' . $canvasName) !!}</x-global::forms.button>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 center">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="pull-right">
|
||||
<div class="btn-group viewDropDown">
|
||||
@if(count($allCanvas) > 0 && ! empty($statusLabels))
|
||||
@if($filter['status'] == 'all')
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown"><i class="fas fa-filter"></i> {!! __('status.all') !!} {!! __('links.view') !!}</button>
|
||||
@else
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown"><i class="fas fa-fw {!! __($statusLabels[$filter['status']]['icon']) !!}"></i> {{ $statusLabels[$filter['status']]['title'] }} {!! __('links.view') !!}</button>
|
||||
@endif
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas?filter_status=all" @if($filter['status'] == 'all') class="active" @endif><i class="fas fa-globe"></i> {!! __('status.all') !!}</a></li>
|
||||
@foreach($statusLabels as $key => $data)
|
||||
<li><a href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas?filter_status={{ $key }}" @if($filter['status'] == $key) class="active" @endif><i class="fas fa-fw {{ $data['icon'] }}"></i> {{ $data['title'] }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="btn-group viewDropDown">
|
||||
@if(count($allCanvas) > 0 && ! empty($relatesLabels))
|
||||
@if($filter['relates'] == 'all')
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown"><i class="fas fa-fw fa-globe"></i> {!! __('relates.all') !!} {!! __('links.view') !!}</button>
|
||||
@else
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown"><i class="fas fa-fw {!! __($relatesLabels[$filter['relates']]['icon']) !!}"></i> {{ $relatesLabels[$filter['relates']]['title'] }} {!! __('links.view') !!}</button>
|
||||
@endif
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas?filter_relates=all" @if($filter['relates'] == 'all') class="active" @endif><i class="fas fa-globe"></i> {!! __('relates.all') !!}</a></li>
|
||||
@foreach($relatesLabels as $key => $data)
|
||||
<li><a href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas?filter_relates={{ $key }}" @if($filter['relates'] == $key) class="active" @endif><i class="fas fa-fw {{ $data['icon'] }}"></i> {{ $data['title'] }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
Reference in New Issue
Block a user