made a copy
[atutor.git] / include / lib / file_storage.inc.php
1 <?php
2 /************************************************************************/
3 /* ATutor                                                                                                                               */
4 /************************************************************************/
5 /* Copyright (c) 2002-2008 by Greg Gay, Joel Kronenberg & Heidi Hazelton*/
6 /* Adaptive Technology Resource Centre / University of Toronto                  */
7 /* http://atutor.ca                                                                                                             */
8 /*                                                                                                                                              */
9 /* This program is free software. You can redistribute it and/or                */
10 /* modify it under the terms of the GNU General Public License                  */
11 /* as published by the Free Software Foundation.                                                */
12 /************************************************************************/
13 // $Id$
14
15 /**
16  * Additional constants are found in /include/lib/constants.inc.php (for the work spaces)
17  *
18  * The File Storage was designed to allow for unlimited workspace, although only four right now.
19  *
20  * All the functions are namespaced with fs_ (for File Storage not File System).
21  *
22  * These two variables are used throughout.
23  * $owner_type is used to define the workspace type. Also appears as $ot in _GET.
24  * $owner_id is the ID of the particular workspace type. Also appears as $oid in _GET.
25  * 
26  **/
27
28 if (!defined('AT_INCLUDE_PATH')) { exit; }
29
30 define('WORKSPACE_AUTH_NONE',  0);
31 define('WORKSPACE_AUTH_READ',  1);
32 define('WORKSPACE_AUTH_WRITE', 2); 
33 define('WORKSPACE_AUTH_RW',    3); // to save time
34
35 /**
36  * given an owner_type and owner_id
37  * returns false if user cannot read or write to this workspace
38  * returns WORKSPACE_AUTH_READ if the user can read
39  * returns WORKSPACE_AUTH_WRITE if the user can write
40  */
41 function fs_authenticate($owner_type, $owner_id) {
42         if (($owner_type == WORKSPACE_PERSONAL) && $_SESSION['member_id'] && $_SESSION['enroll'] && ($owner_id == $_SESSION['member_id'])) {
43
44                 return WORKSPACE_AUTH_RW;
45
46         } else if ($owner_type == WORKSPACE_ASSIGNMENT) {
47                 if (authenticate(AT_PRIV_ASSIGNMENTS, AT_PRIV_RETURN))
48                 { 
49                         // instructors have read only access to assignments
50                         return WORKSPACE_AUTH_READ;
51                 }
52                 else
53                 { 
54                         // students have read access to their own assignments
55                         global $db;
56                         $sql = "SELECT COUNT(*) cnt FROM ".TABLE_PREFIX."files
57                                  WHERE owner_id =".$owner_id."
58                            AND owner_type= ".WORKSPACE_ASSIGNMENT."
59                            AND member_id = ".$_SESSION['member_id'];
60                         $result = mysql_query($sql, $db);
61                         $row = mysql_fetch_assoc($result);
62                         
63                         if ($row['cnt'] > 0) RETURN WORKSPACE_AUTH_READ;
64                 }
65         
66         } else if ($owner_type == WORKSPACE_GROUP) {
67                 if (isset($_SESSION['groups'][$owner_id])) {
68                         global $db;
69                         $sql = "SELECT * FROM ".TABLE_PREFIX."file_storage_groups WHERE group_id=$owner_id";
70                         $result = mysql_query($sql, $db);
71                         if (mysql_fetch_assoc($result)) {
72                                 return WORKSPACE_AUTH_RW;
73                         }
74                 }
75
76         } else if ($owner_type == WORKSPACE_COURSE) {
77                 if (($owner_id == $_SESSION['course_id']) && authenticate(AT_PRIV_FILE_STORAGE, AT_PRIV_RETURN)) {
78                         return WORKSPACE_AUTH_RW;
79                 } else if ($owner_id == $_SESSION['course_id']) {
80                         return WORKSPACE_AUTH_READ;
81                 }
82         }
83         /* else if ($owner_type == WORKSPACE_SYSTEM) {
84                 if (admin_authenticate(AT_ADMIN_PRIV_FILE_STORAGE, TRUE)) {
85                         return WORKSPACE_AUTH_RW;
86                 } // else
87                 return WORKSPACE_AUTH_READ; // everyone can read the System File Space
88         } */
89
90         return WORKSPACE_AUTH_NONE;
91 }
92
93 /**
94  * returns the localised name of the specified workspace
95  */
96 function fs_get_workspace($owner_type, $owner_id) {
97         if ($owner_type == WORKSPACE_PERSONAL) {
98                 return _AT('my_files');
99
100         } else if ($owner_type == WORKSPACE_COURSE) {
101                 return _AT('course_files');
102
103         } else if ($owner_type == WORKSPACE_GROUP) {
104                 global $db;
105                 $sql = "SELECT title FROM ".TABLE_PREFIX."groups WHERE group_id=$owner_id";
106                 $result = mysql_query($sql, $db);
107                 $row    = mysql_fetch_assoc($result);
108                 return $row['title'];
109
110         } else if ($owner_type == WORKSPACE_ASSIGNMENT) {
111                 $row    = fs_get_assignment($owner_id);
112                 return ($row ? $row['title'] : false);
113         } /*
114                 else if ($owner_type == WORKSPACE_SYSTEM) {
115                 return _AT('system_files');
116      }
117         */
118 }
119
120 /**
121  * returns the assignment row specified by $assignment_id
122  * false if not found.
123  */
124 function fs_get_assignment($assignment_id) {
125         global $db;
126         $sql = "SELECT assignment_id, title, assign_to, date_due, date_cutoff, UNIX_TIMESTAMP(date_cutoff) AS u_date_cutoff, multi_submit FROM ".TABLE_PREFIX."assignments WHERE assignment_id=$assignment_id AND course_id=$_SESSION[course_id]";
127         $result = mysql_query($sql, $db);
128         $row    = mysql_fetch_assoc($result);
129         return $row;
130 }
131
132 /**
133  * retrieve folder(s) specified by $folder_id
134  * $folder_id the ID of the single folder, or array of IDs
135  * if $folder_id is an array then returns an array of folder rows
136  * if $folder_id is an int then returns the single row array
137  *
138  * This function does not authenticate the $folder_id for the assignment.
139  *
140  * Note: This function checks if the $owner_type is an Assignment.
141  *
142  */
143 function fs_get_folder_by_id($folder_id, $owner_type, $owner_id) {
144         global $db;
145
146         $rows = array();
147
148         if ($owner_type == WORKSPACE_ASSIGNMENT) {
149                 // get the folder row from the assignments table
150
151                 $sql = "SELECT assign_to FROM ".TABLE_PREFIX."assignments WHERE assignment_id=$owner_id AND course_id=$_SESSION[course_id]";
152                 $result = mysql_query($sql, $db);
153                 $row  = mysql_fetch_assoc($result);
154                 if ($row['assign_to']) {
155                         $sql = "SELECT title FROM ".TABLE_PREFIX."groups WHERE group_id=$folder_id";
156                         $result = mysql_query($sql, $db);
157                         $row = mysql_fetch_assoc($result);
158
159                         $rows = array('title' => $row['title'], 'folder_id' => $folder_id);
160                 } else {
161                         $rows = array('title' => get_display_name($folder_id), 'folder_id' => $folder_id);
162                 }
163         } else {
164                 if (is_array($folder_id)) {
165                         $folder_id_list = implode(',', $folder_id);
166
167                         $sql = "SELECT folder_id, title, parent_folder_id FROM ".TABLE_PREFIX."folders WHERE folder_id IN ($folder_id_list) AND owner_type=$owner_type AND owner_id=$owner_id ORDER BY title";
168                         $result = mysql_query($sql, $db);
169                         while ($row = mysql_fetch_assoc($result)) {
170                                 $rows[] = $row;
171                         }
172
173                 } else {
174                         $sql = "SELECT folder_id, title, parent_folder_id FROM ".TABLE_PREFIX."folders WHERE folder_id=$folder_id AND owner_type=$owner_type AND owner_id=$owner_id";
175                         $result = mysql_query($sql, $db);
176                         $rows = mysql_fetch_assoc($result);
177                 }
178         }
179         return $rows;
180 }
181
182 /**
183  * retrieve folder(s) specified by $parent_folder_id
184  *
185  * Note: This function checks if the $owner_type is an Assignment.
186  *
187  */
188 function fs_get_folder_by_pid($parent_folder_id, $owner_type, $owner_id) {
189         global $db;
190
191         $rows = array();
192         if ($owner_type == WORKSPACE_ASSIGNMENT) {
193                 // get the folder row from the assignments table
194                 // does not currently support sub-folders for assignments
195                 if ($parent_folder_id == 0 && authenticate(AT_PRIV_ASSIGNMENTS, AT_PRIV_RETURN)) {
196                         $sql = "SELECT assign_to FROM ".TABLE_PREFIX."assignments WHERE assignment_id=$owner_id AND course_id=$_SESSION[course_id]";
197                         $result = mysql_query($sql, $db);
198                         $row  = mysql_fetch_assoc($result);
199                         if ($row['assign_to']) {
200                                 $sql = "SELECT G.group_id AS folder_id, G.title FROM ".TABLE_PREFIX."groups G INNER JOIN ".TABLE_PREFIX."file_storage_groups FS USING (group_id) WHERE G.type_id=$row[assign_to] ORDER BY G.title";
201                         } else {
202                                 global $system_courses;
203
204                                 $sql = "SELECT E.member_id AS folder_id, M.login AS title FROM ".TABLE_PREFIX."course_enrollment E INNER JOIN ".TABLE_PREFIX."members M USING (member_id) WHERE E.course_id=$_SESSION[course_id] AND E.approved='y' AND E.privileges & ".AT_PRIV_GROUPS." = 0 AND E.member_id<>{$system_courses[$_SESSION[course_id]][member_id]} ORDER BY M.login";
205                         }
206                         $result = mysql_query($sql, $db);
207
208                         while ($row = mysql_fetch_assoc($result)) {
209                                 $rows[] = $row;
210                         }
211                 }
212         } else {
213                 $sql = "SELECT folder_id, title FROM ".TABLE_PREFIX."folders WHERE parent_folder_id=$parent_folder_id AND owner_type=$owner_type AND owner_id=$owner_id ORDER BY title";
214                 $result = mysql_query($sql, $db);
215                 while ($row = mysql_fetch_assoc($result)) {
216                         $rows[] = $row; 
217                 }
218         }
219         return $rows;
220 }
221
222 /**
223  * outputs the folders as a  list.
224  *
225  * $current_folder_id the current folder id, used for pre-selecting the radio button
226  * $parent_folder_id the folder id to display children of
227  * $folders the array of folders returned from get_folders()
228  * $disable whether or not the radio button is available
229  */
230 function fs_print_folders($current_folder_id, $parent_folder_id, &$folders, $disable = FALSE) {
231         if (!isset($folders[$parent_folder_id])) {
232                 return;
233         }
234
235         echo '<ul>';
236         foreach ($folders[$parent_folder_id] as $folder_id => $folder_info) {
237                 echo '<li class="folders">';
238                 
239                 echo '<input type="radio" name="new_folder" value="'.$folder_id.'" id="f'.$folder_id.'"';
240                 if ($_GET['folders'] && in_array($folder_id, $_GET['folders'])) {
241                         $disable = TRUE;
242                 }
243                 if ($folder_id == $current_folder_id) {
244                         echo ' checked="checked"';
245                 }
246                 if ($disable) {
247                         echo ' disabled="disabled"';
248                 }
249                 echo '/><label for="f'.$folder_id.'">'.htmlspecialchars($folder_info['title']);
250                 if ($folder_id == $current_folder_id) {
251                         echo ' '._AT('current_location');
252                 }
253                 echo '</label>';
254                 
255                 fs_print_folders($current_folder_id, $folder_id, $folders, $disable);
256                 if ($_GET['folders'] && in_array($folder_id, $_GET['folders'])) {
257                         $disable = FALSE;
258                 }
259                 echo '</li>';
260         }
261         echo '</ul>';
262 }
263
264 /**
265  * returns an array of all the revisions for the given file_id
266  *
267  * $file_id ID of a file in a revision sequence. can be any revision, does not have to be the latest.
268  * This function is recursive and uses fs_get_revisions_down_recursive() and fs_get_revisions_recurisve() below.
269  */
270 function fs_get_revisions($file_id, $owner_type, $owner_id) {
271         global $db;
272
273         $sql = "SELECT * FROM ".TABLE_PREFIX."files WHERE file_id=$file_id AND owner_type=$owner_type AND owner_id=$owner_id";
274         $result = mysql_query($sql, $db);
275         if ($row = mysql_fetch_assoc($result)) {
276                 return array_merge(array_reverse(fs_get_revisions_down_recursive($row['parent_file_id'])), array($row), fs_get_revisions_recursive($file_id));
277         }
278         return array();
279 }
280
281 /**
282  * recursively retrieves all the revisions of the file.
283  * recurses DOWN the revisions path.
284  * PRIVATE! use fs_get_revisions() above.
285  */
286 function fs_get_revisions_down_recursive($file_id) {
287         global $db;
288
289         if ($file_id == 0) {
290                 return array();
291         }
292
293         $sql = "SELECT * FROM ".TABLE_PREFIX."files WHERE file_id=$file_id";
294         $result = mysql_query($sql, $db);
295         $row = mysql_fetch_assoc($result);
296
297         if (!$row) {
298                 return array();
299         } else if (!$row['parent_file_id']) {
300                 return array($row);
301         }
302
303         return array_merge(array($row), fs_get_revisions_down_recursive($row['parent_file_id']));
304 }
305
306 /**
307  * recursively retrieves all the revisions of the file.
308  * recurses UP the revisions path.
309  * PRIVATE! use fs_get_revisions() above.
310  */
311 function fs_get_revisions_recursive($file_id) {
312         global $db;
313
314         if ($file_id == 0) {
315                 return array();
316         }
317
318         $sql = "SELECT * FROM ".TABLE_PREFIX."files WHERE parent_file_id=$file_id";
319         $result = mysql_query($sql, $db);
320         $row = mysql_fetch_assoc($result);
321
322         if (!$row) {
323                 return array();
324         }
325
326         return array_merge(array($row), fs_get_revisions_recursive($row['file_id']));
327 }
328
329 /**
330  * returns the full path based on $file_id with trailing slash.
331  *
332  * Ex. if file_id is 2345 and WORKSPACE_PATH_DEPTH is set to 3 then
333  * the path returned will be WORKSPACE_FILE_PATH.'5/4/3/'
334  *
335  * If the path does not exist within the WORKSPACE_FILE_PATH then attempts
336  * to create it.
337  */
338 function fs_get_file_path($file_id) {
339         $end_part = substr($file_id, -WORKSPACE_PATH_DEPTH);
340         $path = WORKSPACE_FILE_PATH;
341         $dirs = max(-WORKSPACE_PATH_DEPTH, -strlen($file_id));
342         $id_threshold = pow(10,WORKSPACE_PATH_DEPTH); // only check for the dir before reaching this value.
343     for ($i = -1; $i >= $dirs; $i--) {
344                 $path .= substr($file_id, $i, 1) . DIRECTORY_SEPARATOR;
345                 if ($file_id <= $id_threshold) {
346                         if (!is_dir($path)) {
347                                 @mkdir($path);
348                         }
349                 }
350         }
351
352         return $path;
353 }
354
355 /**
356  * delete a given file, its revisions, and comments.
357  *
358  * $file_id the ID of the file to delete. can be any ID within a revision sequence.
359  */
360 function fs_delete_file($file_id, $owner_type, $owner_id) {
361         global $db;
362         $revisions = fs_get_revisions($file_id, $owner_type, $owner_id);
363         foreach ($revisions as $file) {
364                 $sql = "DELETE FROM ".TABLE_PREFIX."files WHERE file_id=$file[file_id] AND owner_type=$owner_type AND owner_id=$owner_id";
365                 mysql_query($sql, $db);
366
367                 if (mysql_affected_rows($db) == 1) {
368                         $sql = "DELETE FROM ".TABLE_PREFIX."files_comments WHERE file_id=$file[file_id]";
369                         mysql_query($sql, $db);
370
371                         $path = fs_get_file_path($file['file_id']);
372                         if (file_exists($path . $file['file_id'])) {
373                                 @unlink($path . $file['file_id']);
374                         }
375                 }
376         }
377 }
378
379 /**
380  * returns only the extension part of the specified file name
381  *
382  * $file_name the full name of the file.
383  */
384 function fs_get_file_extension($file_name) {
385         $ext = pathinfo($file_name);
386         return $ext['extension'];
387 }
388
389 /**
390  * returns the image name (w/o the ".gif" ending) of the icon to use
391  * for the given file name.
392  * if no icon is specified (by mime.inc.php) then returns "generic"
393  */
394 function fs_get_file_type_icon($file_name) {
395         global $mime;
396         if (!isset($mime)) {
397                 require(AT_INCLUDE_PATH.'lib/mime.inc.php');
398         }
399         $ext = fs_get_file_extension($file_name);
400
401         if (isset($mime[$ext]) && $mime[$ext][1]) {
402                 return $mime[$ext][1];
403         }
404         return 'generic';
405 }
406
407 /**
408  * deletes the folder, its sub-folders and associated files.
409  *
410  * $folder_id the ID of the folder to delete, recursively, with content.
411  */
412 function fs_delete_folder($folder_id, $owner_type, $owner_id) {
413         if (!$folder_id) { return; }
414
415         global $db;
416
417         $rows = fs_get_folder_by_pid($folder_id, $owner_type, $owner_id);
418         foreach ($rows as $row) {
419                 fs_delete_folder($row['folder_id'], $owner_type, $owner_id);
420         }
421
422         $sql = "DELETE FROM ".TABLE_PREFIX."folders WHERE folder_id=$folder_id AND owner_type=$owner_type AND owner_id=$owner_id";
423         mysql_query($sql, $db);
424
425         // delete this file's folders (we only select the latest versions because
426         // the delete_file() function takes care of the revisions for us
427         $sql = "SELECT file_id FROM ".TABLE_PREFIX."files WHERE folder_id=$folder_id AND parent_file_id=0 AND owner_type=$owner_type AND owner_id=$owner_id";
428         $result = mysql_query($sql, $db);
429         while ($row = mysql_fetch_assoc($result)) {
430                 fs_delete_file($row['file_id'], $owner_type, $owner_id);
431         }
432 }
433
434 /**
435  * archives a folder into a specified zip handler.
436  *
437  * $folder_id the ID of the folder to archive recursively, with content.
438  * $zipfile reference to the zipFile object.
439  * $path the absolute path to the current folder.
440  */
441 function fs_download_folder($folder_id, &$zipfile, $owner_type, $owner_id, $path = '') {
442         global $db;
443
444         $parent_row = fs_get_folder_by_id($folder_id, $owner_type, $owner_id);
445         //$sql = "SELECT title FROM ".TABLE_PREFIX."folders WHERE folder_id=$folder_id AND owner_type=$owner_type AND owner_id=$owner_id";
446         //$result = mysql_query($sql, $db);
447         if ($parent_row) {
448                 $zipfile->create_dir($path . $parent_row['title']);
449         }
450
451         $sql = "SELECT file_id, file_name, UNIX_TIMESTAMP(date) AS date FROM ".TABLE_PREFIX."files WHERE folder_id=$folder_id AND parent_file_id=0 AND owner_type=$owner_type AND owner_id=$owner_id";
452         $result = mysql_query($sql, $db);
453         while ($row = mysql_fetch_assoc($result)) {
454                 $file_path = fs_get_file_path($row['file_id']) . $row['file_id'];
455
456                 $zipfile->add_file(file_get_contents($file_path), $path . $parent_row['title'] .'/' . $row['file_name'], $row['date']);
457         }
458
459         $rows = fs_get_folder_by_pid($folder_id, $owner_type, $owner_id);
460         foreach ($rows as $row) {
461                 fs_download_folder($row['folder_id'], $zipfile, $owner_type, $owner_id, $path . $parent_row['title'] . '/');
462         }
463 }
464
465 /**
466  * returns the full path to the current folder
467  *
468  * $folder_id the current folder
469  * $workspace the owner_type of this folder
470  * $owner_id the ID of the owner.
471  */
472 function fs_get_folder_path($folder_id, $owner_type, $owner_id) {
473         $folder_path = fs_get_folder_path_recursive($folder_id, $owner_type, $owner_id);
474
475         return array_reverse($folder_path);
476 }
477
478 /**
479  * recursively return the path to the current folder
480  * PRIVATE! do not call directly, use get_folder_path() above.
481  */
482 function fs_get_folder_path_recursive($folder_id, $owner_type, $owner_id) {
483         global $db;
484
485         if ($folder_id == 0) {
486                 return array();
487         }
488
489         $row = fs_get_folder_by_id($folder_id, $owner_type, $owner_id);
490
491         return array_merge(array($row), fs_get_folder_path_recursive($row['parent_folder_id'], $owner_type, $owner_id));
492 }
493
494 /**
495  * deletes all the files, folders, comments, revisions, etc.. in the specified workspace.
496  */
497 function fs_delete_workspace($owner_type, $owner_id) {
498         global $db;
499
500         $sql = "SELECT folder_id, owner_type, owner_id FROM ".TABLE_PREFIX."folders WHERE owner_type=$owner_type AND owner_id=$owner_id AND parent_folder_id=0";
501         $result = mysql_query($sql, $db);
502         while ($row = mysql_fetch_assoc($result)) {
503                 fs_delete_folder($row['folder_id'], $row['owner_type'], $row['owner_id']);
504         }
505
506         $sql = "SELECT file_id, owner_type, owner_id FROM ".TABLE_PREFIX."files WHERE owner_type=$owner_type AND owner_id=$owner_id";
507         $result = mysql_query($sql, $db);
508         while ($row = mysql_fetch_assoc($result)) {
509                 fs_delete_file($row['file_id'], $row['owner_type'], $row['owner_id']);
510         }
511 }
512
513 /**
514  * copies a file to another workspace.
515  * currently only used for submitting assignments.
516  **/
517 function fs_copy_file($file_id, $src_owner_type, $src_owner_id, $dest_owner_type, $dest_owner_id, $dest_folder_id) {
518         global $db;
519
520         $sql = "SELECT file_name, file_size, description FROM ".TABLE_PREFIX."files WHERE file_id=$file_id AND owner_type=$src_owner_type AND owner_id=$src_owner_id";
521         $result = mysql_query($sql, $db);
522         if (!$row = mysql_fetch_assoc($result)) {
523                 return false;
524         }
525         $sql = "INSERT INTO ".TABLE_PREFIX."files VALUES (NULL, $dest_owner_type, $dest_owner_id, $_SESSION[member_id], $dest_folder_id, 0, NOW(), 0, 0, '$row[file_name]', '$row[file_size]', '$row[description]')";
526         $result = mysql_query($sql, $db);
527
528         $id = mysql_insert_id($db);
529
530         $src_file  = fs_get_file_path($file_id) . $file_id;
531         $dest_file = fs_get_file_path($id) . $id;
532         copy($src_file, $dest_file);
533 }
534
535 /**
536  * used with usort() to sort the revisions array returned from fs_get_revisions()
537  * $col is a valid array key to sort by
538  * $order is either 'asc' or 'desc'
539  */
540 function fs_revisions_sort_compare($a, $b) {
541         global $col, $order;
542
543         if ($order == 'asc') {
544                 return strcasecmp($a[$col], $b[$col]);
545         }
546         return strcasecmp($b[$col], $a[$col]);
547 }
548
549 /**
550  * copies a directory to another workspace.
551  * not currently used anywhere.
552  */
553 /***
554 function fs_copy_folder($folder_id, $src_owner_type, $src_owner_id, $dest_owner_type, $dest_owner_id, $dest_parent_folder_id) {
555         global $db;
556
557         $folder = fs_get_folder_by_id($folder_id, $src_owner_type, $src_owner_id);
558         if (!$folder) {
559                 return false;
560         }
561
562         $sql = "INSERT INTO ".TABLE_PREFIX."folders VALUES (0, $dest_parent_folder_id, $dest_owner_type, $dest_owner_id, '$folder[title]')";
563         $result = mysql_query($sql, $db);
564         $id = mysql_insert_id($db);
565
566         $sql = "SELECT file_id FROM ".TABLE_PREFIX."files WHERE folder_id=$folder_id AND owner_type=$src_owner_type AND owner_id=$src_owner_id";
567         $result = mysql_query($sql, $db);
568         while ($row = mysql_fetch_assoc($result)) {
569                 fs_copy_file($row['file_id'], $src_owner_type, $src_owner_id, $dest_owner_type, $dest_owner_id, $id);
570         }
571
572         $folders = fs_get_folder_by_pid($folder_id, $src_owner_type, $src_owner_id);
573         foreach ($folders as $folder) {
574                 fs_copy_folder($folder['folder_id'], $src_owner_type, $src_owner_id, $dest_owner_type, $dest_owner_id, $id);
575         }
576 }
577 */
578 ?>