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/>.
19 * Library of functions and constants for module glossary
20 * outside of what is required for the core moodle api
22 * @package mod_glossary
23 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 require_once($CFG->libdir
. '/portfolio/caller.php');
28 require_once($CFG->libdir
. '/filelib.php');
31 * class to handle exporting an entire glossary database
33 class glossary_full_portfolio_caller
extends portfolio_module_caller_base
{
37 private $keyedfiles = array(); // keyed on entry
40 * return array of expected call back arguments
41 * and whether they are required or not
45 public static function expected_callbackargs() {
52 * load up all data required for this export.
56 public function load_data() {
58 if (!$this->cm
= get_coursemodule_from_id('glossary', $this->id
)) {
59 throw new portfolio_caller_exception('invalidid', 'glossary');
61 if (!$this->glossary
= $DB->get_record('glossary', array('id' => $this->cm
->instance
))) {
62 throw new portfolio_caller_exception('invalidid', 'glossary');
64 $entries = $DB->get_records('glossary_entries', array('glossaryid' => $this->glossary
->id
));
65 list($where, $params) = $DB->get_in_or_equal(array_keys($entries));
67 $aliases = $DB->get_records_select('glossary_alias', 'entryid ' . $where, $params);
68 $categoryentries = $DB->get_records_sql('SELECT ec.entryid, c.name FROM {glossary_entries_categories} ec
69 JOIN {glossary_categories} c
70 ON c.id = ec.categoryid
71 WHERE ec.entryid ' . $where, $params);
73 $this->exportdata
= array('entries' => $entries, 'aliases' => $aliases, 'categoryentries' => $categoryentries);
74 $fs = get_file_storage();
75 $context = context_module
::instance($this->cm
->id
);
76 $this->multifiles
= array();
77 foreach (array_keys($entries) as $entry) {
78 $this->keyedfiles
[$entry] = array_merge(
79 $fs->get_area_files($context->id
, 'mod_glossary', 'attachment', $entry, "timemodified", false),
80 $fs->get_area_files($context->id
, 'mod_glossary', 'entry', $entry, "timemodified", false)
82 $this->multifiles
= array_merge($this->multifiles
, $this->keyedfiles
[$entry]);
87 * how long might we expect this export to take
89 * @return constant one of PORTFOLIO_TIME_XX
91 public function expected_time() {
92 $filetime = portfolio_expected_time_file($this->multifiles
);
93 $dbtime = portfolio_expected_time_db(count($this->exportdata
['entries']));
94 return ($filetime > $dbtime) ?
$filetime : $dbtime;
98 * return the sha1 of this content
102 public function get_sha1() {
104 if ($this->multifiles
) {
105 $file = $this->get_sha1_file();
107 return sha1(serialize($this->exportdata
) . $file);
111 * prepare the package ready to be passed off to the portfolio plugin
115 public function prepare_package() {
116 $entries = $this->exportdata
['entries'];
118 $categories = array();
119 if (is_array($this->exportdata
['aliases'])) {
120 foreach ($this->exportdata
['aliases'] as $alias) {
121 if (!array_key_exists($alias->entryid
, $aliases)) {
122 $aliases[$alias->entryid
] = array();
124 $aliases[$alias->entryid
][] = $alias->alias
;
127 if (is_array($this->exportdata
['categoryentries'])) {
128 foreach ($this->exportdata
['categoryentries'] as $cat) {
129 if (!array_key_exists($cat->entryid
, $categories)) {
130 $categories[$cat->entryid
] = array();
132 $categories[$cat->entryid
][] = $cat->name
;
135 if ($this->get('exporter')->get('formatclass') == PORTFOLIO_FORMAT_SPREADSHEET
) {
136 $csv = glossary_generate_export_csv($entries, $aliases, $categories);
137 $this->exporter
->write_new_file($csv, clean_filename($this->cm
->name
) . '.csv', false);
139 } else if ($this->get('exporter')->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A
) {
140 $ids = array(); // keep track of these to make into a selection later
142 $writer = $this->get('exporter')->get('format')->leap2a_writer($USER);
143 $format = $this->exporter
->get('format');
144 $filename = $this->get('exporter')->get('format')->manifest_name();
145 foreach ($entries as $e) {
146 $content = glossary_entry_portfolio_caller
::entry_content(
151 (array_key_exists($e->id
, $aliases) ?
$aliases[$e->id
] : array()),
154 $entry = new portfolio_format_leap2a_entry('glossaryentry' . $e->id
, $e->concept
, 'entry', $content);
155 $entry->author
= $DB->get_record('user', array('id' => $e->userid
), 'id,firstname,lastname,email');
156 $entry->published
= $e->timecreated
;
157 $entry->updated
= $e->timemodified
;
158 if (!empty($this->keyedfiles
[$e->id
])) {
159 $writer->link_files($entry, $this->keyedfiles
[$e->id
], 'glossaryentry' . $e->id
. 'file');
160 foreach ($this->keyedfiles
[$e->id
] as $file) {
161 $this->exporter
->copy_existing_file($file);
164 if (!empty($categories[$e->id
])) {
165 foreach ($categories[$e->id
] as $cat) {
166 // this essentially treats them as plain tags
167 // leap has the idea of category schemes
168 // but I think this is overkill here
169 $entry->add_category($cat);
172 $writer->add_entry($entry);
175 $selection = new portfolio_format_leap2a_entry('wholeglossary' . $this->glossary
->id
, get_string('modulename', 'glossary'), 'selection');
176 $writer->add_entry($selection);
177 $writer->make_selection($selection, $ids, 'Grouping');
178 $content = $writer->to_xml();
180 $this->exporter
->write_new_file($content, $filename, true);
184 * make sure that the current user is allowed to do this
188 public function check_permissions() {
189 return has_capability('mod/glossary:export', context_module
::instance($this->cm
->id
));
193 * return a nice name to be displayed about this export location
197 public static function display_name() {
198 return get_string('modulename', 'glossary');
202 * what formats this function *generally* supports
206 public static function base_supported_formats() {
207 return array(PORTFOLIO_FORMAT_SPREADSHEET
, PORTFOLIO_FORMAT_LEAP2A
);
212 * class to export a single glossary entry
214 * @package mod_glossary
215 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
216 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
218 class glossary_entry_portfolio_caller
extends portfolio_module_caller_base
{
226 public static function expected_callbackargs() {
234 * load up all data required for this export.
238 public function load_data() {
240 if (!$this->cm
= get_coursemodule_from_id('glossary', $this->id
)) {
241 throw new portfolio_caller_exception('invalidid', 'glossary');
243 if (!$this->glossary
= $DB->get_record('glossary', array('id' => $this->cm
->instance
))) {
244 throw new portfolio_caller_exception('invalidid', 'glossary');
246 if ($this->entryid
) {
247 if (!$this->entry
= $DB->get_record('glossary_entries', array('id' => $this->entryid
))) {
248 throw new portfolio_caller_exception('noentry', 'glossary');
250 // in case we don't have USER this will make the entry be printed
251 $this->entry
->approved
= true;
253 $this->categories
= $DB->get_records_sql('SELECT ec.entryid, c.name FROM {glossary_entries_categories} ec
254 JOIN {glossary_categories} c
255 ON c.id = ec.categoryid
256 WHERE ec.entryid = ?', array($this->entryid
));
257 $context = context_module
::instance($this->cm
->id
);
258 if ($this->entry
->sourceglossaryid
== $this->cm
->instance
) {
259 if ($maincm = get_coursemodule_from_instance('glossary', $this->entry
->glossaryid
)) {
260 $context = context_module
::instance($maincm->id
);
263 $this->aliases
= $DB->get_record('glossary_alias', array('entryid'=>$this->entryid
));
264 $fs = get_file_storage();
265 $this->multifiles
= array_merge(
266 $fs->get_area_files($context->id
, 'mod_glossary', 'attachment', $this->entry
->id
, "timemodified", false),
267 $fs->get_area_files($context->id
, 'mod_glossary', 'entry', $this->entry
->id
, "timemodified", false)
270 if (!empty($this->multifiles
)) {
271 $this->add_format(PORTFOLIO_FORMAT_RICHHTML
);
273 $this->add_format(PORTFOLIO_FORMAT_PLAINHTML
);
278 * how long might we expect this export to take
280 * @return constant one of PORTFOLIO_TIME_XX
282 public function expected_time() {
283 return PORTFOLIO_TIME_LOW
;
287 * make sure that the current user is allowed to do this
291 public function check_permissions() {
292 $context = context_module
::instance($this->cm
->id
);
293 return has_capability('mod/glossary:exportentry', $context)
294 ||
($this->entry
->userid
== $this->user
->id
&& has_capability('mod/glossary:exportownentry', $context));
298 * return a nice name to be displayed about this export location
302 public static function display_name() {
303 return get_string('modulename', 'glossary');
307 * prepare the package ready to be passed off to the portfolio plugin
311 public function prepare_package() {
314 $format = $this->exporter
->get('format');
315 $content = self
::entry_content($this->course
, $this->cm
, $this->glossary
, $this->entry
, $this->aliases
, $format);
317 if ($this->exporter
->get('formatclass') === PORTFOLIO_FORMAT_PLAINHTML
) {
318 $filename = clean_filename($this->entry
->concept
) . '.html';
319 $this->exporter
->write_new_file($content, $filename);
321 } else if ($this->exporter
->get('formatclass') === PORTFOLIO_FORMAT_RICHHTML
) {
322 if ($this->multifiles
) {
323 foreach ($this->multifiles
as $file) {
324 $this->exporter
->copy_existing_file($file);
327 $filename = clean_filename($this->entry
->concept
) . '.html';
328 $this->exporter
->write_new_file($content, $filename);
330 } else if ($this->exporter
->get('formatclass') === PORTFOLIO_FORMAT_LEAP2A
) {
331 $writer = $this->get('exporter')->get('format')->leap2a_writer();
332 $entry = new portfolio_format_leap2a_entry('glossaryentry' . $this->entry
->id
, $this->entry
->concept
, 'entry', $content);
333 $entry->author
= $DB->get_record('user', array('id' => $this->entry
->userid
), 'id,firstname,lastname,email');
334 $entry->published
= $this->entry
->timecreated
;
335 $entry->updated
= $this->entry
->timemodified
;
336 if ($this->multifiles
) {
337 $writer->link_files($entry, $this->multifiles
);
338 foreach ($this->multifiles
as $file) {
339 $this->exporter
->copy_existing_file($file);
342 if ($this->categories
) {
343 foreach ($this->categories
as $cat) {
344 // this essentially treats them as plain tags
345 // leap has the idea of category schemes
346 // but I think this is overkill here
347 $entry->add_category($cat->name
);
350 $writer->add_entry($entry);
351 $content = $writer->to_xml();
352 $filename = $this->get('exporter')->get('format')->manifest_name();
353 $this->exporter
->write_new_file($content, $filename);
356 throw new portfolio_caller_exception('unexpected_format_class', 'glossary');
361 * return the sha1 of this content
365 public function get_sha1() {
366 if ($this->multifiles
) {
367 return sha1(serialize($this->entry
) . $this->get_sha1_file());
369 return sha1(serialize($this->entry
));
373 * what formats this function *generally* supports
377 public static function base_supported_formats() {
378 return array(PORTFOLIO_FORMAT_RICHHTML
, PORTFOLIO_FORMAT_PLAINHTML
, PORTFOLIO_FORMAT_LEAP2A
);
382 * helper function to get the html content of an entry
383 * for both this class and the full glossary exporter
384 * this is a very simplified version of the dictionary format output,
385 * but with its 500 levels of indirection removed
386 * and file rewriting handled by the portfolio export format.
388 * @param stdclass $course
389 * @param stdclass $cm
390 * @param stdclass $glossary
391 * @param stdclass $entry
395 public static function entry_content($course, $cm, $glossary, $entry, $aliases, $format) {
397 $entry = clone $entry;
398 $context = context_module
::instance($cm->id
);
399 $options = portfolio_format_text_options();
400 $options->trusted
= $entry->definitiontrust
;
401 $options->context
= $context;
403 $output = '<table class="glossarypost dictionary" cellspacing="0">' . "\n";
404 $output .= '<tr valign="top">' . "\n";
405 $output .= '<td class="entry">' . "\n";
407 $output .= '<div class="concept">';
408 $output .= format_text($OUTPUT->heading($entry->concept
, 3), FORMAT_MOODLE
, $options);
409 $output .= '</div> ' . "\n";
411 $entry->definition
= format_text($entry->definition
, $entry->definitionformat
, $options);
412 $output .= portfolio_rewrite_pluginfile_urls($entry->definition
, $context->id
, 'mod_glossary', 'entry', $entry->id
, $format);
414 if (isset($entry->footer
)) {
415 $output .= $entry->footer
;
418 $output .= '</td></tr>' . "\n";
420 if (!empty($aliases)) {
421 $aliases = explode(',', $aliases->alias
);
422 $output .= '<tr valign="top"><td class="entrylowersection">';
423 $key = (count($aliases) == 1) ?
'alias' : 'aliases';
424 $output .= get_string($key, 'glossary') . ': ';
425 foreach ($aliases as $alias) {
426 $output .= s($alias) . ',';
428 $output = substr($output, 0, -1);
429 $output .= '</td></tr>' . "\n";
432 if ($entry->sourceglossaryid
== $cm->instance
) {
433 if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid
)) {
436 $filecontext = context_module
::instance($maincm->id
);
439 $filecontext = $context;
441 $fs = get_file_storage();
442 if ($files = $fs->get_area_files($filecontext->id
, 'mod_glossary', 'attachment', $entry->id
, "timemodified", false)) {
443 $output .= '<table border="0" width="100%"><tr><td>' . "\n";
445 foreach ($files as $file) {
446 $output .= $format->file_output($file);
448 $output .= '</td></tr></table>' . "\n";
451 $output .= '</table>' . "\n";
459 * Class representing the virtual node with all itemids in the file browser
462 * @copyright 2012 David Mudrak <david@moodle.com>
463 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
465 class glossary_file_info_container
extends file_info
{
466 /** @var file_browser */
473 protected $component;
482 * Constructor (in case you did not realize it ;-)
484 * @param file_browser $browser
485 * @param stdClass $course
486 * @param stdClass $cm
487 * @param stdClass $context
488 * @param array $areas
489 * @param string $filearea
491 public function __construct($browser, $course, $cm, $context, $areas, $filearea) {
492 parent
::__construct($browser, $context);
493 $this->browser
= $browser;
494 $this->course
= $course;
496 $this->component
= 'mod_glossary';
497 $this->context
= $context;
498 $this->areas
= $areas;
499 $this->filearea
= $filearea;
503 * @return array with keys contextid, filearea, itemid, filepath and filename
505 public function get_params() {
507 'contextid' => $this->context
->id
,
508 'component' => $this->component
,
509 'filearea' => $this->filearea
,
517 * Can new files or directories be added via the file browser
521 public function is_writable() {
526 * Should this node be considered as a folder in the file browser
530 public function is_directory() {
535 * Returns localised visible name of this node
539 public function get_visible_name() {
540 return $this->areas
[$this->filearea
];
544 * Returns list of children nodes
546 * @return array of file_info instances
548 public function get_children() {
549 return $this->get_filtered_children('*', false, true);
553 * Help function to return files matching extensions or their count
555 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
556 * @param bool|int $countonly if false returns the children, if an int returns just the
557 * count of children but stops counting when $countonly number of children is reached
558 * @param bool $returnemptyfolders if true returns items that don't have matching files inside
559 * @return array|int array of file_info instances or the count
561 private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
563 $sql = 'SELECT DISTINCT f.itemid, ge.concept
565 JOIN {modules} m ON (m.name = :modulename AND m.visible = 1)
566 JOIN {course_modules} cm ON (cm.module = m.id AND cm.id = :instanceid)
567 JOIN {glossary} g ON g.id = cm.instance
568 JOIN {glossary_entries} ge ON (ge.glossaryid = g.id AND ge.id = f.itemid)
569 WHERE f.contextid = :contextid
570 AND f.component = :component
571 AND f.filearea = :filearea';
573 'modulename' => 'glossary',
574 'instanceid' => $this->context
->instanceid
,
575 'contextid' => $this->context
->id
,
576 'component' => $this->component
,
577 'filearea' => $this->filearea
);
578 if (!$returnemptyfolders) {
579 $sql .= ' AND f.filename <> :emptyfilename';
580 $params['emptyfilename'] = '.';
582 list($sql2, $params2) = $this->build_search_files_sql($extensions, 'f');
584 $params = array_merge($params, $params2);
585 if ($countonly !== false) {
586 $sql .= ' ORDER BY ge.concept, f.itemid';
589 $rs = $DB->get_recordset_sql($sql, $params);
591 foreach ($rs as $record) {
592 if ($child = $this->browser
->get_file_info($this->context
, 'mod_glossary', $this->filearea
, $record->itemid
)) {
593 $children[] = $child;
595 if ($countonly !== false && count($children) >= $countonly) {
600 if ($countonly !== false) {
601 return count($children);
607 * Returns list of children which are either files matching the specified extensions
608 * or folders that contain at least one such file.
610 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
611 * @return array of file_info instances
613 public function get_non_empty_children($extensions = '*') {
614 return $this->get_filtered_children($extensions, false);
618 * Returns the number of children which are either files matching the specified extensions
619 * or folders containing at least one such file.
621 * @param string|array $extensions, for example '*' or array('.gif','.jpg')
622 * @param int $limit stop counting after at least $limit non-empty children are found
625 public function count_non_empty_children($extensions = '*', $limit = 1) {
626 return $this->get_filtered_children($extensions, $limit);
630 * Returns parent file_info instance
632 * @return file_info or null for root
634 public function get_parent() {
635 return $this->browser
->get_file_info($this->context
);