1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17
18/**
19 * Base for all file browsing classes.
20 *
21 * @package    core_files
22 * @copyright  2008 Petr Skoda (http://skodak.org)
23 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28/**
29 * Base class for things in the tree navigated by {@link file_browser}.
30 *
31 * @package    core_files
32 * @copyright  2008 Petr Skoda (http://skodak.org)
33 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34 */
35abstract class file_info {
36
37    /** @var stdClass File context */
38    protected $context;
39
40    /** @var file_browser File browser instance */
41    protected $browser;
42
43    /**
44     * Constructor
45     *
46     * @param file_browser $browser file_browser instance
47     * @param stdClass $context
48     */
49    public function __construct($browser, $context) {
50        $this->browser = $browser;
51        $this->context = $context;
52    }
53
54    /**
55     * Returns list of standard virtual file/directory identification.
56     * The difference from stored_file parameters is that null values
57     * are allowed in all fields
58     *
59     * @return array with keys contextid, component, filearea, itemid, filepath and filename
60     */
61    public function get_params() {
62        return array('contextid' => $this->context->id,
63                     'component' => null,
64                     'filearea'  => null,
65                     'itemid'    => null,
66                     'filepath'  => null,
67                     'filename'  => null);
68    }
69
70    /**
71     * Returns localised visible name.
72     *
73     * @return string
74     */
75    public abstract function get_visible_name();
76
77    /**
78     * Whether or not this is a directory
79     *
80     * @return bool
81     */
82    public abstract function is_directory();
83
84    /**
85     * Returns list of children.
86     *
87     * @return array of file_info instances
88     */
89    public abstract function get_children();
90
91    /**
92     * Builds SQL sub query (WHERE clause) for selecting files with the specified extensions
93     *
94     * If $extensions == '*' (any file), the result is array('', array())
95     * otherwise the result is something like array('AND filename ...', array(...))
96     *
97     * @param string|array $extensions - either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
98     * @param string $prefix prefix for DB table files in the query (empty by default)
99     * @return array of two elements: $sql - sql where clause and $params - array of parameters
100     */
101    protected function build_search_files_sql($extensions, $prefix = null) {
102        global $DB;
103        if (strlen($prefix)) {
104            $prefix = $prefix.'.';
105        } else {
106            $prefix = '';
107        }
108        $sql = '';
109        $params = array();
110        if (is_array($extensions) && !in_array('*', $extensions)) {
111            $likes = array();
112            $cnt = 0;
113            foreach ($extensions as $ext) {
114                $cnt++;
115                $likes[] = $DB->sql_like($prefix.'filename', ':filename'.$cnt, false);
116                $params['filename'.$cnt] = '%'.$ext;
117            }
118            $sql .= ' AND (' . join(' OR ', $likes) . ')';
119        }
120        return array($sql, $params);
121     }
122
123    /**
124     * Returns list of children which are either files matching the specified extensions
125     * or folders that contain at least one such file.
126     *
127     * It is recommended to overwrite this function so it uses a proper SQL
128     * query and does not create unnecessary file_info objects (might require a lot of time
129     * and memory usage on big sites).
130     *
131     * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
132     * @return array of file_info instances
133     */
134    public function get_non_empty_children($extensions = '*') {
135        $list = $this->get_children();
136        $nonemptylist = array();
137        foreach ($list as $fileinfo) {
138            if ($fileinfo->is_directory()) {
139                if ($fileinfo->count_non_empty_children($extensions)) {
140                    $nonemptylist[] = $fileinfo;
141                }
142            } else if ($extensions === '*') {
143                $nonemptylist[] = $fileinfo;
144            } else {
145                $filename = $fileinfo->get_visible_name();
146                $extension = core_text::strtolower(pathinfo($filename, PATHINFO_EXTENSION));
147                if (!empty($extension) && in_array('.' . $extension, $extensions)) {
148                    $nonemptylist[] = $fileinfo;
149                }
150            }
151        }
152        return $nonemptylist;
153    }
154
155    /**
156     * Returns the number of children which are either files matching the specified extensions
157     * or folders containing at least one such file.
158     *
159     * We usually don't need the exact number of non empty children if it is >=2 (see param $limit)
160     * This function is used by repository_local to evaluate if the folder is empty. But
161     * it also can be used to check if folder has only one subfolder because in some cases
162     * this subfolder can be skipped.
163     *
164     * It is strongly recommended to overwrite this function so it uses a proper SQL
165     * query and does not create file_info objects (later might require a lot of time
166     * and memory usage on big sites).
167     *
168     * @param string|array $extensions, for example '*' or array('.gif','.jpg')
169     * @param int $limit stop counting after at least $limit non-empty children are found
170     * @return int
171     */
172    public function count_non_empty_children($extensions = '*', $limit = 1) {
173        $list = $this->get_children();
174        $cnt = 0;
175        // first loop through files
176        foreach ($list as $fileinfo) {
177            if (!$fileinfo->is_directory()) {
178                if ($extensions !== '*') {
179                    $filename = $fileinfo->get_visible_name();
180                    $extension = core_text::strtolower(pathinfo($filename, PATHINFO_EXTENSION));
181                    if (empty($extension) || !in_array('.' . $extension, $extensions)) {
182                        continue;
183                    }
184                }
185                if ((++$cnt) >= $limit) {
186                    return $cnt;
187                }
188            }
189        }
190        // now loop through directories
191        foreach ($list as $fileinfo) {
192            if ($fileinfo->is_directory() && $fileinfo->count_non_empty_children($extensions)) {
193                if ((++$cnt) >= $limit) {
194                    return $cnt;
195                }
196            }
197        }
198        return $cnt;
199    }
200
201    /**
202     * Returns parent file_info instance
203     *
204     * @return file_info or null for root
205     */
206    public abstract function get_parent();
207
208    /**
209     * Returns array of url encoded params.
210     *
211     * @return array with numeric keys
212     */
213    public function get_params_rawencoded() {
214        $params = $this->get_params();
215        $encoded = array();
216        $encoded[] = 'contextid=' . $params['contextid'];
217        $encoded[] = 'component=' . $params['component'];
218        $encoded[] = 'filearea=' . $params['filearea'];
219        $encoded[] = 'itemid=' . (is_null($params['itemid']) ? -1 : $params['itemid']);
220        $encoded[] = 'filepath=' . (is_null($params['filepath']) ? '' : rawurlencode($params['filepath']));
221        $encoded[] = 'filename=' . ((is_null($params['filename']) or $params['filename'] === '.') ? '' : rawurlencode($params['filename']));
222
223        return $encoded;
224    }
225
226    /**
227     * Returns file download url
228     *
229     * @param bool $forcedownload whether or not force download
230     * @param bool $https whether or not force https
231     * @return string url
232     */
233    public function get_url($forcedownload=false, $https=false) {
234        return null;
235    }
236
237    /**
238     * Whether or not I can read content of this file or enter directory
239     *
240     * @return bool
241     */
242    public function is_readable() {
243        return true;
244    }
245
246    /**
247     * Whether or not new files or directories can be added
248     *
249     * @return bool
250     */
251    public function is_writable() {
252        return true;
253    }
254
255    /**
256     * Is this info area and is it "empty"? Are there any files in subfolders?
257     *
258     * This is used mostly in repositories to reduce the
259     * number of empty folders. This method may be very slow,
260     * use with care.
261     *
262     * @return bool
263     */
264    public function is_empty_area() {
265        return false;
266    }
267
268    /**
269     * Returns file size in bytes, null for directories
270     *
271     * @return int bytes or null if not known
272     */
273    public function get_filesize() {
274        return null;
275    }
276
277    /**
278     * Returns mimetype
279     *
280     * @return string mimetype or null if not known
281     */
282    public function get_mimetype() {
283        return null;
284    }
285
286    /**
287     * Returns time created unix timestamp if known
288     *
289     * @return int timestamp or null
290     */
291    public function get_timecreated() {
292        return null;
293    }
294
295    /**
296     * Returns time modified unix timestamp if known
297     *
298     * @return int timestamp or null
299     */
300    public function get_timemodified() {
301        return null;
302    }
303
304    /**
305     * Returns the license type of the file
306     * @return string license short name or null
307     */
308    public function get_license() {
309        return null;
310    }
311
312    /**
313     * Returns the author name of the file
314     *
315     * @return string author name or null
316     */
317    public function get_author() {
318        return null;
319    }
320
321    /**
322     * Returns the source of the file
323     *
324     * @return string a source url or null
325     */
326    public function get_source() {
327        return null;
328    }
329
330    /**
331     * Returns the sort order of the file
332     *
333     * @return int
334     */
335    public function get_sortorder() {
336        return 0;
337    }
338
339    /**
340     * Whether or not this is a external resource
341     *
342     * @return bool
343     */
344    public function is_external_file() {
345        return false;
346    }
347
348    /**
349     * Returns file status flag.
350     *
351     * @return int 0 means file OK, anything else is a problem and file can not be used
352     */
353    public function get_status() {
354        return 0;
355    }
356
357    /**
358     * Returns the localised human-readable name of the file together with virtual path
359     *
360     * @see file_info_stored::get_readable_fullname()
361     * @return string
362     */
363    public function get_readable_fullname() {
364        return null;
365    }
366
367    /**
368     * Create new directory, may throw exception - make sure
369     * params are valid.
370     *
371     * @param string $newdirname name of new directory
372     * @param int $userid id of author, default $USER->id
373     * @return file_info new directory
374     */
375    public function create_directory($newdirname, $userid = NULL) {
376        return null;
377    }
378
379    /**
380     * Create new file from string - make sure
381     * params are valid.
382     *
383     * @param string $newfilename name of new file
384     * @param string $content of file
385     * @param int $userid id of author, default $USER->id
386     * @return file_info new file
387     */
388    public function create_file_from_string($newfilename, $content, $userid = NULL) {
389        return null;
390    }
391
392    /**
393     * Create new file from pathname - make sure
394     * params are valid.
395     *
396     * @param string $newfilename name of new file
397     * @param string $pathname location of file
398     * @param int $userid id of author, default $USER->id
399     * @return file_info new file
400     */
401    public function create_file_from_pathname($newfilename, $pathname, $userid = NULL) {
402        return null;
403    }
404
405    /**
406     * Create new file from stored file - make sure
407     * params are valid.
408     *
409     * @param string $newfilename name of new file
410     * @param int|stored_file $fid id or stored_file of file
411     * @param int $userid id of author, default $USER->id
412     * @return file_info new file
413     */
414    public function create_file_from_storedfile($newfilename, $fid, $userid = NULL) {
415        return null;
416    }
417
418    /**
419     * Delete file, make sure file is deletable first.
420     *
421     * @return bool success
422     */
423    public function delete() {
424        return false;
425    }
426
427    /**
428     * Copy content of this file to local storage, overriding current file if needed.
429     *
430     * @param array|stdClass $filerecord contains contextid, component, filearea,
431     *    itemid, filepath, filename and optionally other attributes of the new file
432     * @return bool success
433     */
434    public function copy_to_storage($filerecord) {
435        return false;
436    }
437
438    /**
439     * Copy content of this file to local storage, overriding current file if needed.
440     *
441     * @todo MDL-31068 implement move() rename() unzip() zip()
442     * @param string $pathname real local full file name
443     * @return boolean success
444     */
445    public function copy_to_pathname($pathname) {
446        return false;
447    }
448
449
450//TODO: following methods are not implemented yet ;-)
451    //public abstract function move(location params);
452    //public abstract function rename(new name);
453    //public abstract function unzip(location params);
454    //public abstract function zip(zip file, file info);
455}
456