Skip to content

Commit cc5b099

Browse files
committed
2 parents f02a9d6 + 1a8b0e6 commit cc5b099

11 files changed

Lines changed: 122 additions & 52 deletions

File tree

app/Domain/Tickets/Controllers/ShowKanban.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Leantime\Domain\Tickets\Controllers;
44

5+
use Carbon\CarbonImmutable;
56
use Illuminate\Contracts\Container\BindingResolutionException;
67
use Leantime\Core\Controller\Controller;
78
use Leantime\Core\Controller\Frontcontroller;
@@ -84,13 +85,13 @@ public function post(array $params): Response
8485
$swimlaneValue = $_POST['swimlane'] ?? null;
8586
$groupBy = $_POST['groupBy'] ?? null;
8687

87-
if (! empty($swimlaneValue) && ! empty($groupBy)) {
88+
if ($swimlaneValue !== null && $swimlaneValue !== '' && ! empty($groupBy)) {
8889
// Map groupBy field to the parameter name expected by quickAddTicket()
8990
$fieldMapping = [
9091
'priority' => 'priority',
9192
'storypoints' => 'storypoints',
92-
'effort' => 'storypoints', // effort maps to storypoints field
93-
'milestoneid' => 'milestone', // Note: service expects 'milestone' not 'milestoneid'
93+
'effort' => 'storypoints',
94+
'milestoneid' => 'milestone',
9495
'editorId' => 'editorId',
9596
'sprint' => 'sprint',
9697
'type' => 'type',
@@ -99,6 +100,19 @@ public function post(array $params): Response
99100
if (isset($fieldMapping[$groupBy])) {
100101
$paramName = $fieldMapping[$groupBy];
101102
$formParams[$paramName] = $swimlaneValue;
103+
} elseif ($groupBy === 'dueDate') {
104+
// Map due date bucket names to actual dates
105+
$now = dtHelper()->userNow();
106+
$dueDateMapping = [
107+
'overdue' => $now->formatDateForUser(),
108+
'due-this-week' => $now->endOfWeek(CarbonImmutable::FRIDAY)->formatDateForUser(),
109+
'due-next-week' => $now->addWeek()->endOfWeek(CarbonImmutable::FRIDAY)->formatDateForUser(),
110+
'due-later' => $now->addWeeks(2)->formatDateForUser(),
111+
];
112+
113+
if (isset($dueDateMapping[$swimlaneValue])) {
114+
$formParams['dateToFinish'] = $dueDateMapping[$swimlaneValue];
115+
}
102116
}
103117
}
104118

app/Domain/Tickets/Controllers/ShowList.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,15 @@ public function post(array $params): Response
5555
{
5656
// QuickAdd
5757
if (isset($_POST['quickadd'])) {
58-
$result = $this->ticketService->quickAddTicket($params);
58+
$formParams = [
59+
'headline' => $_POST['headline'] ?? '',
60+
'milestone' => $_POST['milestone'] ?? '',
61+
'sprint' => $_POST['sprint'] ?? '',
62+
'projectId' => session('currentProject'),
63+
'editorId' => session('userdata.id'),
64+
];
65+
66+
$result = $this->ticketService->quickAddTicket($formParams);
5967

6068
if (is_array($result)) {
6169
$this->tpl->setNotification($result['message'], $result['status']);

app/Domain/Tickets/Repositories/Tickets.php

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,25 +1459,73 @@ public function addTicket(array $values): bool|int
14591459
return false;
14601460
}
14611461

1462+
/**
1463+
* Patchable columns on the zp_tickets table.
1464+
* Only these fields will be included in patch UPDATE queries.
1465+
*
1466+
* @var array<string, true>
1467+
*/
1468+
private const PATCHABLE_COLUMNS = [
1469+
'headline' => true,
1470+
'type' => true,
1471+
'description' => true,
1472+
'projectId' => true,
1473+
'status' => true,
1474+
'date' => true,
1475+
'dateToFinish' => true,
1476+
'sprint' => true,
1477+
'storypoints' => true,
1478+
'priority' => true,
1479+
'hourRemaining' => true,
1480+
'planHours' => true,
1481+
'tags' => true,
1482+
'editorId' => true,
1483+
'userId' => true,
1484+
'editFrom' => true,
1485+
'editTo' => true,
1486+
'acceptanceCriteria' => true,
1487+
'dependingTicketId' => true,
1488+
'milestoneid' => true,
1489+
'sortIndex' => true,
1490+
'kanbanSortIndex' => true,
1491+
];
1492+
1493+
/**
1494+
* Patch specific fields on a ticket.
1495+
*
1496+
* Only fields present in PATCHABLE_COLUMNS are included in the update.
1497+
* Non-column fields (e.g. request_parts, saveTicket) are silently ignored.
1498+
*
1499+
* @param int|string $id The ticket ID.
1500+
* @param array $params The fields to update.
1501+
* @return bool Whether any rows were affected.
1502+
*/
14621503
public function patchTicket($id, array $params): bool
14631504
{
14641505
$this->addTicketChange(session('userdata.id'), $id, $params);
14651506

1466-
// Sanitize params to use only valid column names
14671507
$updates = [];
14681508
foreach ($params as $key => $value) {
14691509
$sanitizedKey = DbCore::sanitizeToColumnString($key);
1510+
1511+
if (! isset(self::PATCHABLE_COLUMNS[$sanitizedKey])) {
1512+
continue;
1513+
}
1514+
14701515
$updates[$sanitizedKey] = $value;
14711516

1472-
// send status update event
14731517
if ($key == 'status') {
14741518
static::dispatch_event('ticketStatusUpdate', ['ticketId' => $id, 'status' => $value, 'action' => 'ticketStatusUpdate']);
14751519
}
14761520
}
14771521

1522+
if (empty($updates)) {
1523+
return false;
1524+
}
1525+
14781526
$updates['modified'] = dtHelper()->userNow()->formatDateTimeForDb();
14791527

1480-
return $this->connection->table('zp_tickets')
1528+
return (bool) $this->connection->table('zp_tickets')
14811529
->where('id', $id)
14821530
->update($updates);
14831531
}

app/Domain/Tickets/Services/Tickets.php

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ public function getAllGrouped($searchCriteria): array
598598
$ticketGroups['all'] = [
599599
'label' => 'all',
600600
'id' => 'all',
601+
'value' => '',
601602
'class' => '',
602603
'items' => $tickets,
603604
];
@@ -729,6 +730,7 @@ public function getAllGrouped($searchCriteria): array
729730
'label' => $label,
730731
'more-info' => $moreInfo,
731732
'id' => $sortId ?? strtolower($groupedFieldValue),
733+
'value' => $groupedFieldValue,
732734
'class' => $class,
733735
'color' => $groupColor,
734736
'items' => [$ticket],
@@ -876,6 +878,7 @@ private function groupTicketsByDueDate(array $tickets): array
876878
$ticketGroups[$bucketKey] = [
877879
'label' => $bucketDef['label'],
878880
'id' => $bucketDef['id'],
881+
'value' => $bucketKey,
879882
'class' => $bucketDef['class'],
880883
'more-info' => '',
881884
'items' => [],
@@ -2159,13 +2162,19 @@ public function updateTicket($values): array|bool
21592162
*/
21602163
public function patch($id, $params): bool
21612164
{
2162-
2163-
// $params is an array of field names. Exclude id
2164-
if (is_array($params)) {
2165-
unset($params['id']);
2166-
unset($params['act']);
2165+
if (! is_array($params)) {
2166+
return false;
21672167
}
21682168

2169+
// Strip non-ticket fields that may leak in from the framework or form submissions
2170+
unset(
2171+
$params['id'],
2172+
$params['act'],
2173+
$params['request_parts'],
2174+
$params['saveTicket'],
2175+
$params['saveAndCloseTicket'],
2176+
);
2177+
21692178
$ticket = $this->getTicket($id);
21702179

21712180
if (! $ticket) {
@@ -2176,10 +2185,14 @@ public function patch($id, $params): bool
21762185

21772186
$return = $this->ticketRepository->patchTicket($id, $params);
21782187

2188+
if (! $return) {
2189+
return false;
2190+
}
2191+
21792192
self::dispatchEvent('ticket_updated');
21802193

21812194
// Todo: create events and move notification logic to notification module
2182-
if (isset($params['status']) && $return) {
2195+
if (isset($params['status'])) {
21832196
$ticket = $this->getTicket($id);
21842197
$subject = sprintf($this->language->__('email_notifications.todo_update_subject'), $id, strip_tags($ticket->headline));
21852198
$actual_link = BASE_URL.'/dashboard/home#/tickets/showTicket/'.$id;
@@ -2199,14 +2212,9 @@ public function patch($id, $params): bool
21992212
$notification->message = $message;
22002213

22012214
$this->projectService->notifyProjectUsers($notification);
2202-
2203-
self::dispatchEvent('ticket_updated');
2204-
2205-
// Update ticket
2206-
return $this->patch($ticket->id, ['projectId' => $ticket->projectId, 'sprint' => '', 'dependingTicketId' => '', 'milestoneid' => '']);
22072215
}
22082216

2209-
return false;
2217+
return (bool) $return;
22102218
}
22112219

22122220
/**

app/Domain/Tickets/Templates/partials/quickadd-form.inc.php

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
$hasError = $isActive && ! empty($reopenState['error']);
1616
?>
1717

18-
<div class="quickaddContainer <?= $isEmpty ? 'quickaddContainer--empty' : '' ?>" data-status="<?= $statusId ?>" data-swimlane="<?= $swimlaneKey ?? '' ?>">
18+
<div class="quickaddContainer tw-mb-s <?= $isEmpty ? 'quickaddContainer--empty' : '' ?>" data-status="<?= $statusId ?>" data-swimlane="<?= $swimlaneKey ?? '' ?>">
1919
<a href="javascript:void(0);"
2020
class="quickAddLink <?= $isEmpty ? 'empty-state' : 'inline-add' ?>"
2121
onclick="leantime.kanbanController.toggleQuickAdd(this)"
@@ -40,11 +40,6 @@ class="quickAddForm <?= $isActive ? 'active' : '' ?>"
4040
<input type="hidden" name="sprint" value="<?= session('currentSprint') ?? '' ?>" />
4141
<input type="hidden" name="stay_open" value="0" data-stay-open-input />
4242

43-
<i class="fa fa-circle-question quickAddHelp"
44-
data-tippy-content="<strong>Keyboard Shortcuts:</strong><br>• Enter - Save and add another<br>• Shift+Enter - Save and close<br>• Esc - Cancel"
45-
tabindex="0"
46-
aria-label="Keyboard shortcuts help"></i>
47-
4843
<div class="form-group">
4944
<label for="headline-<?= $statusId ?>-<?= $swimlaneKey ?? 'default' ?>" class="sr-only">Task name</label>
5045
<input type="text"
@@ -71,10 +66,10 @@ class="form-control quickAddInput <?= $hasError ? 'error' : '' ?>"
7166
onclick="leantime.kanbanController.toggleQuickAdd(this.closest('.quickaddContainer').querySelector('.quickAddLink'))">
7267
Cancel
7368
</button>
74-
<i class="fa fa-circle-question quickAddHelp"
69+
<i class="fa fa-circle-question"
70+
data-tippy-content="<strong>Keyboard Shortcuts:</strong><br>Enter: Save and close<br>Shift+Enter: Save and add another<br>Esc: Cancel"
7571
tabindex="0"
76-
title="Enter: Save and close&#10;Shift+Enter: Save and add another&#10;Esc: Cancel"
77-
aria-label="Enter: Save and close, Shift+Enter: Save and add another, Esc: Cancel"></i>
72+
aria-label="Keyboard shortcuts help"></i>
7873
</div>
7974
</form>
8075
</div>

app/Domain/Tickets/Templates/showKanban.tpl.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@
199199

200200
<?php
201201
$statusId = $key;
202-
$swimlaneKey = $group['id'] ?? null;
202+
$swimlaneKey = $group['value'] ?? $group['id'] ?? null;
203203
$isEmpty = isset($emptyColumns[$key]);
204204
$currentGroupBy = $searchCriteria['groupBy'] ?? null;
205205
include __DIR__.'/partials/quickadd-form.inc.php';

app/Domain/Tickets/Templates/showList.tpl.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
<form action="" method="post">
6363
<input type="text" name="headline" autofocus placeholder="<?php echo $tpl->__('input.placeholders.create_task'); ?>" style="width: 100%;"/>
6464
<input type="hidden" name="sprint" value="<?= $currentSprint?>" />
65+
<input type="hidden" name="milestone" value="<?= htmlspecialchars((string) ($searchCriteria['milestone'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" />
66+
<input type="hidden" name="groupBy" value="<?= htmlspecialchars((string) ($searchCriteria['groupBy'] ?? ''), ENT_QUOTES, 'UTF-8') ?>" />
6567
<input type="hidden" name="quickadd" value="1"/>
6668
<input type="submit" class="btn btn-primary tw-mb-m" value="<?php echo $tpl->__('buttons.save'); ?>" name="saveTicket" style="vertical-align: top; "/>
6769
</form>

app/Domain/Widgets/Templates/components/moveableWidget.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</div>
1010

1111
@if($name != '' && $noTitle == false)
12-
<h5 class="subtitle tw-pb-m tw-float-left tw-mr-sm">{{ __($name) }}</h5>
12+
<h5 class="subtitle tw-pb-m tw-float-left tw-mr-sm" style="margin-top:-5px;">{{ __($name) }}</h5>
1313
@endif
1414
<div class="inlineDropDownContainer tw-float-right">
1515
<a href="javascript:void(0);" class="dropdown-toggle ticketDropDown editHeadline" data-toggle="dropdown">

app/Plugins

package-lock.json

Lines changed: 12 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)