/** * Sets up SCORM 1.2/2004 packages using the manifest file. * Called whenever SCORM changes * @param object $scorm instance - fields are updated and changes saved into database * @param stored_file|string $manifest - path to manifest file or stored_file. * @return bool */ function scorm_parse_scorm(&$scorm, $manifest) { global $CFG, $DB; // load manifest into string if ($manifest instanceof stored_file) { $xmltext = $manifest->get_content(); } else { require_once "{$CFG->libdir}/filelib.php"; $xmltext = download_file_content($manifest); } $defaultorgid = 0; $firstinorg = 0; $pattern = '/&(?!\\w{2,6};)/'; $replacement = '&'; $xmltext = preg_replace($pattern, $replacement, $xmltext); $objXML = new xml2Array(); $manifests = $objXML->parse($xmltext); $scoes = new stdClass(); $scoes->version = ''; $scoes = scorm_get_manifest($manifests, $scoes); $newscoes = array(); $sortorder = 0; if (count($scoes->elements) > 0) { $olditems = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id)); foreach ($scoes->elements as $manifest => $organizations) { foreach ($organizations as $organization => $items) { foreach ($items as $identifier => $item) { $sortorder++; // This new db mngt will support all SCORM future extensions $newitem = new stdClass(); $newitem->scorm = $scorm->id; $newitem->manifest = $manifest; $newitem->organization = $organization; $newitem->sortorder = $sortorder; $standarddatas = array('parent', 'identifier', 'launch', 'scormtype', 'title'); foreach ($standarddatas as $standarddata) { if (isset($item->{$standarddata})) { $newitem->{$standarddata} = $item->{$standarddata}; } else { $newitem->{$standarddata} = ''; } } if (!empty($defaultorgid) && !empty($scoes->defaultorg) && empty($firstinorg) && $newitem->parent == $scoes->defaultorg) { $firstinorg = $sortorder; } if (!empty($olditems) && ($olditemid = scorm_array_search('identifier', $newitem->identifier, $olditems))) { $newitem->id = $olditemid; // Update the Sco sortorder but keep id so that user tracks are kept against the same ids. $DB->update_record('scorm_scoes', $newitem); $id = $olditemid; // Remove all old data so we don't duplicate it. $DB->delete_records('scorm_scoes_data', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_objective', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_mapinfo', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_ruleconds', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_rulecond', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_rolluprule', array('scoid' => $olditemid)); $DB->delete_records('scorm_seq_rolluprulecond', array('scoid' => $olditemid)); // Now remove this SCO from the olditems object as we have dealt with it. unset($olditems[$olditemid]); } else { // Insert the new SCO, and retain the link between the old and new for later adjustment $id = $DB->insert_record('scorm_scoes', $newitem); } $newscoes[$id] = $newitem; // Save this sco in memory so we can use it later. if ($optionaldatas = scorm_optionals_data($item, $standarddatas)) { $data = new stdClass(); $data->scoid = $id; foreach ($optionaldatas as $optionaldata) { if (isset($item->{$optionaldata})) { $data->name = $optionaldata; $data->value = $item->{$optionaldata}; $dataid = $DB->insert_record('scorm_scoes_data', $data); } } } if (isset($item->sequencingrules)) { foreach ($item->sequencingrules as $sequencingrule) { $rule = new stdClass(); $rule->scoid = $id; $rule->ruletype = $sequencingrule->type; $rule->conditioncombination = $sequencingrule->conditioncombination; $rule->action = $sequencingrule->action; $ruleid = $DB->insert_record('scorm_seq_ruleconds', $rule); if (isset($sequencingrule->ruleconditions)) { foreach ($sequencingrule->ruleconditions as $rulecondition) { $rulecond = new stdClass(); $rulecond->scoid = $id; $rulecond->ruleconditionsid = $ruleid; $rulecond->referencedobjective = $rulecondition->referencedobjective; $rulecond->measurethreshold = $rulecondition->measurethreshold; $rulecond->operator = $rulecondition->operator; $rulecond->cond = $rulecondition->cond; $rulecondid = $DB->insert_record('scorm_seq_rulecond', $rulecond); } } } } if (isset($item->rolluprules)) { foreach ($item->rolluprules as $rolluprule) { $rollup = new stdClass(); $rollup->scoid = $id; $rollup->childactivityset = $rolluprule->childactivityset; $rollup->minimumcount = $rolluprule->minimumcount; $rollup->minimumpercent = $rolluprule->minimumpercent; $rollup->rollupruleaction = $rolluprule->rollupruleaction; $rollup->conditioncombination = $rolluprule->conditioncombination; $rollupruleid = $DB->insert_record('scorm_seq_rolluprule', $rollup); if (isset($rollup->conditions)) { foreach ($rollup->conditions as $condition) { $cond = new stdClass(); $cond->scoid = $rollup->scoid; $cond->rollupruleid = $rollupruleid; $cond->operator = $condition->operator; $cond->cond = $condition->cond; $conditionid = $DB->insert_record('scorm_seq_rolluprulecond', $cond); } } } } if (isset($item->objectives)) { foreach ($item->objectives as $objective) { $obj = new stdClass(); $obj->scoid = $id; $obj->primaryobj = $objective->primaryobj; $obj->satisfiedbumeasure = $objective->satisfiedbymeasure; $obj->objectiveid = $objective->objectiveid; $obj->minnormalizedmeasure = trim($objective->minnormalizedmeasure); $objectiveid = $DB->insert_record('scorm_seq_objective', $obj); if (isset($objective->mapinfos)) { foreach ($objective->mapinfos as $objmapinfo) { $mapinfo = new stdClass(); $mapinfo->scoid = $id; $mapinfo->objectiveid = $objectiveid; $mapinfo->targetobjectiveid = $objmapinfo->targetobjectiveid; $mapinfo->readsatisfiedstatus = $objmapinfo->readsatisfiedstatus; $mapinfo->writesatisfiedstatus = $objmapinfo->writesatisfiedstatus; $mapinfo->readnormalizedmeasure = $objmapinfo->readnormalizedmeasure; $mapinfo->writenormalizedmeasure = $objmapinfo->writenormalizedmeasure; $mapinfoid = $DB->insert_record('scorm_seq_mapinfo', $mapinfo); } } } } if (empty($defaultorgid) && (empty($scoes->defaultorg) || $scoes->defaultorg == $identifier)) { $defaultorgid = $id; } } } } if (!empty($olditems)) { foreach ($olditems as $olditem) { $DB->delete_records('scorm_scoes', array('id' => $olditem->id)); $DB->delete_records('scorm_scoes_data', array('scoid' => $olditem->id)); $DB->delete_records('scorm_scoes_track', array('scoid' => $olditem->id)); $DB->delete_records('scorm_seq_objective', array('scoid' => $olditem->id)); $DB->delete_records('scorm_seq_mapinfo', array('scoid' => $olditem->id)); $DB->delete_records('scorm_seq_ruleconds', array('scoid' => $olditem->id)); $DB->delete_records('scorm_seq_rulecond', array('scoid' => $olditem->id)); $DB->delete_records('scorm_seq_rolluprule', array('scoid' => $olditem->id)); $DB->delete_records('scorm_seq_rolluprulecond', array('scoid' => $olditem->id)); } } if (empty($scoes->version)) { $scoes->version = 'SCORM_1.2'; } $DB->set_field('scorm', 'version', $scoes->version, array('id' => $scorm->id)); $scorm->version = $scoes->version; } $scorm->launch = 0; // Check launch sco is valid. if (!empty($defaultorgid) && isset($newscoes[$defaultorgid]) && !empty($newscoes[$defaultorgid]->launch)) { // Launch param is valid - do nothing. $scorm->launch = $defaultorgid; } else { if (!empty($defaultorgid) && isset($newscoes[$defaultorgid]) && empty($newscoes[$defaultorgid]->launch)) { // The launch is probably the default org so we need to find the first launchable item inside this org. $sqlselect = 'scorm = ? AND sortorder >= ? AND ' . $DB->sql_isnotempty('scorm_scoes', 'launch', false, true); // We use get_records here as we need to pass a limit in the query that works cross db. $scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id, $firstinorg), 'sortorder', 'id', 0, 1); if (!empty($scoes)) { $sco = reset($scoes); // We only care about the first record - the above query only returns one. $scorm->launch = $sco->id; } } } if (empty($scorm->launch)) { // No valid Launch is specified - find the first launchable sco instead. $sqlselect = 'scorm = ? AND ' . $DB->sql_isnotempty('scorm_scoes', 'launch', false, true); // We use get_records here as we need to pass a limit in the query that works cross db. $scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id), 'sortorder', 'id', 0, 1); if (!empty($scoes)) { $sco = reset($scoes); // We only care about the first record - the above query only returns one. $scorm->launch = $sco->id; } } return true; }
/** * Handles the sending of file data to the user's browser, including support for * byteranges etc. * * @category files * @global stdClass $CFG * @global stdClass $COURSE * @global moodle_session $SESSION * @param stored_file $stored_file local file object * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours) * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin * @param string $filename Override filename * @param bool $dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks. * if this is passed as true, ignore_user_abort is called. if you don't want your processing to continue on cancel, * you must detect this case when control is returned using connection_aborted. Please not that session is closed * and should not be reopened. * @return null script execution stopped unless $dontdie is true */ function send_stored_file($stored_file, $lifetime = 86400, $filter = 0, $forcedownload = false, $filename = null, $dontdie = false) { global $CFG, $COURSE, $SESSION; if (!$stored_file or $stored_file->is_directory()) { // nothing to serve if ($dontdie) { return; } die; } if ($dontdie) { ignore_user_abort(true); } session_get_instance()->write_close(); // unlock session during fileserving // Use given MIME type if specified, otherwise guess it using mimeinfo. // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O // only Firefox saves all files locally before opening when content-disposition: attachment stated $filename = is_null($filename) ? $stored_file->get_filename() : $filename; $isFF = check_browser_version('Firefox', '1.5'); // only FF > 1.5 properly tested $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' : ($stored_file->get_mimetype() ? $stored_file->get_mimetype() : mimeinfo('type', $filename)); $lastmodified = $stored_file->get_timemodified(); $filesize = $stored_file->get_filesize(); if ($lifetime > 0 && !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { // get unixtime of request header; clip extra junk off first $since = strtotime(preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"])); if ($since && $since >= $lastmodified) { header('HTTP/1.1 304 Not Modified'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $lifetime) . ' GMT'); header('Cache-Control: max-age=' . $lifetime); header('Content-Type: ' . $mimetype); if ($dontdie) { return; } die; } } //do not put '@' before the next header to detect incorrect moodle configurations, //error should be better than "weird" empty lines for admins/users header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastmodified) . ' GMT'); // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup if (check_browser_version('MSIE')) { $filename = rawurlencode($filename); } if ($forcedownload) { header('Content-Disposition: attachment; filename="' . $filename . '"'); } else { header('Content-Disposition: inline; filename="' . $filename . '"'); } if ($lifetime > 0) { header('Cache-Control: max-age=' . $lifetime); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $lifetime) . ' GMT'); header('Pragma: '); if (empty($CFG->disablebyteserving) && $mimetype != 'text/plain' && $mimetype != 'text/html') { header('Accept-Ranges: bytes'); if (!empty($_SERVER['HTTP_RANGE']) && strpos($_SERVER['HTTP_RANGE'], 'bytes=') !== FALSE) { // byteserving stuff - for acrobat reader and download accelerators // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 // inspired by: http://www.coneural.org/florian/papers/04_byteserving.php $ranges = false; if (preg_match_all('/(\\d*)-(\\d*)/', $_SERVER['HTTP_RANGE'], $ranges, PREG_SET_ORDER)) { foreach ($ranges as $key => $value) { if ($ranges[$key][1] == '') { //suffix case $ranges[$key][1] = $filesize - $ranges[$key][2]; $ranges[$key][2] = $filesize - 1; } else { if ($ranges[$key][2] == '' || $ranges[$key][2] > $filesize - 1) { //fix range length $ranges[$key][2] = $filesize - 1; } } if ($ranges[$key][2] != '' && $ranges[$key][2] < $ranges[$key][1]) { //invalid byte-range ==> ignore header $ranges = false; break; } //prepare multipart header $ranges[$key][0] = "\r\n--" . BYTESERVING_BOUNDARY . "\r\nContent-Type: {$mimetype}\r\n"; $ranges[$key][0] .= "Content-Range: bytes {$ranges[$key][1]}-{$ranges[$key][2]}/{$filesize}\r\n\r\n"; } } else { $ranges = false; } if ($ranges) { byteserving_send_file($stored_file->get_content_file_handle(), $mimetype, $ranges, $filesize); } } } else { /// Do not byteserve (disabled, strings, text and html files). header('Accept-Ranges: none'); } } else { // Do not cache files in proxies and browsers if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431 header('Cache-Control: max-age=10'); header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . ' GMT'); header('Pragma: '); } else { //normal http - prevent caching at all cost header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0'); header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . ' GMT'); header('Pragma: no-cache'); } header('Accept-Ranges: none'); // Do not allow byteserving when caching disabled } if (empty($filter)) { if ($mimetype == 'text/plain') { header('Content-Type: Text/plain; charset=utf-8'); //add encoding } else { header('Content-Type: ' . $mimetype); } header('Content-Length: ' . $filesize); //flush the buffers - save memory and disable sid rewrite //this also disables zlib compression prepare_file_content_sending(); // send the contents $stored_file->readfile(); } else { // Try to put the file through filters if ($mimetype == 'text/html') { $options = new stdClass(); $options->noclean = true; $options->nocache = true; // temporary workaround for MDL-5136 $text = $stored_file->get_content(); $text = file_modify_html_header($text); $output = format_text($text, FORMAT_HTML, $options, $COURSE->id); header('Content-Length: ' . strlen($output)); header('Content-Type: text/html'); //flush the buffers - save memory and disable sid rewrite //this also disables zlib compression prepare_file_content_sending(); // send the contents echo $output; } else { if ($mimetype == 'text/plain' and $filter == 1) { // only filter text if filter all files is selected $options = new stdClass(); $options->newlines = false; $options->noclean = true; $text = $stored_file->get_content(); $output = '<pre>' . format_text($text, FORMAT_MOODLE, $options, $COURSE->id) . '</pre>'; header('Content-Length: ' . strlen($output)); header('Content-Type: text/html; charset=utf-8'); //add encoding //flush the buffers - save memory and disable sid rewrite //this also disables zlib compression prepare_file_content_sending(); // send the contents echo $output; } else { // Just send it out raw header('Content-Length: ' . $filesize); header('Content-Type: ' . $mimetype); //flush the buffers - save memory and disable sid rewrite //this also disables zlib compression prepare_file_content_sending(); // send the contents $stored_file->readfile(); } } } if ($dontdie) { return; } die; //no more chars to output!!! }
/** * @param stored_file $file * @param string $encoding * @param string $delimiter * @param context $defaultcontext * @return array */ protected function process_upload_file($file, $encoding, $delimiter, $defaultcontext) { global $CFG, $DB; require_once $CFG->libdir . '/csvlib.class.php'; $cohorts = array(0 => array('errors' => array(), 'warnings' => array(), 'data' => array())); // Read and parse the CSV file using csv library. $content = $file->get_content(); if (!$content) { $cohorts[0]['errors'][] = new lang_string('csvemptyfile', 'error'); return $cohorts; } $uploadid = csv_import_reader::get_new_iid('uploadcohort'); $cir = new csv_import_reader($uploadid, 'uploadcohort'); $readcount = $cir->load_csv_content($content, $encoding, $delimiter); unset($content); if (!$readcount) { $cohorts[0]['errors'][] = get_string('csvloaderror', 'error', $cir->get_error()); return $cohorts; } $columns = $cir->get_columns(); // Check that columns include 'name' and warn about extra columns. $allowedcolumns = array('contextid', 'name', 'idnumber', 'description', 'descriptionformat', 'visible'); $additionalcolumns = array('context', 'category', 'category_id', 'category_idnumber', 'category_path'); $displaycolumns = array(); $extracolumns = array(); $columnsmapping = array(); foreach ($columns as $i => $columnname) { $columnnamelower = preg_replace('/ /', '', core_text::strtolower($columnname)); $columnsmapping[$i] = null; if (in_array($columnnamelower, $allowedcolumns)) { $displaycolumns[$columnnamelower] = $columnname; $columnsmapping[$i] = $columnnamelower; } else { if (in_array($columnnamelower, $additionalcolumns)) { $columnsmapping[$i] = $columnnamelower; } else { $extracolumns[] = $columnname; } } } if (!in_array('name', $columnsmapping)) { $cohorts[0]['errors'][] = new lang_string('namecolumnmissing', 'cohort'); return $cohorts; } if ($extracolumns) { $cohorts[0]['warnings'][] = new lang_string('csvextracolumns', 'cohort', s(join(', ', $extracolumns))); } if (!isset($displaycolumns['contextid'])) { $displaycolumns['contextid'] = 'contextid'; } $cohorts[0]['data'] = $displaycolumns; // Parse data rows. $cir->init(); $rownum = 0; $idnumbers = array(); $haserrors = false; $haswarnings = false; while ($row = $cir->next()) { $rownum++; $cohorts[$rownum] = array('errors' => array(), 'warnings' => array(), 'data' => array()); $hash = array(); foreach ($row as $i => $value) { if ($columnsmapping[$i]) { $hash[$columnsmapping[$i]] = $value; } } $this->clean_cohort_data($hash); $warnings = $this->resolve_context($hash, $defaultcontext); $cohorts[$rownum]['warnings'] = array_merge($cohorts[$rownum]['warnings'], $warnings); if (!empty($hash['idnumber'])) { if (isset($idnumbers[$hash['idnumber']]) || $DB->record_exists('cohort', array('idnumber' => $hash['idnumber']))) { $cohorts[$rownum]['errors'][] = new lang_string('duplicateidnumber', 'cohort'); } $idnumbers[$hash['idnumber']] = true; } if (empty($hash['name'])) { $cohorts[$rownum]['errors'][] = new lang_string('namefieldempty', 'cohort'); } $cohorts[$rownum]['data'] = array_intersect_key($hash, $cohorts[0]['data']); $haserrors = $haserrors || !empty($cohorts[$rownum]['errors']); $haswarnings = $haswarnings || !empty($cohorts[$rownum]['warnings']); } if ($haserrors) { $cohorts[0]['errors'][] = new lang_string('csvcontainserrors', 'cohort'); } if ($haswarnings) { $cohorts[0]['warnings'][] = new lang_string('csvcontainswarnings', 'cohort'); } return $cohorts; }
/** * Shame that this was nicked from gdlib.php and that there isn't a function I could have used from there. * Creates a resized version of image and stores copy in file area * * @param context $context * @param string $component * @param string filearea * @param int $itemid * @param stored_file $originalfile * @param int $newwidth; * @param int $newheight; * @return stored_file */ public static function resize(\stored_file $originalfile, $resizefilename = false, $newwidth = false, $newheight = false, $jpgquality = 90) { if ($resizefilename === false) { $resizefilename = $originalfile->get_filename(); } if (!$newwidth && !$newheight) { return false; } $contextid = $originalfile->get_contextid(); $component = $originalfile->get_component(); $filearea = $originalfile->get_filearea(); $itemid = $originalfile->get_itemid(); $imageinfo = (object) $originalfile->get_imageinfo(); $imagefnc = ''; if (empty($imageinfo)) { return false; } // Create temporary image for processing. $tmpimage = tempnam(sys_get_temp_dir(), 'tmpimg'); \file_put_contents($tmpimage, $originalfile->get_content()); if (!$newheight) { $m = $imageinfo->height / $imageinfo->width; // Multiplier to work out $newheight. $newheight = $newwidth * $m; } else { if (!$newwidth) { $m = $imageinfo->width / $imageinfo->height; // Multiplier to work out $newwidth. $newwidth = $newheight * $m; } } $t = null; switch ($imageinfo->mimetype) { case 'image/gif': if (\function_exists('imagecreatefromgif')) { $im = \imagecreatefromgif($tmpimage); } else { \debugging('GIF not supported on this server'); unlink($tmpimage); return false; } // Guess transparent colour from GIF. $transparent = \imagecolortransparent($im); if ($transparent != -1) { $t = \imagecolorsforindex($im, $transparent); } break; case 'image/jpeg': if (\function_exists('imagecreatefromjpeg')) { $im = \imagecreatefromjpeg($tmpimage); } else { \debugging('JPEG not supported on this server'); unlink($tmpimage); return false; } // If the user uploads a jpeg them we should process as a jpeg if possible. if (\function_exists('imagejpeg')) { $imagefnc = 'imagejpeg'; $filters = null; // Not used. $quality = $jpgquality; } else { if (\function_exists('imagepng')) { $imagefnc = 'imagepng'; $filters = PNG_NO_FILTER; $quality = 1; } else { \debugging('Jpeg and png not supported on this server, please fix server configuration'); unlink($tmpimage); return false; } } break; case 'image/png': if (\function_exists('imagecreatefrompng')) { $im = \imagecreatefrompng($tmpimage); } else { \debugging('PNG not supported on this server'); unlink($tmpimage); return false; } break; default: unlink($tmpimage); return false; } unlink($tmpimage); // The default for all images other than jpegs is to try imagepng first. if (empty($imagefnc)) { if (\function_exists('imagepng')) { $imagefnc = 'imagepng'; $filters = PNG_NO_FILTER; $quality = 1; } else { if (\function_exists('imagejpeg')) { $imagefnc = 'imagejpeg'; $filters = null; // Not used. $quality = $jpgquality; } else { \debugging('Jpeg and png not supported on this server, please fix server configuration'); return false; } } } if (\function_exists('imagecreatetruecolor')) { $newimage = \imagecreatetruecolor($newwidth, $newheight); if ($imageinfo->mimetype != 'image/jpeg' and $imagefnc === 'imagepng') { if ($t) { // Transparent GIF hacking... $transparentcolour = \imagecolorallocate($newimage, $t['red'], $t['green'], $t['blue']); \imagecolortransparent($newimage, $transparentcolour); } \imagealphablending($newimage, false); $color = \imagecolorallocatealpha($newimage, 0, 0, 0, 127); \imagefill($newimage, 0, 0, $color); \imagesavealpha($newimage, true); } } else { $newimage = \imagecreate($newwidth, $newheight); } \imagecopybicubic($newimage, $im, 0, 0, 0, 0, $newwidth, $newheight, $imageinfo->width, $imageinfo->height); $fs = \get_file_storage(); $newimageparams = array('contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => $itemid, 'filepath' => '/'); \ob_start(); if (!$imagefnc($newimage, null, $quality, $filters)) { return false; } $data = \ob_get_clean(); \imagedestroy($newimage); $newimageparams['filename'] = $resizefilename; if ($resizefilename == $originalfile->get_filename()) { $originalfile->delete(); } $file1 = $fs->create_file_from_string($newimageparams, $data); return $file1; }