Merge branch 'MDL-58399-master' of git://github.com/jleyva/moodle
[moodle.git] / mod / imscp / lib.php
1 <?php
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/>.
17 /**
18  * Mandatory public API of imscp module
19  *
20  * @package mod_imscp
21  * @copyright  2009 Petr Skoda  {@link http://skodak.org}
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die();
27 /**
28  * List of features supported in IMS CP module
29  * @param string $feature FEATURE_xx constant for requested feature
30  * @return mixed True if module supports feature, false if not, null if doesn't know
31  */
32 function imscp_supports($feature) {
33     switch($feature) {
34         case FEATURE_MOD_ARCHETYPE:           return MOD_ARCHETYPE_RESOURCE;
35         case FEATURE_GROUPS:                  return false;
36         case FEATURE_GROUPINGS:               return false;
37         case FEATURE_MOD_INTRO:               return true;
38         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
39         case FEATURE_GRADE_HAS_GRADE:         return false;
40         case FEATURE_GRADE_OUTCOMES:          return false;
41         case FEATURE_BACKUP_MOODLE2:          return true;
42         case FEATURE_SHOW_DESCRIPTION:        return true;
44         default: return null;
45     }
46 }
48 /**
49  * Returns all other caps used in module
50  * @return array
51  */
52 function imscp_get_extra_capabilities() {
53     return array('moodle/site:accessallgroups');
54 }
56 /**
57  * This function is used by the reset_course_userdata function in moodlelib.
58  *
59  * @param stdClass $data the data submitted from the reset course.
60  * @return array status array
61  */
62 function imscp_reset_userdata($data) {
63     return array();
64 }
66 /**
67  * List the actions that correspond to a view of this module.
68  * This is used by the participation report.
69  *
70  * Note: This is not used by new logging system. Event with
71  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
72  *       be considered as view action.
73  *
74  * @return array
75  */
76 function imscp_get_view_actions() {
77     return array('view', 'view all');
78 }
80 /**
81  * List the actions that correspond to a post of this module.
82  * This is used by the participation report.
83  *
84  * Note: This is not used by new logging system. Event with
85  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
86  *       will be considered as post action.
87  *
88  * @return array
89  */
90 function imscp_get_post_actions() {
91     return array('update', 'add');
92 }
94 /**
95  * Add imscp instance.
96  * @param object $data
97  * @param object $mform
98  * @return int new imscp instance id
99  */
100 function imscp_add_instance($data, $mform) {
101     global $CFG, $DB;
102     require_once("$CFG->dirroot/mod/imscp/locallib.php");
104     $cmid = $data->coursemodule;
106     $data->timemodified = time();
107     $data->revision     = 1;
108     $data->structure    = null;
110     $data->id = $DB->insert_record('imscp', $data);
112     // We need to use context now, so we need to make sure all needed info is already in db.
113     $DB->set_field('course_modules', 'instance', $data->id, array('id' => $cmid));
114     $context = context_module::instance($cmid);
115     $imscp = $DB->get_record('imscp', array('id' => $data->id), '*', MUST_EXIST);
117     if (!empty($data->package)) {
118         // Save uploaded files to 'backup' filearea.
119         $fs = get_file_storage();
120         $fs->delete_area_files($context->id, 'mod_imscp', 'backup', 1);
121         file_save_draft_area_files($data->package, $context->id, 'mod_imscp', 'backup',
122             1, array('subdirs' => 0, 'maxfiles' => 1));
123         // Get filename of zip that was uploaded.
124         $files = $fs->get_area_files($context->id, 'mod_imscp', 'backup', 1, '', false);
125         if ($files) {
126             // Extract package content to 'content' filearea.
127             $package = reset($files);
128             $packer = get_file_packer('application/zip');
129             $package->extract_to_storage($packer, $context->id, 'mod_imscp', 'content', 1, '/');
130             $structure = imscp_parse_structure($imscp, $context);
131             $imscp->structure = is_array($structure) ? serialize($structure) : null;
132             $DB->update_record('imscp', $imscp);
133         }
134     }
136     $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
137     \core_completion\api::update_completion_date_event($cmid, 'imscp', $data->id, $completiontimeexpected);
139     return $data->id;
142 /**
143  * Update imscp instance.
144  * @param object $data
145  * @param object $mform
146  * @return bool true
147  */
148 function imscp_update_instance($data, $mform) {
149     global $CFG, $DB;
150     require_once("$CFG->dirroot/mod/imscp/locallib.php");
152     $cmid = $data->coursemodule;
154     $data->timemodified = time();
155     $data->id           = $data->instance;
156     $data->structure   = null; // Better reparse structure after each update.
158     $DB->update_record('imscp', $data);
160     $context = context_module::instance($cmid);
161     $imscp = $DB->get_record('imscp', array('id' => $data->id), '*', MUST_EXIST);
163     if (!empty($data->package) && ($draftareainfo = file_get_draft_area_info($data->package)) &&
164             $draftareainfo['filecount']) {
165         $fs = get_file_storage();
167         $imscp->revision++;
168         $DB->update_record('imscp', $imscp);
170         // Get a list of existing packages before adding new package.
171         if ($imscp->keepold > -1) {
172             $packages = $fs->get_area_files($context->id, 'mod_imscp', 'backup', false, "itemid ASC", false);
173         } else {
174             $packages = array();
175         }
177         file_save_draft_area_files($data->package, $context->id, 'mod_imscp', 'backup',
178             $imscp->revision, array('subdirs' => 0, 'maxfiles' => 1));
179         $files = $fs->get_area_files($context->id, 'mod_imscp', 'backup', $imscp->revision, '', false);
180         $package = reset($files);
182         // Purge all extracted content.
183         $fs->delete_area_files($context->id, 'mod_imscp', 'content');
185         // Extract package content.
186         if ($package) {
187             $packer = get_file_packer('application/zip');
188             $package->extract_to_storage($packer, $context->id, 'mod_imscp', 'content', $imscp->revision, '/');
189         }
191         // Cleanup old package files, keep current + keep old.
192         while ($packages and (count($packages) > $imscp->keepold)) {
193             $package = array_shift($packages);
194             $fs->delete_area_files($context->id, 'mod_imscp', 'backup', $package->get_itemid());
195         }
196     }
198     $structure = imscp_parse_structure($imscp, $context);
199     $imscp->structure = is_array($structure) ? serialize($structure) : null;
200     $DB->update_record('imscp', $imscp);
202     $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
203     \core_completion\api::update_completion_date_event($cmid, 'imscp', $imscp->id, $completiontimeexpected);
205     return true;
208 /**
209  * Delete imscp instance.
210  * @param int $id
211  * @return bool true
212  */
213 function imscp_delete_instance($id) {
214     global $DB;
216     if (!$imscp = $DB->get_record('imscp', array('id' => $id))) {
217         return false;
218     }
220     $cm = get_coursemodule_from_instance('imscp', $id);
221     \core_completion\api::update_completion_date_event($cm->id, 'imscp', $id, null);
223     // Note: all context files are deleted automatically.
225     $DB->delete_records('imscp', array('id' => $imscp->id));
227     return true;
230 /**
231  * Lists all browsable file areas
232  *
233  * @package  mod_imscp
234  * @category files
235  * @param stdClass $course course object
236  * @param stdClass $cm course module object
237  * @param stdClass $context context object
238  * @return array
239  */
240 function imscp_get_file_areas($course, $cm, $context) {
241     $areas = array();
243     $areas['content'] = get_string('areacontent', 'imscp');
244     $areas['backup']  = get_string('areabackup', 'imscp');
246     return $areas;
249 /**
250  * File browsing support for imscp module ontent area.
251  *
252  * @package  mod_imscp
253  * @category files
254  * @param stdClass $browser file browser
255  * @param stdClass $areas file areas
256  * @param stdClass $course course object
257  * @param stdClass $cm course module object
258  * @param stdClass $context context object
259  * @param string $filearea file area
260  * @param int $itemid item ID
261  * @param string $filepath file path
262  * @param string $filename file name
263  * @return file_info instance or null if not found
264  */
265 function imscp_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
266     global $CFG, $DB;
268     // Note: imscp_intro handled in file_browser automatically.
270     if (!has_capability('moodle/course:managefiles', $context)) {
271         // No peeking here for students!
272         return null;
273     }
275     if ($filearea !== 'content' and $filearea !== 'backup') {
276         return null;
277     }
279     require_once("$CFG->dirroot/mod/imscp/locallib.php");
281     if (is_null($itemid)) {
282         return new imscp_file_info($browser, $course, $cm, $context, $areas, $filearea, $itemid);
283     }
285     $fs = get_file_storage();
286     $filepath = is_null($filepath) ? '/' : $filepath;
287     $filename = is_null($filename) ? '.' : $filename;
288     if (!$storedfile = $fs->get_file($context->id, 'mod_imscp', $filearea, $itemid, $filepath, $filename)) {
289         return null;
290     }
292     // Do not allow manual modification of any files!
293     $urlbase = $CFG->wwwroot.'/pluginfile.php';
294     return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false); // No writing here!
297 /**
298  * Serves the imscp files.
299  *
300  * @package  mod_imscp
301  * @category files
302  * @param stdClass $course course object
303  * @param stdClass $cm course module object
304  * @param stdClass $context context object
305  * @param string $filearea file area
306  * @param array $args extra arguments
307  * @param bool $forcedownload whether or not force download
308  * @param array $options additional options affecting the file serving
309  * @return bool false if file not found, does not return if found - justsend the file
310  */
311 function imscp_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
312     global $CFG, $DB;
314     if ($context->contextlevel != CONTEXT_MODULE) {
315         return false;
316     }
318     require_login($course, true, $cm);
320     if ($filearea === 'content') {
321         if (!has_capability('mod/imscp:view', $context)) {
322             return false;
323         }
324         $revision = array_shift($args);
325         $fs = get_file_storage();
326         $relativepath = implode('/', $args);
327         if ($relativepath === 'imsmanifest.xml') {
328             if (!has_capability('moodle/course:managefiles', $context)) {
329                 // No stealing of detailed package info.
330                 return false;
331             }
332         }
333         $fullpath = "/$context->id/mod_imscp/$filearea/$revision/$relativepath";
334         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
335             return false;
336         }
338         // Finally send the file.
339         send_stored_file($file, null, 0, $forcedownload, $options);
341     } else if ($filearea === 'backup') {
342         if (!has_capability('moodle/course:managefiles', $context)) {
343             // No stealing of package backups.
344             return false;
345         }
346         $revision = array_shift($args);
347         $fs = get_file_storage();
348         $relativepath = implode('/', $args);
349         $fullpath = "/$context->id/mod_imscp/$filearea/$revision/$relativepath";
350         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
351             return false;
352         }
354         // Finally send the file.
355         send_stored_file($file, null, 0, $forcedownload, $options);
357     } else {
358         return false;
359     }
362 /**
363  * Return a list of page types
364  * @param string $pagetype current page type
365  * @param stdClass $parentcontext Block's parent context
366  * @param stdClass $currentcontext Current context of block
367  * @return array $modulepagetype list
368  */
369 function imscp_page_type_list($pagetype, $parentcontext, $currentcontext) {
370     $modulepagetype = array('mod-imscp-*' => get_string('page-mod-imscp-x', 'imscp'));
371     return $modulepagetype;
374 /**
375  * Export imscp resource contents
376  *
377  * @param  stdClass $cm     Course module object
378  * @param  string $baseurl  Base URL for file downloads
379  * @return array of file content
380  */
381 function imscp_export_contents($cm, $baseurl) {
382     global $DB;
384     $contents = array();
385     $context = context_module::instance($cm->id);
387     $imscp = $DB->get_record('imscp', array('id' => $cm->instance), '*', MUST_EXIST);
389     // We export the IMSCP structure as json encoded string.
390     $structure = array();
391     $structure['type']         = 'content';
392     $structure['filename']     = 'structure';
393     $structure['filepath']     = '/';
394     $structure['filesize']     = 0;
395     $structure['fileurl']      = null;
396     $structure['timecreated']  = $imscp->timemodified;
397     $structure['timemodified'] = $imscp->timemodified;
398     $structure['content']      = json_encode(unserialize($imscp->structure));
399     $structure['sortorder']    = 0;
400     $structure['userid']       = null;
401     $structure['author']       = null;
402     $structure['license']      = null;
403     $contents[] = $structure;
405     // Area files.
406     $fs = get_file_storage();
407     $files = $fs->get_area_files($context->id, 'mod_imscp', 'content', $imscp->revision, 'id ASC', false);
408     foreach ($files as $fileinfo) {
409         $file = array();
410         $file['type']         = 'file';
411         $file['filename']     = $fileinfo->get_filename();
412         $file['filepath']     = $fileinfo->get_filepath();
413         $file['filesize']     = $fileinfo->get_filesize();
414         $file['fileurl']      = moodle_url::make_webservice_pluginfile_url(
415                                     $context->id, 'mod_imscp', 'content', $imscp->revision,
416                                     $fileinfo->get_filepath(), $fileinfo->get_filename())->out(false);
417         $file['timecreated']  = $fileinfo->get_timecreated();
418         $file['timemodified'] = $fileinfo->get_timemodified();
419         $file['sortorder']    = $fileinfo->get_sortorder();
420         $file['userid']       = $fileinfo->get_userid();
421         $file['author']       = $fileinfo->get_author();
422         $file['license']      = $fileinfo->get_license();
423         $file['mimetype']     = $fileinfo->get_mimetype();
424         $file['isexternalfile'] = $fileinfo->is_external_file();
425         if ($file['isexternalfile']) {
426             $file['repositorytype'] = $fileinfo->get_repository_type();
427         }
428         $contents[] = $file;
429     }
431     return $contents;
434 /**
435  * Mark the activity completed (if required) and trigger the course_module_viewed event.
436  *
437  * @param  stdClass $imscp   imscp object
438  * @param  stdClass $course     course object
439  * @param  stdClass $cm         course module object
440  * @param  stdClass $context    context object
441  * @since Moodle 3.0
442  */
443 function imscp_view($imscp, $course, $cm, $context) {
445     // Trigger course_module_viewed event.
446     $params = array(
447         'context' => $context,
448         'objectid' => $imscp->id
449     );
451     $event = \mod_imscp\event\course_module_viewed::create($params);
452     $event->add_record_snapshot('course_modules', $cm);
453     $event->add_record_snapshot('course', $course);
454     $event->add_record_snapshot('imscp', $imscp);
455     $event->trigger();
457     // Completion.
458     $completion = new completion_info($course);
459     $completion->set_module_viewed($cm);
462 /**
463  * Check if the module has any update that affects the current user since a given time.
464  *
465  * @param  cm_info $cm course module data
466  * @param  int $from the time to check updates from
467  * @param  array $filter  if we need to check only specific updates
468  * @return stdClass an object with the different type of areas indicating if they were updated or not
469  * @since Moodle 3.2
470  */
471 function imscp_check_updates_since(cm_info $cm, $from, $filter = array()) {
472     $updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
473     return $updates;
476 /**
477  * Handles creating actions for events.
478  *
479  * @param calendar_event $event
480  * @param \core_calendar\action_factory $factory
481  * @return \core_calendar\local\event\value_objects\action|\core_calendar\local\interfaces\action_interface|null
482  */
483 function mod_imscp_core_calendar_provide_event_action(calendar_event $event,
484                                                       \core_calendar\action_factory $factory) {
485     $cm = get_fast_modinfo($event->courseid)->instances['imscp'][$event->instance];
487     $course = new stdClass();
488     $course->id = $event->courseid;
489     $completion = new \completion_info($course);
491     $completiondata = $completion->get_data($cm, false);
493     if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
494         return null;
495     }
497     return $factory->create_instance(
498         get_string('view'),
499         new \moodle_url('/mod/imscp/view.php', ['id' => $cm->id]),
500         1,
501         true
502     );