7
7
use ChamiloSession as Session ;
8
8
9
9
/**
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.
14
11
*/
15
12
class NotebookManager
16
13
{
17
- /**
18
- * Constructor.
19
- */
20
- public function __construct ()
21
- {
22
- }
14
+ public function __construct () {}
23
15
24
16
/**
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.
32
18
*/
33
- public static function javascript_notebook ()
19
+ public static function javascript_notebook (): string
34
20
{
35
21
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> " ;
44
30
}
45
31
32
+ /**
33
+ * Create a note.
34
+ */
46
35
public static function saveNote (array $ values , $ userId = 0 , $ courseId = 0 , $ sessionId = 0 )
47
36
{
48
37
if (!is_array ($ values ) || empty ($ values ['note_title ' ])) {
49
38
return false ;
50
39
}
51
40
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 );
55
44
$ sessionId = $ sessionId ?: api_get_session_id ();
56
- $ session = api_get_session_entity ($ sessionId );
45
+ $ session = api_get_session_entity ($ sessionId );
57
46
58
47
$ notebook = new CNotebook ();
59
48
$ notebook ->setParent ($ course );
60
49
$ notebook
61
50
->setTitle ($ values ['note_title ' ])
62
51
->setDescription ($ values ['note_comment ' ])
63
52
->setUser (api_get_user_entity ($ userId ))
64
- ->addCourseLink ($ course , $ session )
65
- ;
53
+ ->addCourseLink ($ course , $ session );
66
54
67
55
$ repo = Container::getNotebookRepository ();
68
56
$ repo ->create ($ notebook );
@@ -71,9 +59,7 @@ public static function saveNote(array $values, $userId = 0, $courseId = 0, $sess
71
59
}
72
60
73
61
/**
74
- * @param int $notebook_id
75
- *
76
- * @return array
62
+ * Read note info for edit form defaults.
77
63
*/
78
64
public static function get_note_information (int $ notebook_id ): array
79
65
{
@@ -82,166 +68,198 @@ public static function get_note_information(int $notebook_id): array
82
68
}
83
69
84
70
$ repo = Container::getNotebookRepository ();
71
+ /** @var CNotebook|null $note */
85
72
$ note = $ repo ->find ($ notebook_id );
86
- if (empty ( $ note) ) {
73
+ if (! $ note ) {
87
74
return [];
88
75
}
89
- /** @var CNotebook $note */
76
+
90
77
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 (),
95
82
];
96
83
}
97
84
98
85
/**
99
- * Updates a note
100
- * @param array $values
86
+ * Update a note (owner only).
101
87
*/
102
- public static function updateNote ($ values ): mixed
88
+ public static function updateNote ($ values ): bool
103
89
{
104
90
if (!is_array ($ values ) || empty ($ values ['note_title ' ])) {
105
91
return false ;
106
92
}
107
93
108
94
$ repo = Container::getNotebookRepository ();
109
- $ notebook = $ repo ->find ($ values ['notebook_id ' ]);
95
+ /** @var CNotebook|null $notebook */
96
+ $ notebook = $ repo ->find ((int ) ($ values ['notebook_id ' ] ?? 0 ));
110
97
111
98
if (!$ notebook ) {
112
99
return false ;
113
100
}
114
101
102
+ if ($ notebook ->getUser ()?->getId() !== api_get_user_id ()) {
103
+ return false ;
104
+ }
105
+
115
106
$ notebook
116
107
->setTitle ($ values ['note_title ' ])
117
- ->setDescription ($ values ['note_comment ' ])
118
- ;
108
+ ->setDescription ($ values ['note_comment ' ]);
119
109
120
110
$ repo ->update ($ notebook );
121
111
122
112
return true ;
123
113
}
124
114
125
115
/**
126
- * @param int $notebook_id
127
- *
128
- * @return bool
116
+ * Delete a note (owner only).
129
117
*/
130
118
public static function delete_note ($ notebook_id ): bool
131
119
{
132
- $ notebook_id = ( int ) $ notebook_id ;
120
+ $ repo = Container:: getNotebookRepository () ;
133
121
134
- if (empty ($ notebook_id )) {
122
+ /** @var CNotebook|null $note */
123
+ $ note = $ repo ->find ((int ) $ notebook_id );
124
+ if (!$ note ) {
135
125
return false ;
136
126
}
137
127
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 ()) {
150
129
return false ;
151
130
}
152
131
132
+ $ repo ->delete ($ note );
133
+
153
134
return true ;
154
135
}
155
136
156
137
/**
157
- * Display notes.
138
+ * List notes.
158
139
*/
159
- public static function display_notes ()
140
+ public static function display_notes (): void
160
141
{
161
142
$ sessionId = api_get_session_id ();
162
- $ user = api_get_user_entity ();
143
+ $ user = api_get_user_entity ();
163
144
145
+ // Sorting direction
164
146
if (!isset ($ _GET ['direction ' ])) {
165
- $ sort_direction = 'ASC ' ;
147
+ $ sort_direction = 'ASC ' ;
166
148
$ link_sort_direction = 'DESC ' ;
167
149
} elseif ('ASC ' == $ _GET ['direction ' ]) {
168
- $ sort_direction = 'ASC ' ;
150
+ $ sort_direction = 'ASC ' ;
169
151
$ link_sort_direction = 'DESC ' ;
170
152
} else {
171
- $ sort_direction = 'DESC ' ;
153
+ $ sort_direction = 'DESC ' ;
172
154
$ link_sort_direction = 'ASC ' ;
173
155
}
174
156
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
+ );
183
164
}
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 )) {
200
183
$ notebookView = 'creation_date ' ;
201
- }
202
-
203
- if (!in_array ($ notebookView , ['creation_date ' , 'update_date ' , 'title ' ])) {
204
184
Session::write ('notebook_view ' , 'creation_date ' );
205
185
}
206
186
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
+ }
214
208
215
- $ notebooks = $ repo -> getResourcesByCourse ( $ course , $ session ) ;
209
+ echo ' <div class="space-y-4"> ' ;
216
210
217
211
/** @var CNotebook $item */
218
212
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
+
228
216
$ 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 () );
231
219
}
232
220
233
- $ actions = '<a href=" ' .api_get_self ().'?action=editnote¬ebook_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¬ebook_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¬ebook_id= ' .$ item ->getIid ();
222
+ $ delUrl = api_get_self ().'? ' .api_get_cidreq ().'&action=deletenote¬ebook_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> ' ;
245
261
}
262
+
263
+ echo '</div> ' ;
246
264
}
247
265
}
0 commit comments