Skip to content

Commit 730552f

Browse files
Merge pull request #6751 from christianbeeznest/GH-6743
Notebook: Restore list & CRUD via Doctrine; filter by owner; sort; C2 UI - refs #6743
2 parents bd04f58 + 4ab3572 commit 730552f

File tree

3 files changed

+171
-179
lines changed

3 files changed

+171
-179
lines changed

public/main/inc/lib/notebook.lib.php

Lines changed: 146 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -7,62 +7,50 @@
77
use ChamiloSession as Session;
88

99
/**
10-
* This class provides methods for the notebook management.
11-
* Include/require it in your code to use its features.
12-
*
13-
* @author Carlos Vargas <[email protected]>, move code of main/notebook up here
10+
* Notebook tool manager.
1411
*/
1512
class NotebookManager
1613
{
17-
/**
18-
* Constructor.
19-
*/
20-
public function __construct()
21-
{
22-
}
14+
public function __construct() {}
2315

2416
/**
25-
* a little bit of javascript to display a prettier warning when deleting a note.
26-
*
27-
* @return string
28-
*
29-
* @author Patrick Cool <[email protected]>, Ghent University, Belgium
30-
*
31-
* @version januari 2009, dokeos 1.8.6
17+
* Delete confirmation JS.
3218
*/
33-
public static function javascript_notebook()
19+
public static function javascript_notebook(): string
3420
{
3521
return "<script>
36-
function confirmation (name)
37-
{
38-
if (confirm(\" ".get_lang("Are you sure you want to delete this note")." \"+ name + \" ?\"))
39-
{return true;}
40-
else
41-
{return false;}
42-
}
43-
</script>";
22+
function confirmation (name)
23+
{
24+
if (confirm(\" ".get_lang("Are you sure you want to delete this note")." \"+ name + \" ?\"))
25+
{return true;}
26+
else
27+
{return false;}
28+
}
29+
</script>";
4430
}
4531

32+
/**
33+
* Create a note.
34+
*/
4635
public static function saveNote(array $values, $userId = 0, $courseId = 0, $sessionId = 0)
4736
{
4837
if (!is_array($values) || empty($values['note_title'])) {
4938
return false;
5039
}
5140

52-
$userId = $userId ?: api_get_user_id();
53-
$courseId = $courseId ?: api_get_course_int_id();
54-
$course = api_get_course_entity($courseId);
41+
$userId = $userId ?: api_get_user_id();
42+
$courseId = $courseId ?: api_get_course_int_id();
43+
$course = api_get_course_entity($courseId);
5544
$sessionId = $sessionId ?: api_get_session_id();
56-
$session = api_get_session_entity($sessionId);
45+
$session = api_get_session_entity($sessionId);
5746

5847
$notebook = new CNotebook();
5948
$notebook->setParent($course);
6049
$notebook
6150
->setTitle($values['note_title'])
6251
->setDescription($values['note_comment'])
6352
->setUser(api_get_user_entity($userId))
64-
->addCourseLink($course, $session)
65-
;
53+
->addCourseLink($course, $session);
6654

6755
$repo = Container::getNotebookRepository();
6856
$repo->create($notebook);
@@ -71,9 +59,7 @@ public static function saveNote(array $values, $userId = 0, $courseId = 0, $sess
7159
}
7260

7361
/**
74-
* @param int $notebook_id
75-
*
76-
* @return array
62+
* Read note info for edit form defaults.
7763
*/
7864
public static function get_note_information(int $notebook_id): array
7965
{
@@ -82,166 +68,198 @@ public static function get_note_information(int $notebook_id): array
8268
}
8369

8470
$repo = Container::getNotebookRepository();
71+
/** @var CNotebook|null $note */
8572
$note = $repo->find($notebook_id);
86-
if (empty($note)) {
73+
if (!$note) {
8774
return [];
8875
}
89-
/** @var CNotebook $note */
76+
9077
return [
91-
'iid' => $note->getIid(),
92-
'title' => $note->getTitle(),
93-
'description' => $note->getDescription(),
94-
'session_id' => api_get_session_id(),
78+
'notebook_id' => $note->getIid(),
79+
'note_title' => $note->getTitle(),
80+
'note_comment'=> $note->getDescription(),
81+
'session_id' => $note->getFirstResourceLink()?->getSession()?->getId() ?? api_get_session_id(),
9582
];
9683
}
9784

9885
/**
99-
* Updates a note
100-
* @param array $values
86+
* Update a note (owner only).
10187
*/
102-
public static function updateNote($values): mixed
88+
public static function updateNote($values): bool
10389
{
10490
if (!is_array($values) || empty($values['note_title'])) {
10591
return false;
10692
}
10793

10894
$repo = Container::getNotebookRepository();
109-
$notebook = $repo->find($values['notebook_id']);
95+
/** @var CNotebook|null $notebook */
96+
$notebook = $repo->find((int) ($values['notebook_id'] ?? 0));
11097

11198
if (!$notebook) {
11299
return false;
113100
}
114101

102+
if ($notebook->getUser()?->getId() !== api_get_user_id()) {
103+
return false;
104+
}
105+
115106
$notebook
116107
->setTitle($values['note_title'])
117-
->setDescription($values['note_comment'])
118-
;
108+
->setDescription($values['note_comment']);
119109

120110
$repo->update($notebook);
121111

122112
return true;
123113
}
124114

125115
/**
126-
* @param int $notebook_id
127-
*
128-
* @return bool
116+
* Delete a note (owner only).
129117
*/
130118
public static function delete_note($notebook_id): bool
131119
{
132-
$notebook_id = (int) $notebook_id;
120+
$repo = Container::getNotebookRepository();
133121

134-
if (empty($notebook_id)) {
122+
/** @var CNotebook|null $note */
123+
$note = $repo->find((int) $notebook_id);
124+
if (!$note) {
135125
return false;
136126
}
137127

138-
// Database table definition
139-
$table = Database::get_course_table(TABLE_NOTEBOOK);
140-
$course_id = api_get_course_int_id();
141-
142-
$sql = "DELETE FROM $table
143-
WHERE
144-
iid='".$notebook_id."' AND
145-
user_id = '".api_get_user_id()."'";
146-
$result = Database::query($sql);
147-
$affected_rows = Database::affected_rows($result);
148-
149-
if (1 != $affected_rows) {
128+
if ($note->getUser()?->getId() !== api_get_user_id()) {
150129
return false;
151130
}
152131

132+
$repo->delete($note);
133+
153134
return true;
154135
}
155136

156137
/**
157-
* Display notes.
138+
* List notes.
158139
*/
159-
public static function display_notes()
140+
public static function display_notes(): void
160141
{
161142
$sessionId = api_get_session_id();
162-
$user = api_get_user_entity();
143+
$user = api_get_user_entity();
163144

145+
// Sorting direction
164146
if (!isset($_GET['direction'])) {
165-
$sort_direction = 'ASC';
147+
$sort_direction = 'ASC';
166148
$link_sort_direction = 'DESC';
167149
} elseif ('ASC' == $_GET['direction']) {
168-
$sort_direction = 'ASC';
150+
$sort_direction = 'ASC';
169151
$link_sort_direction = 'DESC';
170152
} else {
171-
$sort_direction = 'DESC';
153+
$sort_direction = 'DESC';
172154
$link_sort_direction = 'ASC';
173155
}
174156

175-
// action links
176-
echo '<div class="actions">';
177-
if (!api_is_anonymous()) {
178-
if (0 == $sessionId || api_is_allowed_to_session_edit(false, true)) {
179-
echo '<a href="index.php?'.api_get_cidreq().'&action=addnote">'.
180-
Display::getMdiIcon(ActionIcon::ADD, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Add new note in my personal notebook')).
181-
'</a>';
182-
}
157+
// Toolbar (single group string)
158+
$left = '';
159+
if (!api_is_anonymous() && (0 == $sessionId || api_is_allowed_to_session_edit(false, true))) {
160+
$left .= Display::url(
161+
Display::getMdiIcon(ActionIcon::ADD, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Add new note in my personal notebook')),
162+
api_get_self().'?'.api_get_cidreq().'&action=addnote'
163+
);
183164
}
184-
185-
echo '<a
186-
href="index.php?'.api_get_cidreq().'&action=changeview&view=creation_date&direction='.$link_sort_direction.'">'.
187-
Display::getMdiIcon(ActionIcon::SORT_DATE, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Sort by date created')).
188-
'</a>';
189-
echo '<a
190-
href="index.php?'.api_get_cidreq().'&action=changeview&view=update_date&direction='.$link_sort_direction.'">'.
191-
Display::getMdiIcon(ActionIcon::SORT_DATE, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Sort by date last modified')).
192-
'</a>';
193-
echo '<a href="index.php?'.api_get_cidreq().'&action=changeview&view=title&direction='.$link_sort_direction.'">'.
194-
Display::getMdiIcon(ActionIcon::SORT_ALPHA, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Sort by title')).
195-
'</a>';
196-
echo '</div>';
197-
198-
$notebookView = Session::read('notebook_view');
199-
if (empty($notebookView)) {
165+
$left .= Display::url(
166+
Display::getMdiIcon(ActionIcon::SORT_DATE, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Sort by date created')),
167+
api_get_self().'?'.api_get_cidreq().'&action=changeview&view=creation_date&direction='.$link_sort_direction
168+
);
169+
$left .= Display::url(
170+
Display::getMdiIcon(ActionIcon::SORT_DATE, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Sort by date last modified')),
171+
api_get_self().'?'.api_get_cidreq().'&action=changeview&view=update_date&direction='.$link_sort_direction
172+
);
173+
$left .= Display::url(
174+
Display::getMdiIcon(ActionIcon::SORT_ALPHA, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Sort by title')),
175+
api_get_self().'?'.api_get_cidreq().'&action=changeview&view=title&direction='.$link_sort_direction
176+
);
177+
178+
echo '<div class="mb-4">'.Display::toolbarAction('nb_actions', [$left]).'</div>';
179+
180+
// View
181+
$notebookView = Session::read('notebook_view') ?: 'creation_date';
182+
if (!in_array($notebookView, ['creation_date', 'update_date', 'title'], true)) {
200183
$notebookView = 'creation_date';
201-
}
202-
203-
if (!in_array($notebookView, ['creation_date', 'update_date', 'title'])) {
204184
Session::write('notebook_view', 'creation_date');
205185
}
206186

207-
//$order_by = " ORDER BY $notebookView $sort_direction ";
208-
209-
$course_id = api_get_course_int_id();
210-
211-
$repo = Container::getNotebookRepository();
212-
$course = api_get_course_entity($course_id);
213-
$session = api_get_session_entity($sessionId);
187+
// Load notebooks via repository helper (filters by course/session/owner + order)
188+
$fieldMap = ['creation_date' => 'creation_date', 'update_date' => 'update_date', 'title' => 'title'];
189+
$orderField = $fieldMap[$notebookView] ?? 'creation_date';
190+
191+
$notebooks = Container::getNotebookRepository()->findByUser(
192+
$user,
193+
api_get_course_entity(),
194+
api_get_session_entity($sessionId),
195+
$orderField,
196+
$sort_direction
197+
);
198+
199+
// Empty state
200+
if (empty($notebooks)) {
201+
echo '<div class="rounded-2xl border border-dashed border-gray-200 p-10 text-center text-gray-600 bg-white">
202+
<div class="text-lg font-medium mb-1">'.get_lang('No notes yet').'</div>
203+
<div class="text-sm">'.get_lang('Create your first note to get started.').'</div>
204+
</div>';
205+
206+
return;
207+
}
214208

215-
$notebooks = $repo->getResourcesByCourse($course, $session);
209+
echo '<div class="space-y-4">';
216210

217211
/** @var CNotebook $item */
218212
foreach ($notebooks as $item) {
219-
$notebookData = [
220-
'id' => $item->getIid(),
221-
'title' => $item->getTitle(),
222-
'description' => $item->getDescription(),
223-
'creation_date' => $item->getCreationDate(),
224-
'update_date' => $item->getUpdateDate(),
225-
];
226-
// Validation when belongs to a session
227-
$session_img = api_get_session_image($course_id, $user);
213+
$sessionIdForIcon = $item->getFirstResourceLink()?->getSession()?->getId();
214+
$session_img = api_get_session_image($sessionIdForIcon, $user);
215+
228216
$updateValue = '';
229-
if ($notebookData['update_date'] != $notebookData['creation_date']) {
230-
$updateValue = ', '.get_lang('Updated').': '.Display::dateToStringAgoAndLongDate($notebookData['update_date']);
217+
if ($item->getUpdateDate() && $item->getUpdateDate() != $item->getCreationDate()) {
218+
$updateValue = ', '.get_lang('Updated').': '.Display::dateToStringAgoAndLongDate($item->getUpdateDate());
231219
}
232220

233-
$actions = '<a href="'.api_get_self().'?action=editnote&notebook_id='.$notebookData['id'].'">'.
234-
Display::getMdiIcon(ActionIcon::EDIT, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Edit')).'</a>';
235-
$actions .= '<a
236-
href="'.api_get_self().'?action=deletenote&notebook_id='.$notebookData['id'].'"
237-
onclick="return confirmation(\''.$notebookData['title'].'\');">'.
238-
Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')).'</a>';
239-
240-
echo Display::panel(
241-
$notebookData['description'],
242-
$notebookData['title'].$session_img.' <div class="pull-right">'.$actions.'</div>',
243-
get_lang('Creation date').': '.Display::dateToStringAgoAndLongDate($notebookData['creation_date']).$updateValue
244-
);
221+
$editUrl = api_get_self().'?'.api_get_cidreq().'&action=editnote&notebook_id='.$item->getIid();
222+
$delUrl = api_get_self().'?'.api_get_cidreq().'&action=deletenote&notebook_id='.$item->getIid();
223+
224+
$actions =
225+
'<div class="flex items-center gap-3">'.
226+
Display::url(
227+
Display::getMdiIcon(ActionIcon::EDIT, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Edit')),
228+
$editUrl,
229+
['class' => 'text-sky-700 hover:text-sky-800', 'aria-label' => get_lang('Edit')]
230+
).
231+
Display::url(
232+
Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')),
233+
$delUrl,
234+
[
235+
'onclick' => "return confirmation('".addslashes($item->getTitle())."');",
236+
'class' => 'text-rose-600 hover:text-rose-700',
237+
'aria-label' => get_lang('Delete'),
238+
]
239+
).
240+
'</div>';
241+
242+
// Sanitize description for safe display (keeps basic formatting)
243+
$desc = Security::remove_XSS($item->getDescription(), COURSEMANAGERLOWSECURITY);
244+
245+
echo '<div class="bg-white shadow-sm rounded-2xl p-5 border border-gray-100">';
246+
echo '<div class="flex items-start justify-between gap-4">';
247+
echo '<div class="min-w-0">';
248+
echo '<div class="flex items-center gap-2">';
249+
echo '<h3 class="font-semibold text-gray-900 text-lg leading-snug break-words">'.$item->getTitle().'</h3>';
250+
echo '<span class="inline-flex">'.$session_img.'</span>';
251+
echo '</div>';
252+
echo '<div class="prose prose-sm max-w-none text-gray-700 mt-2">'.$desc.'</div>';
253+
echo '</div>';
254+
echo $actions;
255+
echo '</div>';
256+
257+
echo '<div class="mt-3 text-xs text-gray-500">';
258+
echo get_lang('Creation date').': '.Display::dateToStringAgoAndLongDate($item->getCreationDate()).$updateValue;
259+
echo '</div>';
260+
echo '</div>';
245261
}
262+
263+
echo '</div>';
246264
}
247265
}

0 commit comments

Comments
 (0)