1<?php
2
3/* Copyright (c) 1998-2012 ILIAS open source, Extended GPL, see docs/LICENSE */
4
5include_once("./Services/Object/classes/class.ilObjectAccess.php");
6require_once('./Services/WebAccessChecker/interfaces/interface.ilWACCheckingClass.php');
7
8/**
9 * Access class for file objects.
10 *
11 * @author  Alex Killing <alex.killing@gmx.de>
12 * @author  Stefan Born <stefan.born@phzh.ch>
13 * @version $Id$
14 *
15 * @ingroup ModulesFile
16 */
17class ilObjFileAccess extends ilObjectAccess implements ilWACCheckingClass
18{
19
20    /**
21     * @param $obj_id
22     *
23     * @return bool
24     */
25    protected function checkAccessToObjectId($obj_id)
26    {
27        global $DIC;
28        $ilAccess = $DIC['ilAccess'];
29        /**
30         * @var $ilAccess ilAccessHandler
31         */
32        foreach (ilObject::_getAllReferences($obj_id) as $ref_id) {
33            if ($ilAccess->checkAccess('read', '', $ref_id)) {
34                return true;
35            }
36        }
37
38        return false;
39    }
40
41
42    /**
43     * @param \ilWACPath $ilWACPath
44     *
45     * @return bool
46     */
47    public function canBeDelivered(ilWACPath $ilWACPath)
48    {
49        switch ($ilWACPath->getSecurePathId()) {
50            case 'previews':
51                $re = '/\/previews\/[\d\/]{0,}\/preview_([\d]{0,})\//uU';
52                break;
53        }
54        preg_match($re, $ilWACPath->getPath(), $matches);
55
56        return $this->checkAccessToObjectId($matches[1]);
57    }
58
59
60
61    // BEGIN WebDAV cache inline file extensions
62
63
64    /**
65     * Contains an array of extensions separated by space.
66     * Since this array is needed for every file object displayed on a
67     * repository page, we only create it once, and cache it here.
68     *
69     * @see function _isFileInline
70     */
71    protected static $_inlineFileExtensionsArray;
72    // END WebDAV cache inline file extensions
73
74    protected static $preload_list_gui_data; // [array]
75
76
77    /**
78     * get commands
79     *
80     * this method returns an array of all possible commands/permission combinations
81     *
82     * example:
83     * $commands = array
84     *    (
85     *        array("permission" => "read", "cmd" => "view", "lang_var" => "show"),
86     *        array("permission" => "write", "cmd" => "edit", "lang_var" => "edit"),
87     *    );
88     */
89    public static function _getCommands()
90    {
91        $commands = array();
92        $commands[] = array(
93            "permission" => "read",
94            "cmd" => "sendfile",
95            "lang_var" => "download",
96            "default" => true,
97        );
98        $commands[] = array(
99            "permission" => "write",
100            "cmd" => "versions",
101            "lang_var" => "versions",
102        );
103        $commands[] = array(
104            "permission" => "write",
105            "cmd" => "edit",
106            "lang_var" => "settings",
107        );
108
109        return $commands;
110    }
111
112
113    /**
114     * check whether goto script will succeed
115     */
116    public static function _checkGoto($a_target)
117    {
118        global $DIC;
119        $ilAccess = $DIC['ilAccess'];
120
121        $t_arr = explode("_", $a_target);
122
123        // personal workspace context: do not force normal login
124        if (isset($t_arr[2]) && $t_arr[2] == "wsp") {
125            include_once "Services/PersonalWorkspace/classes/class.ilSharedResourceGUI.php";
126
127            return ilSharedResourceGUI::hasAccess($t_arr[1]);
128        }
129
130        if ($t_arr[0] != "file" || ((int) $t_arr[1]) <= 0) {
131            return false;
132        }
133
134        if ($ilAccess->checkAccess("visible", "", $t_arr[1])
135            || $ilAccess->checkAccess("read", "", $t_arr[1])
136        ) {
137            return true;
138        }
139
140        return false;
141    }
142
143
144    /**
145     * looks up the file_data for the file object with the specified object id
146     * as an associative array.
147     */
148    public static function _lookupFileData($a_id)
149    {
150        global $DIC;
151        $ilDB = $DIC['ilDB'];
152
153        $q = "SELECT * FROM file_data WHERE file_id = " . $ilDB->quote($a_id, 'integer');
154        $r = $ilDB->query($q);
155        $row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC);
156
157        return $row;
158    }
159
160
161    /**
162     * lookup version
163     */
164    public static function _lookupVersion($a_id)
165    {
166        global $DIC;
167        $ilDB = $DIC['ilDB'];
168
169        $q = "SELECT version FROM file_data WHERE file_id = " . $ilDB->quote($a_id, 'integer');
170        $r = $ilDB->query($q);
171        $row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT);
172
173        $striped = ilUtil::stripSlashes($row->version);
174
175        return $striped > 0 ? $striped : 1;
176    }
177
178
179    /**
180     * Quickly looks up the file size from the database and returns the
181     * number of bytes.
182     */
183    public static function _lookupFileSize($a_id)
184    {
185        global $DIC;
186        $ilDB = $DIC['ilDB'];
187
188        $q = "SELECT file_size FROM file_data WHERE file_id = " . $ilDB->quote($a_id, 'integer');
189        $r = $ilDB->query($q);
190        $row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT);
191
192        $size = $row->file_size;
193
194        return $size;
195    }
196
197
198    /**
199     * Looks up the file size by retrieving it from the filesystem.
200     * This function runs much slower than _lookupFileSize()! Use this
201     * function only, to update the data in the database. For example, if
202     * the file size in the database has become inconsistent for some reason.
203     */
204    public static function _lookupFileSizeFromFilesystem($a_id)
205    {
206        global $DIC;
207        $ilDB = $DIC['ilDB'];
208
209        $q = "SELECT * FROM file_data WHERE file_id = " . $ilDB->quote($a_id, 'integer');
210        $r = $ilDB->query($q);
211        $row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT);
212
213        require_once('Modules/File/classes/class.ilFSStorageFile.php');
214        $fss = new ilFSStorageFile($a_id);
215        $file = $fss->getAbsolutePath() . '/' . $row->file_name;
216
217        if (@!is_file($file)) {
218            $version_subdir = "/" . sprintf("%03d", ilObjFileAccess::_lookupVersion($a_id));
219            $file = $fss->getAbsolutePath() . '/' . $version_subdir . '/' . $row->file_name;
220        }
221
222        if (is_file($file)) {
223            $size = filesize($file);
224        } else {
225            $size = 0;
226        }
227
228        return $size;
229    }
230
231
232    /**
233     * lookup suffix
234     */
235    public static function _lookupSuffix($a_id)
236    {
237        include_once('Modules/File/classes/class.ilFSStorageFile.php');
238
239        global $DIC;
240        $ilDB = $DIC['ilDB'];
241
242        // BEGIN WebDAV: Filename suffix is determined by file title
243        $q = "SELECT * FROM object_data WHERE obj_id = " . $ilDB->quote($a_id, 'integer');
244        $r = $ilDB->query($q);
245        $row = $r->fetchRow(ilDBConstants::FETCHMODE_OBJECT);
246        require_once 'Modules/File/classes/class.ilObjFile.php';
247
248        return self::_getFileExtension($row->title);
249        // END WebDAV: Filename suffix is determined by file title
250    }
251
252
253    /**
254     * Returns the number of bytes used on the harddisk by the file object
255     * with the specified object id.
256     *
257     * @param int object id of a file object.
258     */
259    public static function _lookupDiskUsage($a_id)
260    {
261        include_once('Modules/File/classes/class.ilFSStorageFile.php');
262        $fileStorage = new ilFSStorageFile($a_id);
263        $dir = $fileStorage->getAbsolutePath();
264
265        return ilUtil::dirsize($dir);
266    }
267
268    // BEGIN WebDAV: Get file extension, determine if file is inline, guess file type.
269
270
271    /**
272     * Returns true, if the specified file shall be displayed inline in the browser.
273     */
274    public static function _isFileInline($a_file_name)
275    {
276        if (self::$_inlineFileExtensionsArray
277            === null
278        ) {        // the === makes a huge difference, if the array is empty
279            require_once 'Services/Administration/classes/class.ilSetting.php';
280            $settings = new ilSetting('file_access');
281            self::$_inlineFileExtensionsArray = preg_split('/ /', $settings->get('inline_file_extensions'), -1, PREG_SPLIT_NO_EMPTY);
282        }
283        $extension = self::_getFileExtension($a_file_name);
284
285        return in_array($extension, self::$_inlineFileExtensionsArray);
286    }
287
288
289    /**
290     * Gets the file extension of the specified file name.
291     * The file name extension is converted to lower case before it is returned.
292     *
293     * For example, for the file name "HELLO.MP3", this function returns "mp3".
294     *
295     * A file name extension can have multiple parts. For the file name
296     * "hello.tar.gz", this function returns "gz".
297     *
298     *
299     * @param string $a_file_name The file name
300     */
301    public static function _getFileExtension($a_file_name)
302    {
303        if (preg_match('/\.([a-z0-9]+)\z/i', $a_file_name, $matches) == 1) {
304            return strtolower($matches[1]);
305        } else {
306            return '';
307        }
308    }
309
310
311    /**
312     * Returns true, if a file with the specified name, is usually hidden from
313     * the user.
314     *
315     * - Filenames starting with '.' are hidden Unix files
316     * - Filenames ending with '~' are temporary Unix files
317     * - Filenames starting with '~$' are temporary Windows files
318     * - The file "Thumbs.db" is a hidden Windows file
319     */
320    public static function _isFileHidden($a_file_name)
321    {
322        return substr($a_file_name, 0, 1) == '.' || substr($a_file_name, -1, 1) == '~'
323            || substr($a_file_name, 0, 2) == '~$'
324            || $a_file_name == 'Thumbs.db';
325    }
326    // END WebDAV: Get file extension, determine if file is inline, guess file type.
327
328
329    /**
330     * Appends the text " - Copy" to a filename in the language of
331     * the current user.
332     *
333     * If the provided $nth_copy parameter is greater than 1, then
334     * is appended in round brackets. If $nth_copy parameter is null, then
335     * the function determines the copy number on its own.
336     *
337     * If this function detects, that the filename already ends with " - Copy",
338     * or with "- Copy ($nth_copy), it only appends the number of the copy to
339     * the filename.
340     *
341     * This function retains the extension of the filename.
342     *
343     * Examples:
344     * - Calling ilObjFileAccess::_appendCopyToTitle('Hello.txt', 1)
345     *   returns: "Hello - Copy.txt".
346     *
347     * - Calling ilObjFileAccess::_appendCopyToTitle('Hello.txt', 2)
348     *   returns: "Hello - Copy (2).txt".
349     *
350     * - Calling ilObjFileAccess::_appendCopyToTitle('Hello - Copy (3).txt', 2)
351     *   returns: "Hello - Copy (2).txt".
352     *
353     * - Calling ilObjFileAccess::_appendCopyToTitle('Hello - Copy (3).txt', null)
354     *   returns: "Hello - Copy (4).txt".
355     */
356    public static function _appendNumberOfCopyToFilename($a_file_name, $nth_copy = null, $a_handle_extension = false)
357    {
358        global $DIC;
359        $lng = $DIC['lng'];
360
361        $filenameWithoutExtension = $a_file_name;
362
363        $extension = null;
364        if ($a_handle_extension) {
365            // Get the extension and the filename without the extension
366            $extension = ilObjFileAccess::_getFileExtension($a_file_name);
367            if (strlen($extension) > 0) {
368                $extension = '.' . $extension;
369                $filenameWithoutExtension = substr($a_file_name, 0, -strlen($extension));
370            }
371        }
372
373        // create a regular expression from the language text copy_n_of_suffix, so that
374        // we can match it against $filenameWithoutExtension, and retrieve the number of the copy.
375        // for example, if copy_n_of_suffix is 'Copy (%1s)', this creates the regular
376        // expression '/ Copy \\([0-9]+)\\)$/'.
377        $nthCopyRegex = preg_replace('/([\^$.\[\]|()?*+{}])/', '\\\\${1}', ' '
378            . $lng->txt('copy_n_of_suffix'));
379        $nthCopyRegex = '/' . preg_replace('/%1\\\\\$s/', '([0-9]+)', $nthCopyRegex) . '$/';
380
381        // Get the filename without any previously added number of copy.
382        // Determine the number of copy, if it has not been specified.
383        if (preg_match($nthCopyRegex, $filenameWithoutExtension, $matches)) {
384            // this is going to be at least the third copy of the filename
385            $filenameWithoutCopy = substr($filenameWithoutExtension, 0, -strlen($matches[0]));
386            if ($nth_copy == null) {
387                $nth_copy = $matches[1] + 1;
388            }
389        } else {
390            if (substr($filenameWithoutExtension, -strlen(' ' . $lng->txt('copy_of_suffix')))
391                == ' ' . $lng->txt('copy_of_suffix')
392            ) {
393                // this is going to be the second copy of the filename
394                $filenameWithoutCopy = substr($filenameWithoutExtension, 0, -strlen(' '
395                    . $lng->txt('copy_of_suffix')));
396                if ($nth_copy == null) {
397                    $nth_copy = 2;
398                }
399            } else {
400                // this is going to be the first copy of the filename
401                $filenameWithoutCopy = $filenameWithoutExtension;
402                if ($nth_copy == null) {
403                    $nth_copy = 1;
404                }
405            }
406        }
407
408        // Construct the new filename
409        if ($nth_copy > 1) {
410            // this is at least the second copy of the filename, append " - Copy ($nth_copy)"
411            $newFilename = $filenameWithoutCopy . sprintf(' '
412                    . $lng->txt('copy_n_of_suffix'), $nth_copy)
413                . $extension;
414        } else {
415            // this is the first copy of the filename, append " - Copy"
416            $newFilename = $filenameWithoutCopy . ' ' . $lng->txt('copy_of_suffix') . $extension;
417        }
418
419        return $newFilename;
420    }
421
422
423    /**
424     * Gets the permanent download link for the file.
425     *
426     * @param int $ref_id
427     *
428     * @return string
429     */
430    public static function _getPermanentDownloadLink($ref_id)
431    {
432        return ilLink::_getStaticLink($ref_id, "file", true, "_download");
433    }
434
435
436    /**
437     * @param array $a_obj_ids
438     * @param int[] $a_ref_ids
439     */
440    public static function _preloadData($a_obj_ids, $a_ref_ids)
441    {
442        global $DIC;
443
444        self::$preload_list_gui_data = array();
445
446        $set = $DIC->database()->query("SELECT obj_id,max(hdate) latest" . " FROM history"
447            . " WHERE obj_type = " . $DIC->database()->quote("file", "text") . " AND "
448            . $DIC->database()->in("obj_id", $a_obj_ids, "", "integer") . " GROUP BY obj_id");
449        while ($row = $DIC->database()->fetchAssoc($set)) {
450            self::$preload_list_gui_data[$row["obj_id"]]["date"] = $row["latest"];
451        }
452
453        $set = $DIC->database()->query("SELECT file_size, version, file_id, page_count" . " FROM file_data" . " WHERE "
454            . $DIC->database()->in("file_id", $a_obj_ids, "", "integer"));
455        while ($row = $DIC->database()->fetchAssoc($set)) {
456            self::$preload_list_gui_data[$row["file_id"]]["size"] = $row["file_size"];
457            self::$preload_list_gui_data[$row["file_id"]]["version"] = $row["version"];
458            self::$preload_list_gui_data[$row["file_id"]]["page_count"] = $row["page_count"];
459        }
460    }
461
462
463    /**
464     * @param $a_obj_id
465     *
466     * @return array
467     */
468    public static function getListGUIData($a_obj_id)
469    {
470        if (isset(self::$preload_list_gui_data[$a_obj_id])) {
471            return self::$preload_list_gui_data[$a_obj_id];
472        }
473    }
474}
475