weekly release 2.4dev
[moodle.git] / repository / local / lib.php
CommitLineData
6a391ebf 1<?php
aedb0396
DC
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/>.
6a391ebf 16
96297ca2 17/**
67233725 18 * This plugin is used to access local files
96297ca2 19 *
67233725 20 * @since 2.0
e2652f1a 21 * @package repository_local
67233725 22 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
d078f6d3 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
96297ca2 24 */
67233725 25require_once($CFG->dirroot . '/repository/lib.php');
aedb0396 26
67233725
DC
27/**
28 * repository_local class is used to browse moodle files
29 *
30 * @since 2.0
31 * @package repository_local
32 * @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34 */
be236ef5 35class repository_local extends repository {
96297ca2 36 /**
acb70a9b 37 * local plugin doesn't require login, so list all files
cd312233 38 * @return mixed
96297ca2 39 */
5f9087f3 40 public function print_login() {
6a391ebf 41 return $this->get_listing();
42 }
5fd3e8f7 43
96297ca2 44 /**
aedb0396 45 * Get file listing
96297ca2 46 *
cd312233 47 * @param string $encodedpath
cd312233 48 * @return mixed
96297ca2 49 */
58eb9b9f 50 public function get_listing($encodedpath = '', $page = '') {
390baf46 51 global $CFG, $USER, $OUTPUT;
cd312233 52 $ret = array();
53 $ret['dynload'] = true;
d1bfc05e 54 $ret['nosearch'] = true;
03896eb7 55 $ret['nologin'] = true;
cd312233 56 $list = array();
21942470 57
a6058fed 58 if (!empty($encodedpath)) {
59 $params = unserialize(base64_decode($encodedpath));
60 if (is_array($params)) {
aff24313
PS
61 $component = is_null($params['component']) ? NULL : clean_param($params['component'], PARAM_COMPONENT);
62 $filearea = is_null($params['filearea']) ? NULL : clean_param($params['filearea'], PARAM_AREA);
3136dc16
PS
63 $itemid = is_null($params['itemid']) ? NULL : clean_param($params['itemid'], PARAM_INT);
64 $filepath = is_null($params['filepath']) ? NULL : clean_param($params['filepath'], PARAM_PATH);;
65 $filename = is_null($params['filename']) ? NULL : clean_param($params['filename'], PARAM_FILE);
d197ea43 66 $context = context::instance_by_id(clean_param($params['contextid'], PARAM_INT));
a6058fed 67 }
68 } else {
69 $itemid = null;
70 $filename = null;
71 $filearea = null;
72 $filepath = null;
16f61c70 73 $component = null;
be85f7ab
DC
74 if (!empty($this->context)) {
75 list($context, $course, $cm) = get_context_info_array($this->context->id);
e2652f1a 76 if (is_object($course)) {
21c08c63 77 $context = context_course::instance($course->id);
e2652f1a
MG
78 } else {
79 $context = get_system_context();
80 }
be85f7ab
DC
81 } else {
82 $context = get_system_context();
83 }
a6058fed 84 }
85
955b6e09
DC
86 $browser = get_file_browser();
87
e2652f1a 88 $list = array();
16f61c70 89 if ($fileinfo = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename)) {
955b6e09 90 // build file tree
e2652f1a
MG
91 $element = repository_local_file::retrieve_file_info($fileinfo, $this);
92 $nonemptychildren = $element->get_non_empty_children();
93 foreach ($nonemptychildren as $child) {
94 $list[] = (array)$child->get_node();
cd312233 95 }
eed6f2e7
DC
96 } else {
97 // if file doesn't exist, build path nodes root of current context
eed6f2e7 98 $fileinfo = $browser->get_file_info($context, null, null, null, null, null);
e2652f1a
MG
99 }
100 // build path navigation
101 $ret['path'] = array();
102 $element = repository_local_file::retrieve_file_info($fileinfo, $this);
103 for ($level = $element; $level; $level = $level->get_parent()) {
104 if ($level == $element || !$level->can_skip()) {
105 array_unshift($ret['path'], $level->get_node_path());
eed6f2e7 106 }
6a391ebf 107 }
87628a67 108 $ret['list'] = array_filter($list, array($this, 'filter'));
cd312233 109 return $ret;
6a391ebf 110 }
dbc01944 111
aedb0396
DC
112 /**
113 * Local file don't support to link to external links
114 *
2f67a9b3 115 * @return int
aedb0396 116 */
41076c58 117 public function supported_returntypes() {
be920f9a 118 return FILE_INTERNAL | FILE_REFERENCE;
41076c58 119 }
c7e4621e 120
acb70a9b 121 /**
f392caba 122 * Does this repository used to browse moodle files?
acb70a9b 123 *
f392caba 124 * @return boolean
acb70a9b 125 */
f392caba
DC
126 public function has_moodle_files() {
127 return true;
f892da72 128 }
be920f9a
MG
129
130 /**
131 * Return reference file life time
132 *
133 * @param string $ref
134 * @return int
135 */
136 public function get_reference_file_lifetime($ref) {
137 // this should be realtime
138 return 0;
139 }
acb70a9b 140}
e2652f1a
MG
141
142/**
143 * Class to cache some information about file
144 *
145 * This class is a wrapper to instances of file_info. It caches such information as
146 * parent and list of children. It also stores an array of already retrieved elements.
147 *
148 * It also implements more comprehensive algorithm for checking if folder is empty
149 * (taking into account the filtering of the files). To decrease number of levels
150 * we check if some subfolders can be skipped from the tree.
151 *
152 * As a result we display in Server files repository only non-empty folders and skip
153 * filearea folders if this is the only filearea in the module.
154 * For non-admin the course categories are not shown as well (courses are shown as a list)
155 *
156 * @package repository_local
157 * @copyright 2012 Marina Glancy
158 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
159 */
160class repository_local_file {
161 /** @var array stores already retrieved files */
162 private static $cachedfiles = array();
163 /** @var file_info Stores the original file */
164 public $fileinfo;
165 /** @var bool whether this file is directory */
166 private $isdir;
167 /** @var array caches retrieved children */
168 private $children = null;
169 /** @var array caches retrieved information whether this file is an empty directory */
170 protected $isempty = null;
171 /** @var repository link to the container repository (for filtering the results) */
172 private $repository;
173 /** @var repository_local_file link to parent directory */
174 protected $parent;
175 /** @var bool caches calculated information on whether this directory must be skipped in the tree */
176 private $skip = null;
177
178 /**
179 * Creates (or retrieves from cache) the repository_local_file object for $file_info
180 *
181 * @param file_info $fileinfo
182 * @param repository $repository
183 * @param repository_local_file $parent
184 * @return repository_local_file
185 */
186 public static function retrieve_file_info(file_info $fileinfo, repository $repository, repository_local_file $parent = null) {
187 $encodedpath = base64_encode(serialize($fileinfo->get_params()));
188 if (!isset(self::$cachedfiles[$encodedpath])) {
189 self::$cachedfiles[$encodedpath] = new repository_local_file($fileinfo, $repository, $parent);
190 }
191 return self::$cachedfiles[$encodedpath];
192 }
193
194 /**
195 * Creates an object
196 *
197 * @param file_info $fileinfo
198 * @param repository $repository
199 * @param repository_local_file $parent
200 */
201 private function __construct(file_info $fileinfo, repository $repository, repository_local_file $parent = null) {
202 $this->repository = $repository;
203 $this->fileinfo = $fileinfo;
204 $this->isdir = $fileinfo->is_directory();
205 if (!$this->isdir) {
206 $node = array('title' => $this->fileinfo->get_visible_name());
207 $this->isempty = !$repository->filter($node);
208 $this->skip = false;
209 }
210 }
211
212 /**
213 * Returns node for $ret['list']
214 *
215 * @return array
216 */
217 public function get_node() {
218 global $OUTPUT;
219 $encodedpath = base64_encode(serialize($this->fileinfo->get_params()));
220 $node = array(
221 'title' => $this->fileinfo->get_visible_name(),
5bdf63cc
MG
222 'datemodified' => $this->fileinfo->get_timemodified(),
223 'datecreated' => $this->fileinfo->get_timecreated()
224 );
e2652f1a
MG
225 if ($this->isdir) {
226 $node['path'] = $encodedpath;
559276b1 227 $node['thumbnail'] = $OUTPUT->pix_url(file_folder_icon(90))->out(false);
e2652f1a
MG
228 $node['children'] = array();
229 } else {
5bdf63cc
MG
230 $node['size'] = $this->fileinfo->get_filesize();
231 $node['author'] = $this->fileinfo->get_author();
232 $node['license'] = $this->fileinfo->get_license();
1778f310
MG
233 $node['isref'] = $this->fileinfo->is_external_file();
234 if ($this->fileinfo->get_status() == 666) {
235 $node['originalmissing'] = true;
236 }
e2652f1a 237 $node['source'] = $encodedpath;
559276b1 238 $node['thumbnail'] = $OUTPUT->pix_url(file_file_icon($this->fileinfo, 90))->out(false);
9213f547 239 $node['icon'] = $OUTPUT->pix_url(file_file_icon($this->fileinfo, 24))->out(false);
dfad252c
DM
240 if ($imageinfo = $this->fileinfo->get_imageinfo()) {
241 // what a beautiful picture, isn't it
242 $fileurl = new moodle_url($this->fileinfo->get_url());
3333e7e2
DM
243 $node['realthumbnail'] = $fileurl->out(false, array('preview' => 'thumb', 'oid' => $this->fileinfo->get_timemodified()));
244 $node['realicon'] = $fileurl->out(false, array('preview' => 'tinyicon', 'oid' => $this->fileinfo->get_timemodified()));
dfad252c
DM
245 $node['image_width'] = $imageinfo['width'];
246 $node['image_height'] = $imageinfo['height'];
247 }
e2652f1a
MG
248 }
249 return $node;
250 }
251
252 /**
253 * Returns node for $ret['path']
254 *
255 * @return array
256 */
257 public function get_node_path() {
258 $encodedpath = base64_encode(serialize($this->fileinfo->get_params()));
259 return array(
260 'path' => $encodedpath,
261 'name' => $this->fileinfo->get_visible_name()
262 );
263 }
264
265 /**
266 * Checks if this is a directory
267 *
268 * @return bool
269 */
270 public function is_dir() {
271 return $this->isdir;
272 }
273
274 /**
275 * Returns children of this element
276 *
277 * @return array
278 */
279 public function get_children() {
280 if (!$this->isdir) {
281 return array();
282 }
283 if ($this->children === null) {
284 $this->children = array();
285 $children = $this->fileinfo->get_children();
286 for ($i=0; $i<count($children); $i++) {
287 $this->children[] = self::retrieve_file_info($children[$i], $this->repository, $this);
288 }
289 }
290 return $this->children;
291 }
292
293 /**
294 * Checks if this folder is empty (contains no non-empty children)
295 *
296 * @return bool
297 */
298 public function is_empty() {
299 if ($this->isempty === null) {
300 $this->isempty = true;
301 if (!$this->fileinfo->is_empty_area()) {
302 // even if is_empty_area() returns false, element still may be empty
303 $children = $this->get_children();
304 if (!empty($children)) {
305 // 1. Let's look at already retrieved children
306 foreach ($children as $childnode) {
307 if ($childnode->isempty === false) {
308 // we already calculated isempty for a child, and it is not empty
309 $this->isempty = false;
310 break;
311 }
312 }
313 if ($this->isempty) {
314 // 2. now we know that this directory contains children that are either empty or we don't know
315 foreach ($children as $childnode) {
316 if (!$childnode->is_empty()) {
317 $this->isempty = false;
318 break;
319 }
320 }
321 }
322 }
323 }
324 }
325 return $this->isempty;
326 }
327
328 /**
329 * Returns the parent element
330 *
331 * @return repository_local_file
332 */
333 public function get_parent() {
334 if ($this->parent === null) {
335 if ($parent = $this->fileinfo->get_parent()) {
336 $this->parent = self::retrieve_file_info($parent, $this->repository);
337 } else {
338 $this->parent = false;
339 }
340 }
341 return $this->parent;
342 }
343
344 /**
345 * Wether this folder may be skipped in tree view
346 *
347 * @return bool
348 */
349 public function can_skip() {
350 global $CFG;
351 if ($this->skip === null) {
352 $this->skip = false;
353 if ($this->fileinfo instanceof file_info_stored) {
354 $params = $this->fileinfo->get_params();
355 if (strlen($params['filearea']) && $params['filepath'] == '/' && $params['filename'] == '.') {
356 // This is a filearea inside an activity, it can be skipped if it has no non-empty siblings
357 if ($parent = $this->get_parent()) {
358 $siblings = $parent->get_children();
359 $countnonempty = 0;
360 foreach ($siblings as $sibling) {
361 if (!$sibling->is_empty()) {
362 $countnonempty++;
363 if ($countnonempty > 1) {
364 break;
365 }
366 }
367 }
368 if ($countnonempty <= 1) {
369 $this->skip = true;
370 }
371 }
372 }
373 } else if ($this->fileinfo instanceof file_info_context_coursecat) {
374 // This is a course category. For non-admins we do not display categories
375 $this->skip = empty($CFG->navshowmycoursecategories) &&
21c08c63 376 !has_capability('moodle/course:update', context_system::instance());
e2652f1a
MG
377 }
378 }
379 return $this->skip;
380 }
381
382 /**
383 * Returns array of children who have any elmenets
384 *
385 * If a subfolder can be skipped - list children of subfolder instead
386 * (recursive function)
387 *
388 * @return array
389 */
390 public function get_non_empty_children() {
391 $children = $this->get_children();
392 $nonemptychildren = array();
393 foreach ($children as $child) {
394 if (!$child->is_empty()) {
395 if ($child->can_skip()) {
396 $nonemptychildren = array_merge($nonemptychildren, $child->get_non_empty_children());
397 } else {
398 $nonemptychildren[] = $child;
399 }
400 }
401 }
402 return $nonemptychildren;
403 }
727dac2f 404}