3 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
20 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 require_once($CFG->dirroot
. '/mod/data/lib.php');
25 require_once($CFG->libdir
. '/portfolio/caller.php');
26 require_once($CFG->libdir
. '/filelib.php');
29 * The class to handle entry exports of a database module
31 class data_portfolio_caller
extends portfolio_module_caller_base
{
33 /** @var int the single record to export */
36 /** @var object the record from the data table */
39 /**#@+ @var array the fields used and their fieldtypes */
43 /** @var object the records to export */
46 /** @var int how many records are 'mine' */
50 * the required callback arguments for a single-record export
54 public static function expected_callbackargs() {
62 * @param array $callbackargs the arguments passed through
64 public function __construct($callbackargs) {
65 parent
::__construct($callbackargs);
66 // set up the list of fields to export
67 $this->selectedfields
= array();
68 foreach ($callbackargs as $key => $value) {
69 if (strpos($key, 'field_') === 0) {
70 $this->selectedfields
[] = substr($key, 6);
76 * load up the data needed for the export
80 public function load_data() {
82 if (!$this->cm
= get_coursemodule_from_id('data', $this->id
)) {
83 throw new portfolio_caller_exception('invalidid', 'data');
85 if (!$this->data
= $DB->get_record('data', array('id' => $this->cm
->instance
))) {
86 throw new portfolio_caller_exception('invalidid', 'data');
88 $fieldrecords = $DB->get_records('data_fields', array('dataid' => $this->cm
->instance
), 'id');
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
;
97 $this->records
= array();
98 if ($this->recordid
) {
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;
103 $where = array('dataid' => $this->data
->id
);
104 if (!has_capability('mod/data:exportallentries', context_module
::instance($this->cm
->id
))) {
105 $where['userid'] = $USER->id
; // get them all in case, we'll unset ones that aren't ours later if necessary
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;
112 $this->minecount
= $DB->count_records('data_records', array('dataid' => $this->data
->id
, 'userid' => $USER->id
));
115 if ($this->recordid
) {
116 list($formats, $files) = self
::formats($this->fields
, $this->records
[0]);
117 $this->set_file_and_format_data($files);
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
127 * @return one of PORTFOLIO_TIME_XX constants
129 public function expected_time() {
130 if ($this->recordid
) {
131 return $this->expected_time_file();
133 return portfolio_expected_time_db(count($this->records
));
138 * Calculate the shal1 of this export
139 * Dependent on the export format.
142 public function get_sha1() {
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();
148 // otherwise we're exporting some sort of multipart content so use the data
150 foreach ($this->records
as $record) {
151 foreach ($record as $data) {
152 if (is_array($data) ||
is_object($data)) {
153 $keys = array_keys($data);
154 $testkey = array_pop($keys);
155 if (is_array($data[$testkey]) ||
is_object($data[$testkey])) {
156 foreach ($data as $d) {
157 $str .= implode(',', (array)$d);
160 $str .= implode(',', (array)$data);
167 return sha1($str . ',' . $this->exporter
->get('formatclass'));
171 * Prepare the package for export
173 * @return stored_file object
175 public function prepare_package() {
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();
188 if ($this->exporter
->get('format') instanceof portfolio_format_file
&& $this->singlefile
) {
189 return $this->get('exporter')->copy_existing_file($this->singlefile
);
191 foreach ($this->records
as $key => $record) {
192 if ($onlymine && $record->userid
!= $uid) {
193 unset($this->records
[$key]); // sha1
196 list($tmpcontent, $files) = $this->exportentry($record);
197 $content .= $tmpcontent;
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');
206 $entry->author
= $users[$record->userid
];
209 $leapwriter->link_files($entry, $files, 'dataentry' . $record->id
. 'file');
210 $leapwriter->add_entry($entry);
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');
220 $filename = $this->exporter
->get('format')->manifest_name();
221 $content = $leapwriter->to_xml();
223 if (count($this->records
) == 1) {
224 $filename = clean_filename($this->cm
->name
. '-entry.html');
226 $filename = clean_filename($this->cm
->name
. '-full.html');
229 return $this->exporter
->write_new_file(
232 ($this->exporter
->get('format') instanceof PORTFOLIO_FORMAT_RICH
) // if we have associate files, this is a 'manifest'
237 * Verify the user can still export this entry
241 public function check_permissions() {
242 if ($this->recordid
) {
243 if (data_isowner($this->recordid
)) {
244 return has_capability('mod/data:exportownentry', context_module
::instance($this->cm
->id
));
246 return has_capability('mod/data:exportentry', context_module
::instance($this->cm
->id
));
248 if ($this->has_export_config() && !$this->get_export_config('mineonly')) {
249 return has_capability('mod/data:exportallentries', context_module
::instance($this->cm
->id
));
251 return has_capability('mod/data:exportownentry', context_module
::instance($this->cm
->id
));
257 public static function display_name() {
258 return get_string('modulename', 'data');
265 public function __wakeup() {
268 return true; // too early yet
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]));
277 * Prepare a single entry for export, replacing all the content etc
279 * @param stdclass $record the entry to export
281 * @return array with key 0 = the html content, key 1 = array of attachments
283 private function exportentry($record) {
286 $replacement = array();
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'))) {
294 if (!$file = $field->get_file($record->id
)) {
296 continue; // probably left empty
298 $replacement[] = $format->file_output($file);
299 $this->get('exporter')->copy_existing_file($file);
302 $replacement[] = $field->display_browse_field($record->id
, 'singletemplate');
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##';
314 $patterns[]='##disapprove##';
315 $patterns[]='##comments##';
316 $patterns[] = '##timeadded##';
317 $patterns[] = '##timemodified##';
327 $replacement[] = userdate($record->timecreated
);
328 $replacement[] = userdate($record->timemodified
);
330 // actual replacement of the tags
331 return array(str_ireplace($patterns, $replacement, $this->data
->singletemplate
), $files);
335 * Given the fields being exported, and the single record,
336 * work out which export format(s) we can use
338 * @param array $fields array of field objects
339 * @param object $record The data record object
341 * @uses PORTFOLIO_FORMAT_PLAINHTML
342 * @uses PORTFOLIO_FORMAT_RICHHTML
344 * @return array of PORTFOLIO_XX constants
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'))) {
351 $includedfiles[] = $singlefield->get_file($record->id
);
354 if (count($includedfiles) == 1 && count($fields) == 1) {
355 $formats = array(portfolio_format_from_mimetype($includedfiles[0]->get_mimetype()));
356 } else if (count($includedfiles) > 0) {
357 $formats = array(PORTFOLIO_FORMAT_RICHHTML
);
359 return array($formats, $includedfiles);
362 public static function has_files($data) {
364 $fieldrecords = $DB->get_records('data_fields', array('dataid' => $data->id
), 'id');
365 // populate objets for this databases fields
366 foreach ($fieldrecords as $fieldrecord) {
367 $field = data_get_field($fieldrecord, $data);
368 if (is_callable(array($field, 'get_file'))) {
376 * base supported formats before we know anything about the export
378 public static function base_supported_formats() {
379 return array(PORTFOLIO_FORMAT_RICHHTML
, PORTFOLIO_FORMAT_PLAINHTML
, PORTFOLIO_FORMAT_LEAP2A
);
382 public function has_export_config() {
383 // if we're exporting more than just a single entry,
384 // and we have the capability to export all entries,
385 // then ask whether we want just our own, or all of them
386 return (empty($this->recordid
) // multi-entry export
387 && $this->minecount
> 0 // some of them are mine
388 && $this->minecount
!= count($this->records
) // not all of them are mine
389 && has_capability('mod/data:exportallentries', context_module
::instance($this->cm
->id
))); // they actually have a choice in the matter
392 public function export_config_form(&$mform, $instance) {
393 if (!$this->has_export_config()) {
396 $mform->addElement('selectyesno', 'mineonly', get_string('exportownentries', 'data', (object)array('mine' => $this->minecount
, 'all' => count($this->records
))));
397 $mform->setDefault('mineonly', 1);
400 public function get_allowed_export_config() {
401 return array('mineonly');
407 * Class representing the virtual node with all itemids in the file browser
410 * @copyright 2012 David Mudrak <david@moodle.com>
411 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
413 class data_file_info_container
extends file_info
{
414 /** @var file_browser */
421 protected $component;
430 * Constructor (in case you did not realize it ;-)
432 * @param file_browser $browser
433 * @param stdClass $course
434 * @param stdClass $cm
435 * @param stdClass $context
436 * @param array $areas
437 * @param string $filearea
439 public function __construct($browser, $course, $cm, $context, $areas, $filearea) {
440 parent
::__construct($browser, $context);
441 $this->browser
= $browser;
442 $this->course
= $course;
444 $this->component
= 'mod_data';
445 $this->context
= $context;
446 $this->areas
= $areas;
447 $this->filearea
= $filearea;
451 * @return array with keys contextid, filearea, itemid, filepath and filename
453 public function get_params() {
455 'contextid' => $this->context
->id
,
456 'component' => $this->component
,
457 'filearea' => $this->filearea
,
465 * Can new files or directories be added via the file browser
469 public function is_writable() {
474 * Should this node be considered as a folder in the file browser
478 public function is_directory() {
483 * Returns localised visible name of this node
487 public function get_visible_name() {
488 return $this->areas
[$this->filearea
];
492 * Returns list of children nodes
494 * @return array of file_info instances
496 public function get_children() {
497 return $this->get_filtered_children('*', false, true);
501 * Help function to return files matching extensions or their count
503 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
504 * @param bool|int $countonly if false returns the children, if an int returns just the
505 * count of children but stops counting when $countonly number of children is reached
506 * @param bool $returnemptyfolders if true returns items that don't have matching files inside
507 * @return array|int array of file_info instances or the count
509 private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
511 $params = array('contextid' => $this->context
->id
,
512 'component' => $this->component
,
513 'filearea' => $this->filearea
);
514 $sql = 'SELECT DISTINCT itemid
516 WHERE contextid = :contextid
517 AND component = :component
518 AND filearea = :filearea';
519 if (!$returnemptyfolders) {
520 $sql .= ' AND filename <> :emptyfilename';
521 $params['emptyfilename'] = '.';
523 list($sql2, $params2) = $this->build_search_files_sql($extensions);
525 $params = array_merge($params, $params2);
526 if ($countonly === false) {
527 $sql .= ' ORDER BY itemid DESC';
530 $rs = $DB->get_recordset_sql($sql, $params);
532 foreach ($rs as $record) {
533 if ($child = $this->browser
->get_file_info($this->context
, 'mod_data', $this->filearea
, $record->itemid
)) {
534 $children[] = $child;
536 if ($countonly !== false && count($children) >= $countonly) {
541 if ($countonly !== false) {
542 return count($children);
548 * Returns list of children which are either files matching the specified extensions
549 * or folders that contain at least one such file.
551 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
552 * @return array of file_info instances
554 public function get_non_empty_children($extensions = '*') {
555 return $this->get_filtered_children($extensions, false);
559 * Returns the number of children which are either files matching the specified extensions
560 * or folders containing at least one such file.
562 * @param string|array $extensions, for example '*' or array('.gif','.jpg')
563 * @param int $limit stop counting after at least $limit non-empty children are found
566 public function count_non_empty_children($extensions = '*', $limit = 1) {
567 return $this->get_filtered_children($extensions, $limit);
571 * Returns parent file_info instance
573 * @return file_info or null for root
575 public function get_parent() {
576 return $this->browser
->get_file_info($this->context
);