1 | <?php |
||||||
2 | /* For licensing terms, see /license.txt */ |
||||||
3 | |||||||
4 | use ChamiloSession as Session; |
||||||
5 | |||||||
6 | /** |
||||||
7 | * Functions and main code for the download folder feature. |
||||||
8 | * |
||||||
9 | * @package chamilo.document |
||||||
10 | */ |
||||||
11 | set_time_limit(0); |
||||||
12 | |||||||
13 | require_once __DIR__.'/../inc/global.inc.php'; |
||||||
14 | |||||||
15 | api_protect_course_script(); |
||||||
16 | |||||||
17 | $sysCoursePath = api_get_path(SYS_COURSE_PATH); |
||||||
18 | $courseInfo = api_get_course_info(); |
||||||
19 | $courseId = api_get_course_int_id(); |
||||||
20 | $sessionId = api_get_session_id(); |
||||||
21 | $groupId = api_get_group_id(); |
||||||
22 | $courseCode = api_get_course_id(); |
||||||
23 | |||||||
24 | // Check if folder exists in current course. |
||||||
25 | $documentInfo = DocumentManager::get_document_data_by_id( |
||||||
26 | $_GET['id'], |
||||||
27 | $courseCode, |
||||||
28 | false, |
||||||
29 | 0 |
||||||
30 | ); |
||||||
31 | |||||||
32 | if (!empty($sessionId)) { |
||||||
33 | /* If no data found and session id exists |
||||||
34 | try to look the file inside the session */ |
||||||
35 | |||||||
36 | if (empty($documentInfo)) { |
||||||
37 | $documentInfo = DocumentManager::get_document_data_by_id( |
||||||
38 | $_GET['id'], |
||||||
39 | $courseCode, |
||||||
40 | false, |
||||||
41 | $sessionId |
||||||
42 | ); |
||||||
43 | } |
||||||
44 | } |
||||||
45 | |||||||
46 | $path = $documentInfo['path']; |
||||||
47 | |||||||
48 | if (empty($path)) { |
||||||
49 | $path = '/'; |
||||||
50 | } |
||||||
51 | |||||||
52 | // A student should not be able to download a root shared directory |
||||||
53 | if (($path == '/shared_folder' || |
||||||
54 | $path == '/shared_folder_session_'.api_get_session_id()) && |
||||||
55 | (!api_is_allowed_to_edit() || !api_is_platform_admin()) |
||||||
56 | ) { |
||||||
57 | api_not_allowed(true); |
||||||
58 | exit; |
||||||
59 | } |
||||||
60 | |||||||
61 | // Creating a ZIP file. |
||||||
62 | $tempZipFile = api_get_path(SYS_ARCHIVE_PATH).api_get_unique_id().".zip"; |
||||||
63 | |||||||
64 | $zip = new PclZip($tempZipFile); |
||||||
65 | $doc_table = Database::get_course_table(TABLE_DOCUMENT); |
||||||
66 | $prop_table = Database::get_course_table(TABLE_ITEM_PROPERTY); |
||||||
67 | |||||||
68 | // We need this path to clean it out of the zip file |
||||||
69 | // I'm not using dir name as it gives too much problems (cfr.) |
||||||
70 | $remove_dir = ($path != '/') ? substr($path, 0, strlen($path) - strlen(basename($path))) : '/'; |
||||||
71 | |||||||
72 | // Put the files in the zip |
||||||
73 | // 2 possibilities: Admins get all files and folders in the selected folder (except for the deleted ones) |
||||||
74 | // Normal users get only visible files that are in visible folders |
||||||
75 | function fixDocumentNameCallback($p_event, &$p_header) |
||||||
76 | { |
||||||
77 | global $remove_dir; |
||||||
78 | $files = Session::read('doc_files_to_download'); |
||||||
79 | $storedFile = $remove_dir.$p_header['stored_filename']; |
||||||
80 | |||||||
81 | if (!isset($files[$storedFile])) { |
||||||
82 | return 0; |
||||||
83 | } |
||||||
84 | |||||||
85 | $documentData = $files[$storedFile]; |
||||||
86 | $documentNameFixed = DocumentManager::undoFixDocumentName( |
||||||
87 | $documentData['path'], |
||||||
88 | $documentData['c_id'], |
||||||
89 | $documentData['session_id'], |
||||||
90 | $documentData['to_group_id'] |
||||||
91 | ); |
||||||
92 | |||||||
93 | // Changes file.phps to file.php |
||||||
94 | $basename = basename($documentNameFixed); |
||||||
95 | $basenamePHPFixed = str_replace('.phps', '.php', $basename); |
||||||
96 | $documentNameFixed = str_replace( |
||||||
97 | $basename, |
||||||
98 | $basenamePHPFixed, |
||||||
99 | $documentNameFixed |
||||||
100 | ); |
||||||
101 | |||||||
102 | if ($remove_dir != '/') { |
||||||
103 | $documentNameFixed = str_replace($remove_dir, '/', $documentNameFixed); |
||||||
104 | if (substr($documentNameFixed, 0, 1) == '/') { |
||||||
105 | $documentNameFixed = substr($documentNameFixed, 1, api_strlen($documentNameFixed)); |
||||||
106 | } |
||||||
107 | } else { |
||||||
108 | $documentNameFixed = ltrim($documentNameFixed, '/'); |
||||||
109 | } |
||||||
110 | |||||||
111 | $p_header['stored_filename'] = $documentNameFixed; |
||||||
112 | |||||||
113 | return 1; |
||||||
114 | } |
||||||
115 | |||||||
116 | $groupJoin = ''; |
||||||
117 | if (!empty($groupId)) { |
||||||
118 | $table = Database::get_course_table(TABLE_GROUP); |
||||||
119 | $groupJoin = " INNER JOIN $table g ON (g.iid = props.to_group_id AND g.c_id = docs.c_id)"; |
||||||
120 | $groupCondition = " g.id = ".$groupId; |
||||||
121 | } else { |
||||||
122 | $groupCondition = " (props.to_group_id = 0 OR props.to_group_id IS NULL ) "; |
||||||
123 | } |
||||||
124 | |||||||
125 | $userIsSubscribed = CourseManager::is_user_subscribed_in_course( |
||||||
126 | api_get_user_id(), |
||||||
127 | $courseInfo['code'] |
||||||
128 | ); |
||||||
129 | |||||||
130 | $filesToZip = []; |
||||||
131 | |||||||
132 | // Admins are allowed to download invisible files |
||||||
133 | if (api_is_allowed_to_edit()) { |
||||||
134 | // Set the path that will be used in the query |
||||||
135 | if ($path == '/') { |
||||||
136 | $querypath = ''; // To prevent ...path LIKE '//%'... in query |
||||||
137 | } else { |
||||||
138 | $querypath = $path; |
||||||
139 | } |
||||||
140 | $querypath = Database::escape_string($querypath); |
||||||
141 | |||||||
142 | // Search for all files that are not deleted => visibility != 2 |
||||||
143 | $sql = "SELECT |
||||||
144 | path, |
||||||
145 | docs.session_id, |
||||||
146 | docs.id, |
||||||
147 | props.to_group_id, |
||||||
148 | docs.c_id |
||||||
149 | FROM $doc_table AS docs |
||||||
150 | INNER JOIN $prop_table AS props |
||||||
151 | ON |
||||||
152 | docs.id = props.ref AND |
||||||
153 | docs.c_id = props.c_id |
||||||
154 | $groupJoin |
||||||
155 | WHERE |
||||||
156 | props.tool ='".TOOL_DOCUMENT."' AND |
||||||
157 | docs.path LIKE '".$querypath."/%' AND |
||||||
158 | docs.filetype = 'file' AND |
||||||
159 | props.visibility <> '2' AND |
||||||
160 | $groupCondition AND |
||||||
161 | (props.session_id IN ('0', '$sessionId') OR props.session_id IS NULL) AND |
||||||
162 | docs.c_id = ".$courseId." "; |
||||||
163 | |||||||
164 | $sql .= DocumentManager::getSessionFolderFilters($querypath, $sessionId); |
||||||
165 | |||||||
166 | $result = Database::query($sql); |
||||||
167 | $files = []; |
||||||
168 | while ($row = Database::fetch_array($result)) { |
||||||
169 | $files[$row['path']] = $row; |
||||||
170 | } |
||||||
171 | |||||||
172 | Session::write('doc_files_to_download', $files); |
||||||
173 | |||||||
174 | foreach ($files as $not_deleted_file) { |
||||||
175 | // Filtering folders and |
||||||
176 | if (strpos($not_deleted_file['path'], 'chat_files') > 0 || |
||||||
177 | strpos($not_deleted_file['path'], 'shared_folder') > 0 |
||||||
178 | ) { |
||||||
179 | if (!empty($sessionId)) { |
||||||
180 | if ($not_deleted_file['session_id'] != $sessionId) { |
||||||
181 | continue; |
||||||
182 | } |
||||||
183 | } |
||||||
184 | } |
||||||
185 | $filesToZip[] = $sysCoursePath.$courseInfo['path'].'/document'.$not_deleted_file['path']; |
||||||
186 | } |
||||||
187 | $zip->add( |
||||||
188 | $filesToZip, |
||||||
189 | PCLZIP_OPT_REMOVE_PATH, |
||||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
190 | $sysCoursePath.$courseInfo['path'].'/document'.$remove_dir, |
||||||
191 | PCLZIP_CB_PRE_ADD, |
||||||
0 ignored issues
–
show
|
|||||||
192 | 'fixDocumentNameCallback' |
||||||
193 | ); |
||||||
194 | |||||||
195 | Session::erase('doc_files_to_download'); |
||||||
196 | } else { |
||||||
197 | // For other users, we need to create a zip file with only visible files and folders |
||||||
198 | if ($path == '/') { |
||||||
199 | $querypath = ''; // To prevent ...path LIKE '//%'... in query |
||||||
200 | } else { |
||||||
201 | $querypath = $path; |
||||||
202 | } |
||||||
203 | |||||||
204 | /* A big problem: Visible files that are in a hidden folder are |
||||||
205 | included when we do a query for visibility='v' |
||||||
206 | So... I do it in a couple of steps: |
||||||
207 | 1st: Get all files that are visible in the given path |
||||||
208 | */ |
||||||
209 | $querypath = Database::escape_string($querypath); |
||||||
210 | $sql = "SELECT path, docs.session_id, docs.id, props.to_group_id, docs.c_id |
||||||
211 | FROM $doc_table AS docs |
||||||
212 | INNER JOIN $prop_table AS props |
||||||
213 | ON |
||||||
214 | docs.id = props.ref AND |
||||||
215 | docs.c_id = props.c_id |
||||||
216 | $groupJoin |
||||||
217 | WHERE |
||||||
218 | docs.c_id = $courseId AND |
||||||
219 | props.tool = '".TOOL_DOCUMENT."' AND |
||||||
220 | docs.path LIKE '".$querypath."/%' AND |
||||||
221 | docs.filetype = 'file' AND |
||||||
222 | (props.session_id IN ('0', '$sessionId') OR props.session_id IS NULL) AND |
||||||
223 | $groupCondition |
||||||
224 | "; |
||||||
225 | |||||||
226 | $sql .= DocumentManager::getSessionFolderFilters($querypath, $sessionId); |
||||||
227 | $result = Database::query($sql); |
||||||
228 | |||||||
229 | $files = []; |
||||||
230 | $all_visible_files_path = []; |
||||||
231 | // Add them to an array |
||||||
232 | while ($all_visible_files = Database::fetch_assoc($result)) { |
||||||
233 | if (strpos($all_visible_files['path'], 'chat_files') > 0 || |
||||||
234 | strpos($all_visible_files['path'], 'shared_folder') > 0 |
||||||
235 | ) { |
||||||
236 | if (!empty($sessionId)) { |
||||||
237 | if ($all_visible_files['session_id'] != $sessionId) { |
||||||
238 | continue; |
||||||
239 | } |
||||||
240 | } |
||||||
241 | } |
||||||
242 | |||||||
243 | $isVisible = DocumentManager::is_visible_by_id( |
||||||
244 | $all_visible_files['id'], |
||||||
245 | $courseInfo, |
||||||
246 | api_get_session_id(), |
||||||
247 | api_get_user_id(), |
||||||
248 | false, |
||||||
249 | $userIsSubscribed |
||||||
250 | ); |
||||||
251 | |||||||
252 | if (!$isVisible) { |
||||||
253 | continue; |
||||||
254 | } |
||||||
255 | |||||||
256 | $all_visible_files_path[] = $all_visible_files['path']; |
||||||
257 | $files[$all_visible_files['path']] = $all_visible_files; |
||||||
258 | } |
||||||
259 | |||||||
260 | // 2nd: Get all folders that are invisible in the given path |
||||||
261 | $sql = "SELECT path, docs.session_id, docs.id, props.to_group_id, docs.c_id |
||||||
262 | FROM $doc_table AS docs |
||||||
263 | INNER JOIN $prop_table AS props |
||||||
264 | ON |
||||||
265 | docs.id = props.ref AND |
||||||
266 | docs.c_id = props.c_id |
||||||
267 | WHERE |
||||||
268 | docs.c_id = $courseId AND |
||||||
269 | props.tool = '".TOOL_DOCUMENT."' AND |
||||||
270 | docs.path LIKE '".$querypath."/%' AND |
||||||
271 | props.visibility <> '1' AND |
||||||
272 | (props.session_id IN ('0', '$sessionId') OR props.session_id IS NULL) AND |
||||||
273 | docs.filetype = 'folder'"; |
||||||
274 | $query2 = Database::query($sql); |
||||||
275 | |||||||
276 | // If we get invisible folders, we have to filter out these results from all visible files we found |
||||||
277 | if (Database::num_rows($query2) > 0) { |
||||||
278 | //$files = []; |
||||||
279 | // Add item to an array |
||||||
280 | while ($invisible_folders = Database::fetch_assoc($query2)) { |
||||||
281 | //3rd: Get all files that are in the found invisible folder (these are "invisible" too) |
||||||
282 | $sql = "SELECT path, docs.id, props.to_group_id, docs.c_id |
||||||
283 | FROM $doc_table AS docs |
||||||
284 | INNER JOIN $prop_table AS props |
||||||
285 | ON |
||||||
286 | docs.id = props.ref AND |
||||||
287 | docs.c_id = props.c_id |
||||||
288 | WHERE |
||||||
289 | docs.c_id = $courseId AND |
||||||
290 | props.tool ='".TOOL_DOCUMENT."' AND |
||||||
291 | docs.path LIKE '".$invisible_folders['path']."/%' AND |
||||||
292 | docs.filetype = 'file' AND |
||||||
293 | (props.session_id IN ('0', '$sessionId') OR props.session_id IS NULL) |
||||||
294 | "; |
||||||
295 | $query3 = Database::query($sql); |
||||||
296 | // Add tem to an array |
||||||
297 | while ($files_in_invisible_folder = Database::fetch_assoc($query3)) { |
||||||
298 | $isVisible = DocumentManager::is_visible_by_id( |
||||||
299 | $files_in_invisible_folder['id'], |
||||||
300 | $courseInfo, |
||||||
301 | api_get_session_id(), |
||||||
302 | api_get_user_id(), |
||||||
303 | false, |
||||||
304 | $userIsSubscribed |
||||||
305 | ); |
||||||
306 | |||||||
307 | if (!$isVisible) { |
||||||
308 | continue; |
||||||
309 | } |
||||||
310 | $files_in_invisible_folder_path[] = $files_in_invisible_folder['path']; |
||||||
311 | $files[$files_in_invisible_folder['path']] = $files_in_invisible_folder; |
||||||
312 | } |
||||||
313 | } |
||||||
314 | |||||||
315 | // Compare the array with visible files and the array with files in invisible folders |
||||||
316 | // and keep the difference (= all visible files that are not in an invisible folder) |
||||||
317 | $files_for_zipfile = diff( |
||||||
318 | (array) $all_visible_files_path, |
||||||
319 | (array) $files_in_invisible_folder_path |
||||||
320 | ); |
||||||
321 | } else { |
||||||
322 | // No invisible folders found, so all visible files can be added to the zipfile |
||||||
323 | $files_for_zipfile = $all_visible_files_path; |
||||||
324 | } |
||||||
325 | |||||||
326 | Session::write('doc_files_to_download', $files); |
||||||
327 | |||||||
328 | // Add all files in our final array to the zipfile |
||||||
329 | for ($i = 0; $i < count($files_for_zipfile); $i++) { |
||||||
330 | $filesToZip[] = $sysCoursePath.$courseInfo['path'].'/document'.$files_for_zipfile[$i]; |
||||||
331 | } |
||||||
332 | $zip->add( |
||||||
333 | $filesToZip, |
||||||
334 | PCLZIP_OPT_REMOVE_PATH, |
||||||
335 | $sysCoursePath.$courseInfo['path'].'/document'.$remove_dir, |
||||||
336 | PCLZIP_CB_PRE_ADD, |
||||||
337 | 'fixDocumentNameCallback' |
||||||
338 | ); |
||||||
339 | Session::erase('doc_files_to_download'); |
||||||
340 | } |
||||||
341 | |||||||
342 | // Launch event |
||||||
343 | Event::event_download( |
||||||
0 ignored issues
–
show
The method
event_download() does not exist on Event .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
344 | ($path == '/') ? 'documents.zip (folder)' : basename($path).'.zip (folder)' |
||||||
345 | ); |
||||||
346 | |||||||
347 | // Start download of created file |
||||||
348 | $name = ($path == '/') ? 'documents.zip' : $documentInfo['title'].'.zip'; |
||||||
349 | |||||||
350 | if (Security::check_abs_path($tempZipFile, api_get_path(SYS_ARCHIVE_PATH))) { |
||||||
351 | $result = DocumentManager::file_send_for_download($tempZipFile, true, $name); |
||||||
352 | if ($result === false) { |
||||||
353 | api_not_allowed(true); |
||||||
354 | } |
||||||
355 | @unlink($tempZipFile); |
||||||
0 ignored issues
–
show
It seems like you do not handle an error condition for
unlink() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||||
356 | exit; |
||||||
357 | } else { |
||||||
358 | api_not_allowed(true); |
||||||
359 | } |
||||||
360 | |||||||
361 | /** |
||||||
362 | * Returns the difference between two arrays, as an array of those key/values |
||||||
363 | * Use this as array_diff doesn't give the. |
||||||
364 | * |
||||||
365 | * @param array $arr1 first array |
||||||
366 | * @param array $arr2 second array |
||||||
367 | * |
||||||
368 | * @return array difference between the two arrays |
||||||
369 | */ |
||||||
370 | function diff($arr1, $arr2) |
||||||
371 | { |
||||||
372 | $res = []; |
||||||
373 | $r = 0; |
||||||
374 | foreach ($arr1 as &$av) { |
||||||
375 | if (!in_array($av, $arr2)) { |
||||||
376 | $res[$r] = $av; |
||||||
377 | $r++; |
||||||
378 | } |
||||||
379 | } |
||||||
380 | |||||||
381 | return $res; |
||||||
382 | } |
||||||
383 |