Skip to content

Commit b145dcb

Browse files
authored
fix: Missing attachments in entity notes
1 parent 58c4b3a commit b145dcb

File tree

4 files changed

+204
-8
lines changed

4 files changed

+204
-8
lines changed

src/Html.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5181,7 +5181,7 @@ class='form-control'
51815181
. ($p['onlyimages'] ? " accept='.gif,.png,.jpg,.jpeg'" : "") . ">";
51825182

51835183
$display .= "<div id='progress{$rand_id}' style='display:none'>" .
5184-
"<div class='uploadbar' style='width: 0%;'></div></div>";
5184+
"<div role='progressbar' class='uploadbar' style='width: 0%;'></div></div>";
51855185
$progressall_js = "
51865186
progressall: function(event, data) {
51875187
var progress = parseInt(data.loaded / data.total * 100, 10);

templates/components/itilobject/fields_panel.html.twig

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,14 @@
303303
<div id="notes" class="accordion-collapse collapse {{ notes_show ? "show" : "" }}" aria-labelledby="notes-heading">
304304
<div class="accordion-body row m-0 mt-n2">
305305
{% for note in notes %}
306-
<div class="alert alert-info entitynote rich_text_container" role="alert">{{ note['content']|safe_html }}</div>
306+
<div class="alert alert-info entitynote rich_text_container" role="alert">
307+
{{ note['content']|safe_html }}
308+
309+
{{ include('components/itilobject/timeline/sub_documents.html.twig', {
310+
'item': note,
311+
'entry': note
312+
}) }}
313+
</div>
307314
{% endfor %}
308315
</div>
309316
</div>

templates/components/notepad/form.html.twig

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,15 @@
148148
'entry': note
149149
}) }}
150150

151-
<div id="edit-{{ id }}" class="modal fade">
151+
</form>
152+
153+
<div id="edit-{{ id }}" class="modal fade">
154+
<div class="modal-dialog modal-xl">
155+
<div class="modal-content">
156+
<form action="{{ url }}" method="post" autocomplete="off" data-submit-once>
157+
<input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token() }}" />
158+
<input type="hidden" name="id" value="{{ note['id'] }}" />
152159

153-
<div class="modal-dialog modal-xl">
154-
<div class="modal-content">
155160
<div class="modal-header">
156161
<h3 class="modal-title">
157162
<i class="ti ti-notes"></i>
@@ -195,11 +200,10 @@
195200
<span>{{ __('Update') }}</span>
196201
</button>
197202
</div>
198-
</div>
203+
</form>
199204
</div>
200205
</div>
201-
202-
</form>
206+
</div>
203207
</div>
204208
</div>
205209
</div>
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/**
2+
* ---------------------------------------------------------------------
3+
*
4+
* GLPI - Gestionnaire Libre de Parc Informatique
5+
*
6+
* http://glpi-project.org
7+
*
8+
* @copyright 2015-2025 Teclib' and contributors.
9+
* @licence https://www.gnu.org/licenses/gpl-3.0.html
10+
*
11+
* ---------------------------------------------------------------------
12+
*
13+
* LICENSE
14+
*
15+
* This file is part of GLPI.
16+
*
17+
* This program is free software: you can redistribute it and/or modify
18+
* it under the terms of the GNU General Public License as published by
19+
* the Free Software Foundation, either version 3 of the License, or
20+
* (at your option) any later version.
21+
*
22+
* This program is distributed in the hope that it will be useful,
23+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
24+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25+
* GNU General Public License for more details.
26+
*
27+
* You should have received a copy of the GNU General Public License
28+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
29+
*
30+
* ---------------------------------------------------------------------
31+
*/
32+
33+
function addEntityNote(visible_on_tickets = false) {
34+
cy.findByRole('button', { name: 'Add a note' }).click();
35+
cy.findByLabelText('Content').awaitTinyMCE().type('This is a test note');
36+
37+
if (visible_on_tickets) {
38+
cy.findByRole('checkbox', { name: 'Visible on tickets' }).check();
39+
}
40+
41+
cy.findByRole('button', { name: 'Add' }).click();
42+
}
43+
44+
function addEntityNoteWithAttachment(visible_on_tickets = false) {
45+
cy.findByRole('button', { name: 'Add a note' }).click();
46+
cy.findByLabelText('Content').awaitTinyMCE().type('This is a test note');
47+
48+
if (visible_on_tickets) {
49+
cy.findByRole('checkbox', { name: 'Visible on tickets' }).check();
50+
}
51+
52+
cy.get('input[type="file"]').selectFile('fixtures/uploads/bar.png');
53+
cy.findByRole('progressbar').should('contain', 'Upload successful');
54+
cy.get('input[type="file"]').selectFile('fixtures/uploads/bar.txt');
55+
cy.findByRole('progressbar').should('contain', 'Upload successful');
56+
cy.findByRole('progressbar').should('not.exist');
57+
58+
cy.findByRole('button', { name: 'Add' }).click();
59+
}
60+
61+
describe('Entity notes', () => {
62+
beforeEach(() => {
63+
cy.login();
64+
65+
// Create entity
66+
cy.createWithAPI('Entity', {
67+
name: `EntityNotesTest ${Date.now()}`,
68+
}).as('entity_id').then((entity_id) => {
69+
cy.visit(`/front/entity.form.php?id=${entity_id}`);
70+
cy.findByRole('tab', { name: 'Notes' }).click();
71+
});
72+
});
73+
74+
it('can add entity notes', () => {
75+
addEntityNote();
76+
77+
cy.findByRole('button', { name: /^#\d+ - \d{4}-\d{2}-\d{2} \d{2}:\d{2}$/ }).click();
78+
cy.findByRole('tabpanel').find('.rich_text_container').should('contain', 'This is a test note');
79+
});
80+
81+
it('can delete entity notes', () => {
82+
addEntityNote();
83+
84+
// Delete the note
85+
cy.findByRole('button', { name: /^#\d+ - \d{4}-\d{2}-\d{2} \d{2}:\d{2}$/ }).click();
86+
cy.findByRole('button', { name: 'Delete' }).click();
87+
88+
// Check that the note is deleted
89+
cy.findByRole('button', { name: /^#\d+ - \d{4}-\d{2}-\d{2} \d{2}:\d{2}$/ }).should('not.exist');
90+
});
91+
92+
it('can edit entity notes', () => {
93+
addEntityNote();
94+
95+
// Edit the note
96+
cy.findByRole('button', { name: /^#\d+ - \d{4}-\d{2}-\d{2} \d{2}:\d{2}$/ }).click();
97+
cy.findByRole('button', { name: 'Edit' }).click();
98+
cy.findByRole('dialog').within(() => {
99+
cy.findByLabelText('Content').awaitTinyMCE().clear();
100+
cy.findByLabelText('Content').awaitTinyMCE().type('This is an edited test note');
101+
cy.findByRole('button', { name: 'Update' }).click();
102+
});
103+
104+
// Check that the note is edited
105+
cy.findByRole('dialog').should('not.exist');
106+
cy.findByRole('tabpanel').find('.rich_text_container').should('contain', 'This is an edited test note');
107+
});
108+
109+
it('can edit entity notes with attachments', () => {
110+
addEntityNoteWithAttachment();
111+
112+
// Edit the note
113+
cy.findByRole('button', { name: /^#\d+ - \d{4}-\d{2}-\d{2} \d{2}:\d{2}$/ }).click();
114+
cy.findByRole('button', { name: 'Edit' }).click();
115+
116+
cy.findByRole('dialog').within(() => {
117+
cy.findByLabelText('Content').awaitTinyMCE().clear();
118+
cy.findByLabelText('Content').awaitTinyMCE().type('This is an edited test note');
119+
cy.findByRole('button', { name: 'Update' }).click();
120+
});
121+
122+
// Check that the note is edited
123+
cy.findByRole('dialog').should('not.exist');
124+
cy.findByRole('tabpanel').find('.rich_text_container').should('contain', 'This is an edited test note');
125+
});
126+
127+
it('entity note can be visible on tickets', () => {
128+
addEntityNote(true);
129+
cy.get('@entity_id').then((entity_id) => {
130+
// Create a ticket
131+
cy.createWithAPI('Ticket', {
132+
name: `Ticket ${Date.now()}`,
133+
entities_id: entity_id,
134+
}).then((ticket_id) => {
135+
cy.visit(`/front/ticket.form.php?id=${ticket_id}`);
136+
cy.findByRole('region', { name: 'Notes' }).should('exist').within(() => {
137+
cy.findByRole('generic', { name: 'Entity notes 1' })
138+
.find('p').should('contain', 'This is a test note');
139+
});
140+
});
141+
});
142+
});
143+
144+
it('entity note attachments are visible on tickets', () => {
145+
addEntityNoteWithAttachment(true);
146+
147+
cy.get('@entity_id').then((entity_id) => {
148+
// Create a ticket
149+
cy.createWithAPI('Ticket', {
150+
name: `Ticket ${Date.now()}`,
151+
entities_id: entity_id,
152+
}).then((ticket_id) => {
153+
cy.visit(`/front/ticket.form.php?id=${ticket_id}`);
154+
cy.findByRole('region', { name: 'Notes' }).should('exist').within(() => {
155+
cy.findByRole('alert').within(() => {
156+
cy.findByRole('figure').should('exist');
157+
cy.findByRole('link', { name: 'File extension bar.txt' }).should('exist');
158+
});
159+
});
160+
});
161+
});
162+
});
163+
164+
it('can add an attachment to an entity note', () => {
165+
addEntityNote();
166+
167+
// Add an attachment
168+
cy.findByRole('button', { name: /^#\d+ - \d{4}-\d{2}-\d{2} \d{2}:\d{2}$/ }).click();
169+
cy.findByRole('button', { name: 'Edit' }).click();
170+
171+
cy.findByRole('dialog').within(() => {
172+
cy.get('input[type="file"]').selectFile('fixtures/uploads/bar.png');
173+
cy.findByRole('progressbar').should('contain', 'Upload successful');
174+
cy.get('input[type="file"]').selectFile('fixtures/uploads/bar.txt');
175+
cy.findByRole('progressbar').should('contain', 'Upload successful');
176+
cy.findByRole('progressbar').should('not.exist');
177+
cy.findByRole('button', { name: 'Update' }).click();
178+
});
179+
180+
// Check that the attachment is added
181+
cy.findByRole('button', { name: /^#\d+ - \d{4}-\d{2}-\d{2} \d{2}:\d{2}$/ }).click();
182+
cy.findByRole('figure').should('exist');
183+
cy.findByRole('link', { name: 'File extension bar.txt' }).should('exist');
184+
});
185+
});

0 commit comments

Comments
 (0)