weekly release 3.2dev
[moodle.git] / mod / data / locallib.php
CommitLineData
24ba58ee
PL
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
002a2a44 19 * @package mod_data
24ba58ee
PL
20 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 */
23
c3e1b5bd 24require_once($CFG->dirroot . '/mod/data/lib.php');
ee314f40 25require_once($CFG->libdir . '/portfolio/caller.php');
b63084a5 26require_once($CFG->libdir . '/filelib.php');
c3e1b5bd 27
24ba58ee 28/**
c3e1b5bd 29 * The class to handle entry exports of a database module
24ba58ee 30 */
24ba58ee
PL
31class data_portfolio_caller extends portfolio_module_caller_base {
32
c3e1b5bd 33 /** @var int the single record to export */
24ba58ee 34 protected $recordid;
24ba58ee 35
c3e1b5bd 36 /** @var object the record from the data table */
24ba58ee 37 private $data;
c3e1b5bd
PL
38
39 /**#@+ @var array the fields used and their fieldtypes */
24ba58ee
PL
40 private $fields;
41 private $fieldtypes;
c3e1b5bd
PL
42
43 /** @var object the records to export */
44 private $records;
45
46 /** @var int how many records are 'mine' */
47 private $minecount;
48
24ba58ee 49 /**
c3e1b5bd
PL
50 * the required callback arguments for a single-record export
51 *
24ba58ee
PL
52 * @return array
53 */
54 public static function expected_callbackargs() {
55 return array(
c3e1b5bd
PL
56 'id' => true,
57 'recordid' => false,
24ba58ee
PL
58 );
59 }
c3e1b5bd 60
24ba58ee 61 /**
c3e1b5bd 62 * @param array $callbackargs the arguments passed through
24ba58ee
PL
63 */
64 public function __construct($callbackargs) {
65 parent::__construct($callbackargs);
c3e1b5bd 66 // set up the list of fields to export
24ba58ee
PL
67 $this->selectedfields = array();
68 foreach ($callbackargs as $key => $value) {
69 if (strpos($key, 'field_') === 0) {
70 $this->selectedfields[] = substr($key, 6);
71 }
72 }
73 }
74
75 /**
c3e1b5bd
PL
76 * load up the data needed for the export
77 *
78 * @global object $DB
24ba58ee
PL
79 */
80 public function load_data() {
c3e1b5bd 81 global $DB, $USER;
24ba58ee
PL
82 if (!$this->cm = get_coursemodule_from_id('data', $this->id)) {
83 throw new portfolio_caller_exception('invalidid', 'data');
84 }
c3e1b5bd
PL
85 if (!$this->data = $DB->get_record('data', array('id' => $this->cm->instance))) {
86 throw new portfolio_caller_exception('invalidid', 'data');
87 }
88 $fieldrecords = $DB->get_records('data_fields', array('dataid' => $this->cm->instance), 'id');
24ba58ee
PL
89 // populate objets for this databases fields
90 $this->fields = array();
91 foreach ($fieldrecords as $fieldrecord) {
92 $tmp = data_get_field($fieldrecord, $this->data);
93 $this->fields[] = $tmp;
94 $this->fieldtypes[] = $tmp->type;
95 }
96
c3e1b5bd 97 $this->records = array();
24ba58ee 98 if ($this->recordid) {
c3e1b5bd
PL
99 $tmp = $DB->get_record('data_records', array('id' => $this->recordid));
100 $tmp->content = $DB->get_records('data_content', array('recordid' => $this->recordid));
101 $this->records[] = $tmp;
24ba58ee 102 } else {
c3e1b5bd 103 $where = array('dataid' => $this->data->id);
0b3b8c30 104 if (!has_capability('mod/data:exportallentries', context_module::instance($this->cm->id))) {
c3e1b5bd
PL
105 $where['userid'] = $USER->id; // get them all in case, we'll unset ones that aren't ours later if necessary
106 }
107 $tmp = $DB->get_records('data_records', $where);
108 foreach ($tmp as $t) {
109 $t->content = $DB->get_records('data_content', array('recordid' => $t->id));
110 $this->records[] = $t;
111 }
112 $this->minecount = $DB->count_records('data_records', array('dataid' => $this->data->id, 'userid' => $USER->id));
24ba58ee 113 }
24ba58ee 114
c3e1b5bd
PL
115 if ($this->recordid) {
116 list($formats, $files) = self::formats($this->fields, $this->records[0]);
117 $this->set_file_and_format_data($files);
118 }
24ba58ee
PL
119 }
120
121 /**
c3e1b5bd
PL
122 * How long we think the export will take
123 * Single entry is probably not too long.
124 * But we check for filesizes
125 * Else base it on the number of records
126 *
127 * @return one of PORTFOLIO_TIME_XX constants
24ba58ee
PL
128 */
129 public function expected_time() {
c3e1b5bd
PL
130 if ($this->recordid) {
131 return $this->expected_time_file();
132 } else {
133 return portfolio_expected_time_db(count($this->records));
24ba58ee 134 }
24ba58ee
PL
135 }
136
137 /**
c3e1b5bd
PL
138 * Calculate the shal1 of this export
139 * Dependent on the export format.
24ba58ee
PL
140 * @return string
141 */
142 public function get_sha1() {
c3e1b5bd
PL
143 // in the case that we're exporting a subclass of 'file' and we have a singlefile,
144 // then we're not exporting any metadata, just the file by itself by mimetype.
145 if ($this->exporter->get('format') instanceof portfolio_format_file && $this->singlefile) {
146 return $this->get_sha1_file();
24ba58ee 147 }
c3e1b5bd 148 // otherwise we're exporting some sort of multipart content so use the data
24ba58ee 149 $str = '';
c3e1b5bd
PL
150 foreach ($this->records as $record) {
151 foreach ($record as $data) {
152 if (is_array($data) || is_object($data)) {
6498b619
PS
153 $keys = array_keys($data);
154 $testkey = array_pop($keys);
c3e1b5bd
PL
155 if (is_array($data[$testkey]) || is_object($data[$testkey])) {
156 foreach ($data as $d) {
157 $str .= implode(',', (array)$d);
158 }
159 } else {
160 $str .= implode(',', (array)$data);
24ba58ee
PL
161 }
162 } else {
c3e1b5bd 163 $str .= $data;
24ba58ee 164 }
24ba58ee
PL
165 }
166 }
c3e1b5bd 167 return sha1($str . ',' . $this->exporter->get('formatclass'));
24ba58ee 168 }
c3e1b5bd 169
24ba58ee 170 /**
c3e1b5bd
PL
171 * Prepare the package for export
172 *
173 * @return stored_file object
24ba58ee
PL
174 */
175 public function prepare_package() {
176 global $DB;
c3e1b5bd 177 $leapwriter = null;
24ba58ee
PL
178 $content = '';
179 $filename = '';
c3e1b5bd
PL
180 $uid = $this->exporter->get('user')->id;
181 $users = array(); //cache
182 $onlymine = $this->get_export_config('mineonly');
183 if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
184 $leapwriter = $this->exporter->get('format')->leap2a_writer();
185 $ids = array();
186 }
187
188 if ($this->exporter->get('format') instanceof portfolio_format_file && $this->singlefile) {
189 return $this->get('exporter')->copy_existing_file($this->singlefile);
190 }
191 foreach ($this->records as $key => $record) {
192 if ($onlymine && $record->userid != $uid) {
193 unset($this->records[$key]); // sha1
194 continue;
195 }
196 list($tmpcontent, $files) = $this->exportentry($record);
197 $content .= $tmpcontent;
198 if ($leapwriter) {
199 $entry = new portfolio_format_leap2a_entry('dataentry' . $record->id, $this->data->name, 'resource', $tmpcontent);
200 $entry->published = $record->timecreated;
201 $entry->updated = $record->timemodified;
202 if ($record->userid != $uid) {
203 if (!array_key_exists($record->userid, $users)) {
204 $users[$record->userid] = $DB->get_record('user', array('id' => $record->userid), 'id,firstname,lastname');
205 }
206 $entry->author = $users[$record->userid];
207 }
208 $ids[] = $entry->id;
ebb7e782 209 $leapwriter->link_files($entry, $files, 'dataentry' . $record->id . 'file');
c3e1b5bd
PL
210 $leapwriter->add_entry($entry);
211 }
212 }
213 if ($leapwriter) {
214 if (count($this->records) > 1) { // make a selection element to tie them all together
215 $selection = new portfolio_format_leap2a_entry('datadb' . $this->data->id,
216 get_string('entries', 'data') . ': ' . $this->data->name, 'selection');
217 $leapwriter->add_entry($selection);
218 $leapwriter->make_selection($selection, $ids, 'Grouping');
219 }
220 $filename = $this->exporter->get('format')->manifest_name();
221 $content = $leapwriter->to_xml();
222 } else {
223 if (count($this->records) == 1) {
24ba58ee 224 $filename = clean_filename($this->cm->name . '-entry.html');
c3e1b5bd
PL
225 } else {
226 $filename = clean_filename($this->cm->name . '-full.html');
227 }
24ba58ee
PL
228 }
229 return $this->exporter->write_new_file(
230 $content,
231 $filename,
232 ($this->exporter->get('format') instanceof PORTFOLIO_FORMAT_RICH) // if we have associate files, this is a 'manifest'
233 );
234 }
235
236 /**
c3e1b5bd
PL
237 * Verify the user can still export this entry
238 *
24ba58ee
PL
239 * @return bool
240 */
241 public function check_permissions() {
c3e1b5bd
PL
242 if ($this->recordid) {
243 if (data_isowner($this->recordid)) {
0b3b8c30 244 return has_capability('mod/data:exportownentry', context_module::instance($this->cm->id));
c3e1b5bd 245 }
0b3b8c30 246 return has_capability('mod/data:exportentry', context_module::instance($this->cm->id));
c3e1b5bd
PL
247 }
248 if ($this->has_export_config() && !$this->get_export_config('mineonly')) {
0b3b8c30 249 return has_capability('mod/data:exportallentries', context_module::instance($this->cm->id));
c3e1b5bd 250 }
0b3b8c30 251 return has_capability('mod/data:exportownentry', context_module::instance($this->cm->id));
24ba58ee
PL
252 }
253
254 /**
255 * @return string
256 */
257 public static function display_name() {
258 return get_string('modulename', 'data');
259 }
260
261 /**
262 * @global object
263 * @return bool|void
264 */
265 public function __wakeup() {
266 global $CFG;
267 if (empty($CFG)) {
268 return true; // too early yet
269 }
270 foreach ($this->fieldtypes as $key => $field) {
271 require_once($CFG->dirroot . '/mod/data/field/' . $field .'/field.class.php');
272 $this->fields[$key] = unserialize(serialize($this->fields[$key]));
273 }
274 }
275
276 /**
c3e1b5bd
PL
277 * Prepare a single entry for export, replacing all the content etc
278 *
279 * @param stdclass $record the entry to export
280 *
281 * @return array with key 0 = the html content, key 1 = array of attachments
24ba58ee 282 */
c3e1b5bd 283 private function exportentry($record) {
24ba58ee
PL
284 // Replacing tags
285 $patterns = array();
286 $replacement = array();
24ba58ee 287
c3e1b5bd 288 $files = array();
24ba58ee
PL
289 // Then we generate strings to replace for normal tags
290 $format = $this->get('exporter')->get('format');
291 foreach ($this->fields as $field) {
292 $patterns[]='[['.$field->field->name.']]';
293 if (is_callable(array($field, 'get_file'))) {
c3e1b5bd 294 if (!$file = $field->get_file($record->id)) {
24ba58ee
PL
295 $replacement[] = '';
296 continue; // probably left empty
297 }
298 $replacement[] = $format->file_output($file);
299 $this->get('exporter')->copy_existing_file($file);
c3e1b5bd 300 $files[] = $file;
24ba58ee 301 } else {
c3e1b5bd 302 $replacement[] = $field->display_browse_field($record->id, 'singletemplate');
24ba58ee
PL
303 }
304 }
305
306 // Replacing special tags (##Edit##, ##Delete##, ##More##)
307 $patterns[]='##edit##';
308 $patterns[]='##delete##';
309 $patterns[]='##export##';
310 $patterns[]='##more##';
311 $patterns[]='##moreurl##';
312 $patterns[]='##user##';
313 $patterns[]='##approve##';
d0372ed6 314 $patterns[]='##disapprove##';
24ba58ee
PL
315 $patterns[]='##comments##';
316 $patterns[] = '##timeadded##';
317 $patterns[] = '##timemodified##';
318 $replacement[] = '';
319 $replacement[] = '';
320 $replacement[] = '';
321 $replacement[] = '';
322 $replacement[] = '';
323 $replacement[] = '';
324 $replacement[] = '';
325 $replacement[] = '';
d0372ed6 326 $replacement[] = '';
c3e1b5bd
PL
327 $replacement[] = userdate($record->timecreated);
328 $replacement[] = userdate($record->timemodified);
24ba58ee
PL
329
330 // actual replacement of the tags
c3e1b5bd 331 return array(str_ireplace($patterns, $replacement, $this->data->singletemplate), $files);
24ba58ee
PL
332 }
333
334 /**
c3e1b5bd
PL
335 * Given the fields being exported, and the single record,
336 * work out which export format(s) we can use
337 *
338 * @param array $fields array of field objects
339 * @param object $record The data record object
340 *
24ba58ee
PL
341 * @uses PORTFOLIO_FORMAT_PLAINHTML
342 * @uses PORTFOLIO_FORMAT_RICHHTML
c3e1b5bd
PL
343 *
344 * @return array of PORTFOLIO_XX constants
24ba58ee
PL
345 */
346 public static function formats($fields, $record) {
347 $formats = array(PORTFOLIO_FORMAT_PLAINHTML);
348 $includedfiles = array();
349 foreach ($fields as $singlefield) {
350 if (is_callable(array($singlefield, 'get_file'))) {
f3998ce4
AG
351 if ($file = $singlefield->get_file($record->id)) {
352 $includedfiles[] = $file;
353 }
24ba58ee
PL
354 }
355 }
356 if (count($includedfiles) == 1 && count($fields) == 1) {
c3e1b5bd 357 $formats = array(portfolio_format_from_mimetype($includedfiles[0]->get_mimetype()));
24ba58ee
PL
358 } else if (count($includedfiles) > 0) {
359 $formats = array(PORTFOLIO_FORMAT_RICHHTML);
360 }
361 return array($formats, $includedfiles);
362 }
363
c3e1b5bd
PL
364 public static function has_files($data) {
365 global $DB;
366 $fieldrecords = $DB->get_records('data_fields', array('dataid' => $data->id), 'id');
367 // populate objets for this databases fields
368 foreach ($fieldrecords as $fieldrecord) {
369 $field = data_get_field($fieldrecord, $data);
370 if (is_callable(array($field, 'get_file'))) {
371 return true;
372 }
373 }
374 return false;
375 }
376
377 /**
378 * base supported formats before we know anything about the export
379 */
24ba58ee 380 public static function base_supported_formats() {
c3e1b5bd
PL
381 return array(PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_LEAP2A);
382 }
383
384 public function has_export_config() {
385 // if we're exporting more than just a single entry,
386 // and we have the capability to export all entries,
387 // then ask whether we want just our own, or all of them
388 return (empty($this->recordid) // multi-entry export
389 && $this->minecount > 0 // some of them are mine
390 && $this->minecount != count($this->records) // not all of them are mine
0b3b8c30 391 && has_capability('mod/data:exportallentries', context_module::instance($this->cm->id))); // they actually have a choice in the matter
c3e1b5bd
PL
392 }
393
394 public function export_config_form(&$mform, $instance) {
395 if (!$this->has_export_config()) {
396 return;
397 }
398 $mform->addElement('selectyesno', 'mineonly', get_string('exportownentries', 'data', (object)array('mine' => $this->minecount, 'all' => count($this->records))));
399 $mform->setDefault('mineonly', 1);
400 }
401
402 public function get_allowed_export_config() {
403 return array('mineonly');
24ba58ee
PL
404 }
405}
386b67d1
DM
406
407
408/**
409 * Class representing the virtual node with all itemids in the file browser
410 *
411 * @category files
412 * @copyright 2012 David Mudrak <david@moodle.com>
413 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
414 */
415class data_file_info_container extends file_info {
416 /** @var file_browser */
417 protected $browser;
418 /** @var stdClass */
419 protected $course;
420 /** @var stdClass */
421 protected $cm;
422 /** @var string */
423 protected $component;
424 /** @var stdClass */
425 protected $context;
426 /** @var array */
427 protected $areas;
428 /** @var string */
429 protected $filearea;
430
431 /**
432 * Constructor (in case you did not realize it ;-)
433 *
434 * @param file_browser $browser
435 * @param stdClass $course
436 * @param stdClass $cm
437 * @param stdClass $context
438 * @param array $areas
439 * @param string $filearea
440 */
441 public function __construct($browser, $course, $cm, $context, $areas, $filearea) {
442 parent::__construct($browser, $context);
443 $this->browser = $browser;
444 $this->course = $course;
445 $this->cm = $cm;
446 $this->component = 'mod_data';
447 $this->context = $context;
448 $this->areas = $areas;
449 $this->filearea = $filearea;
450 }
451
452 /**
453 * @return array with keys contextid, filearea, itemid, filepath and filename
454 */
455 public function get_params() {
456 return array(
457 'contextid' => $this->context->id,
458 'component' => $this->component,
459 'filearea' => $this->filearea,
460 'itemid' => null,
461 'filepath' => null,
462 'filename' => null,
463 );
464 }
465
466 /**
467 * Can new files or directories be added via the file browser
468 *
469 * @return bool
470 */
471 public function is_writable() {
472 return false;
473 }
474
475 /**
476 * Should this node be considered as a folder in the file browser
477 *
478 * @return bool
479 */
480 public function is_directory() {
481 return true;
482 }
483
484 /**
485 * Returns localised visible name of this node
486 *
487 * @return string
488 */
489 public function get_visible_name() {
490 return $this->areas[$this->filearea];
491 }
492
493 /**
494 * Returns list of children nodes
495 *
496 * @return array of file_info instances
497 */
498 public function get_children() {
b8de2621
MG
499 return $this->get_filtered_children('*', false, true);
500 }
501
502 /**
503 * Help function to return files matching extensions or their count
504 *
505 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
43aadf04
MG
506 * @param bool|int $countonly if false returns the children, if an int returns just the
507 * count of children but stops counting when $countonly number of children is reached
b8de2621
MG
508 * @param bool $returnemptyfolders if true returns items that don't have matching files inside
509 * @return array|int array of file_info instances or the count
510 */
511 private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
386b67d1 512 global $DB;
b8de2621
MG
513 $params = array('contextid' => $this->context->id,
514 'component' => $this->component,
515 'filearea' => $this->filearea);
516 $sql = 'SELECT DISTINCT itemid
517 FROM {files}
518 WHERE contextid = :contextid
519 AND component = :component
520 AND filearea = :filearea';
521 if (!$returnemptyfolders) {
522 $sql .= ' AND filename <> :emptyfilename';
523 $params['emptyfilename'] = '.';
524 }
525 list($sql2, $params2) = $this->build_search_files_sql($extensions);
526 $sql .= ' '.$sql2;
527 $params = array_merge($params, $params2);
43aadf04 528 if ($countonly === false) {
b8de2621
MG
529 $sql .= ' ORDER BY itemid DESC';
530 }
386b67d1 531
b8de2621 532 $rs = $DB->get_recordset_sql($sql, $params);
386b67d1 533 $children = array();
b8de2621
MG
534 foreach ($rs as $record) {
535 if ($child = $this->browser->get_file_info($this->context, 'mod_data', $this->filearea, $record->itemid)) {
386b67d1
DM
536 $children[] = $child;
537 }
43aadf04 538 if ($countonly !== false && count($children) >= $countonly) {
b8de2621
MG
539 break;
540 }
541 }
542 $rs->close();
43aadf04 543 if ($countonly !== false) {
b8de2621 544 return count($children);
386b67d1 545 }
386b67d1
DM
546 return $children;
547 }
548
b8de2621
MG
549 /**
550 * Returns list of children which are either files matching the specified extensions
551 * or folders that contain at least one such file.
552 *
553 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
554 * @return array of file_info instances
555 */
556 public function get_non_empty_children($extensions = '*') {
557 return $this->get_filtered_children($extensions, false);
558 }
559
560 /**
561 * Returns the number of children which are either files matching the specified extensions
562 * or folders containing at least one such file.
563 *
b8de2621 564 * @param string|array $extensions, for example '*' or array('.gif','.jpg')
43aadf04 565 * @param int $limit stop counting after at least $limit non-empty children are found
b8de2621
MG
566 * @return int
567 */
43aadf04
MG
568 public function count_non_empty_children($extensions = '*', $limit = 1) {
569 return $this->get_filtered_children($extensions, $limit);
b8de2621
MG
570 }
571
386b67d1
DM
572 /**
573 * Returns parent file_info instance
574 *
575 * @return file_info or null for root
576 */
577 public function get_parent() {
578 return $this->browser->get_file_info($this->context);
579 }
580}