on-demand release 2.3dev
[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);
66 $context = get_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
MG
76 if (is_object($course)) {
77 $context = get_context_instance(CONTEXT_COURSE, $course->id);
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
DC
117 public function supported_returntypes() {
118 return FILE_INTERNAL;
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 }
acb70a9b 129}
e2652f1a
MG
130
131/**
132 * Class to cache some information about file
133 *
134 * This class is a wrapper to instances of file_info. It caches such information as
135 * parent and list of children. It also stores an array of already retrieved elements.
136 *
137 * It also implements more comprehensive algorithm for checking if folder is empty
138 * (taking into account the filtering of the files). To decrease number of levels
139 * we check if some subfolders can be skipped from the tree.
140 *
141 * As a result we display in Server files repository only non-empty folders and skip
142 * filearea folders if this is the only filearea in the module.
143 * For non-admin the course categories are not shown as well (courses are shown as a list)
144 *
145 * @package repository_local
146 * @copyright 2012 Marina Glancy
147 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
148 */
149class repository_local_file {
150 /** @var array stores already retrieved files */
151 private static $cachedfiles = array();
152 /** @var file_info Stores the original file */
153 public $fileinfo;
154 /** @var bool whether this file is directory */
155 private $isdir;
156 /** @var array caches retrieved children */
157 private $children = null;
158 /** @var array caches retrieved information whether this file is an empty directory */
159 protected $isempty = null;
160 /** @var repository link to the container repository (for filtering the results) */
161 private $repository;
162 /** @var repository_local_file link to parent directory */
163 protected $parent;
164 /** @var bool caches calculated information on whether this directory must be skipped in the tree */
165 private $skip = null;
166
167 /**
168 * Creates (or retrieves from cache) the repository_local_file object for $file_info
169 *
170 * @param file_info $fileinfo
171 * @param repository $repository
172 * @param repository_local_file $parent
173 * @return repository_local_file
174 */
175 public static function retrieve_file_info(file_info $fileinfo, repository $repository, repository_local_file $parent = null) {
176 $encodedpath = base64_encode(serialize($fileinfo->get_params()));
177 if (!isset(self::$cachedfiles[$encodedpath])) {
178 self::$cachedfiles[$encodedpath] = new repository_local_file($fileinfo, $repository, $parent);
179 }
180 return self::$cachedfiles[$encodedpath];
181 }
182
183 /**
184 * Creates an object
185 *
186 * @param file_info $fileinfo
187 * @param repository $repository
188 * @param repository_local_file $parent
189 */
190 private function __construct(file_info $fileinfo, repository $repository, repository_local_file $parent = null) {
191 $this->repository = $repository;
192 $this->fileinfo = $fileinfo;
193 $this->isdir = $fileinfo->is_directory();
194 if (!$this->isdir) {
195 $node = array('title' => $this->fileinfo->get_visible_name());
196 $this->isempty = !$repository->filter($node);
197 $this->skip = false;
198 }
199 }
200
201 /**
202 * Returns node for $ret['list']
203 *
204 * @return array
205 */
206 public function get_node() {
207 global $OUTPUT;
208 $encodedpath = base64_encode(serialize($this->fileinfo->get_params()));
209 $node = array(
210 'title' => $this->fileinfo->get_visible_name(),
5bdf63cc
MG
211 'datemodified' => $this->fileinfo->get_timemodified(),
212 'datecreated' => $this->fileinfo->get_timecreated()
213 );
e2652f1a
MG
214 if ($this->isdir) {
215 $node['path'] = $encodedpath;
559276b1 216 $node['thumbnail'] = $OUTPUT->pix_url(file_folder_icon(90))->out(false);
e2652f1a
MG
217 $node['children'] = array();
218 } else {
5bdf63cc
MG
219 $node['size'] = $this->fileinfo->get_filesize();
220 $node['author'] = $this->fileinfo->get_author();
221 $node['license'] = $this->fileinfo->get_license();
e2652f1a 222 $node['source'] = $encodedpath;
559276b1 223 $node['thumbnail'] = $OUTPUT->pix_url(file_file_icon($this->fileinfo, 90))->out(false);
9213f547 224 $node['icon'] = $OUTPUT->pix_url(file_file_icon($this->fileinfo, 24))->out(false);
dfad252c
DM
225 if ($imageinfo = $this->fileinfo->get_imageinfo()) {
226 // what a beautiful picture, isn't it
227 $fileurl = new moodle_url($this->fileinfo->get_url());
3333e7e2
DM
228 $node['realthumbnail'] = $fileurl->out(false, array('preview' => 'thumb', 'oid' => $this->fileinfo->get_timemodified()));
229 $node['realicon'] = $fileurl->out(false, array('preview' => 'tinyicon', 'oid' => $this->fileinfo->get_timemodified()));
dfad252c
DM
230 $node['image_width'] = $imageinfo['width'];
231 $node['image_height'] = $imageinfo['height'];
232 }
e2652f1a
MG
233 }
234 return $node;
235 }
236
237 /**
238 * Returns node for $ret['path']
239 *
240 * @return array
241 */
242 public function get_node_path() {
243 $encodedpath = base64_encode(serialize($this->fileinfo->get_params()));
244 return array(
245 'path' => $encodedpath,
246 'name' => $this->fileinfo->get_visible_name()
247 );
248 }
249
250 /**
251 * Checks if this is a directory
252 *
253 * @return bool
254 */
255 public function is_dir() {
256 return $this->isdir;
257 }
258
259 /**
260 * Returns children of this element
261 *
262 * @return array
263 */
264 public function get_children() {
265 if (!$this->isdir) {
266 return array();
267 }
268 if ($this->children === null) {
269 $this->children = array();
270 $children = $this->fileinfo->get_children();
271 for ($i=0; $i<count($children); $i++) {
272 $this->children[] = self::retrieve_file_info($children[$i], $this->repository, $this);
273 }
274 }
275 return $this->children;
276 }
277
278 /**
279 * Checks if this folder is empty (contains no non-empty children)
280 *
281 * @return bool
282 */
283 public function is_empty() {
284 if ($this->isempty === null) {
285 $this->isempty = true;
286 if (!$this->fileinfo->is_empty_area()) {
287 // even if is_empty_area() returns false, element still may be empty
288 $children = $this->get_children();
289 if (!empty($children)) {
290 // 1. Let's look at already retrieved children
291 foreach ($children as $childnode) {
292 if ($childnode->isempty === false) {
293 // we already calculated isempty for a child, and it is not empty
294 $this->isempty = false;
295 break;
296 }
297 }
298 if ($this->isempty) {
299 // 2. now we know that this directory contains children that are either empty or we don't know
300 foreach ($children as $childnode) {
301 if (!$childnode->is_empty()) {
302 $this->isempty = false;
303 break;
304 }
305 }
306 }
307 }
308 }
309 }
310 return $this->isempty;
311 }
312
313 /**
314 * Returns the parent element
315 *
316 * @return repository_local_file
317 */
318 public function get_parent() {
319 if ($this->parent === null) {
320 if ($parent = $this->fileinfo->get_parent()) {
321 $this->parent = self::retrieve_file_info($parent, $this->repository);
322 } else {
323 $this->parent = false;
324 }
325 }
326 return $this->parent;
327 }
328
329 /**
330 * Wether this folder may be skipped in tree view
331 *
332 * @return bool
333 */
334 public function can_skip() {
335 global $CFG;
336 if ($this->skip === null) {
337 $this->skip = false;
338 if ($this->fileinfo instanceof file_info_stored) {
339 $params = $this->fileinfo->get_params();
340 if (strlen($params['filearea']) && $params['filepath'] == '/' && $params['filename'] == '.') {
341 // This is a filearea inside an activity, it can be skipped if it has no non-empty siblings
342 if ($parent = $this->get_parent()) {
343 $siblings = $parent->get_children();
344 $countnonempty = 0;
345 foreach ($siblings as $sibling) {
346 if (!$sibling->is_empty()) {
347 $countnonempty++;
348 if ($countnonempty > 1) {
349 break;
350 }
351 }
352 }
353 if ($countnonempty <= 1) {
354 $this->skip = true;
355 }
356 }
357 }
358 } else if ($this->fileinfo instanceof file_info_context_coursecat) {
359 // This is a course category. For non-admins we do not display categories
360 $this->skip = empty($CFG->navshowmycoursecategories) &&
361 !has_capability('moodle/course:update', get_context_instance(CONTEXT_SYSTEM));
362 }
363 }
364 return $this->skip;
365 }
366
367 /**
368 * Returns array of children who have any elmenets
369 *
370 * If a subfolder can be skipped - list children of subfolder instead
371 * (recursive function)
372 *
373 * @return array
374 */
375 public function get_non_empty_children() {
376 $children = $this->get_children();
377 $nonemptychildren = array();
378 foreach ($children as $child) {
379 if (!$child->is_empty()) {
380 if ($child->can_skip()) {
381 $nonemptychildren = array_merge($nonemptychildren, $child->get_non_empty_children());
382 } else {
383 $nonemptychildren[] = $child;
384 }
385 }
386 }
387 return $nonemptychildren;
388 }
727dac2f 389}