From b071498f1bc45b6d7f3afe887a67b1100d3efc2e Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Tue, 17 Apr 2012 14:56:14 +0200 Subject: [PATCH 01/15] MDL-32471 pluginfile.php accepts optional parameter 'preview' --- lib/filelib.php | 3 ++- pluginfile.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/filelib.php b/lib/filelib.php index 1d0b01ccd2f0d..29111c8a0211e 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -3211,9 +3211,10 @@ public function get_extensions($types) { * * @param string $relativepath * @param bool $forcedownload + * @param null|string $preview the preview mode, defaults to serving the original file * @todo MDL-31088 file serving improments */ -function file_pluginfile($relativepath, $forcedownload) { +function file_pluginfile($relativepath, $forcedownload, $preview = null) { global $DB, $CFG, $USER; // relative path must start with '/' if (!$relativepath) { diff --git a/pluginfile.php b/pluginfile.php index fd187ceb9edb9..8ace6bd0f88da 100644 --- a/pluginfile.php +++ b/pluginfile.php @@ -33,5 +33,6 @@ $relativepath = get_file_argument(); $forcedownload = optional_param('forcedownload', 0, PARAM_BOOL); +$preview = optional_param('preview', null, PARAM_ALPHANUM); -file_pluginfile($relativepath, $forcedownload); +file_pluginfile($relativepath, $forcedownload, $preview); From 796495fed29f12e4a81bd406558d8eeffd0e64ac Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Tue, 17 Apr 2012 15:05:33 +0200 Subject: [PATCH 02/15] MDL-32471 changing the interface of the send_stored_file() --- lib/filelib.php | 78 ++++++++++++++++++++++++---------------- lib/portfolio/plugin.php | 4 +-- lib/upgrade.txt | 3 ++ 3 files changed, 52 insertions(+), 33 deletions(-) diff --git a/lib/filelib.php b/lib/filelib.php index 29111c8a0211e..eebedb09b560e 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -1950,6 +1950,14 @@ function send_file($path, $filename, $lifetime = 'default' , $filter=0, $pathiss * Handles the sending of file data to the user's browser, including support for * byteranges etc. * + * The $options parameter supports the following keys: + * (string|null) preview - send the preview of the file (e.g. "thumb" for a thumbnail) + * (string|null) filename - overrides the implicit filename + * (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. + * * @category files * @global stdClass $CFG * @global stdClass $COURSE @@ -1958,16 +1966,24 @@ function send_file($path, $filename, $lifetime = 'default' , $filter=0, $pathiss * @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 + * @param array $options additional options affecting the file serving + * @return null script execution stopped unless $options['dontdie'] is true */ -function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, $filename=null, $dontdie=false) { +function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options=array()) { global $CFG, $COURSE, $SESSION; + if (empty($options['filename'])) { + $filename = null; + } else { + $filename = $options['filename']; + } + + if (empty($options['dontdie'])) { + $dontdie = false; + } else { + $dontdie = true; + } + if (!$stored_file or $stored_file->is_directory()) { // nothing to serve if ($dontdie) { @@ -3290,7 +3306,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { send_file_not_found(); } - send_stored_file($file, 10*60, 0, true); // download MUST be forced - security! + send_stored_file($file, 10*60, 0, true, array('preview' => $preview)); // download MUST be forced - security! // ======================================================================================================================== } else if ($component === 'grade') { @@ -3307,7 +3323,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else if ($filearea === 'feedback' and $context->contextlevel == CONTEXT_COURSE) { //TODO: nobody implemented this yet in grade edit form!! @@ -3324,7 +3340,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else { send_file_not_found(); } @@ -3345,7 +3361,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, true); + send_stored_file($file, 60*60, 0, true, array('preview' => $preview)); } else { send_file_not_found(); @@ -3377,7 +3393,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_USER) { @@ -3405,7 +3421,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_COURSE) { @@ -3452,7 +3468,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else { send_file_not_found(); @@ -3489,7 +3505,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { $theme = theme_config::load($themename); redirect($theme->pix_url('u/'.$filename, 'moodle')); } - send_stored_file($file, 60*60*24); // enable long caching, there are many images on each page + send_stored_file($file, 60*60*24, 0, false, array('preview' => $preview)); // enable long caching, there are many images on each page } else if ($filearea === 'private' and $context->contextlevel == CONTEXT_USER) { require_login(); @@ -3509,7 +3525,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, true); // must force download - security! + send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security! } else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_USER) { @@ -3556,7 +3572,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, true); // must force download - security! + send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security! } else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_COURSE) { $userid = (int)array_shift($args); @@ -3594,7 +3610,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, true); // must force download - security! + send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security! } else if ($filearea === 'backup' and $context->contextlevel == CONTEXT_USER) { require_login(); @@ -3615,7 +3631,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, true); // must force download - security! + send_stored_file($file, 0, 0, true, array('preview' => $preview)); // must force download - security! } else { send_file_not_found(); @@ -3640,7 +3656,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else { send_file_not_found(); } @@ -3663,7 +3679,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else if ($filearea === 'section') { if ($CFG->forcelogin) { @@ -3692,7 +3708,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else { send_file_not_found(); @@ -3724,7 +3740,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else if ($filearea === 'icon') { $filename = array_pop($args); @@ -3739,7 +3755,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60); + send_stored_file($file, 60*60, 0, false, array('preview' => $preview)); } else { send_file_not_found(); @@ -3764,7 +3780,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else { send_file_not_found(); @@ -3783,7 +3799,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, $forcedownload); + send_stored_file($file, 0, 0, $forcedownload, array('preview' => $preview)); } else if ($filearea === 'section' and $context->contextlevel == CONTEXT_COURSE) { require_login($course); @@ -3798,7 +3814,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else if ($filearea === 'activity' and $context->contextlevel == CONTEXT_MODULE) { require_login($course, false, $cm); @@ -3811,7 +3827,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } else if ($filearea === 'automated' and $context->contextlevel == CONTEXT_COURSE) { // Backup files that were generated by the automated backup systems. @@ -3826,7 +3842,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, $forcedownload); + send_stored_file($file, 0, 0, $forcedownload, array('preview' => $preview)); } else { send_file_not_found(); @@ -3872,7 +3888,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, array('preview' => $preview)); } // ======================================================================================================================== @@ -3906,7 +3922,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400; // finally send the file - send_stored_file($file, $lifetime, 0); + send_stored_file($file, $lifetime, 0, false, array('preview' => $preview)); } $filefunction = $component.'_pluginfile'; diff --git a/lib/portfolio/plugin.php b/lib/portfolio/plugin.php index d86d1ead59198..6c32ffc64204c 100644 --- a/lib/portfolio/plugin.php +++ b/lib/portfolio/plugin.php @@ -822,8 +822,8 @@ public function send_file() { if (!($file instanceof stored_file)) { throw new portfolio_export_exception($this->get('exporter'), 'filenotfound', 'portfolio'); } - // the last 'true' on the end of this means don't die(); afterwards, so we can clean up. - send_stored_file($file, 0, 0, true, null, true); + // don't die(); afterwards, so we can clean up. + send_stored_file($file, 0, 0, true, array('dontdie' => true)); $this->get('exporter')->log_transfer(); } diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 80af326ce2f97..ffe792af5f194 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -11,6 +11,9 @@ Note: * DDL and DML methods which were deprecated in 2.0 have now been removed, they will no longer produce debug messages and will produce fatal errors +API changes: +* send_stored_file() has changed its interface + === 2.2 === removed unused libraries: From f0f4fff900cbad03abcb5a227e89376c33192846 Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Wed, 18 Apr 2012 00:32:07 +0200 Subject: [PATCH 03/15] MDL-32471 fixing the coding style in lib/gdlib.php The GD functions are listed in lowercase in the PHP manual. So they should be using this case in our code, too (otherwise, doc autolinking tools may not work). I did not remove the underscore from the parameters names intentionally to have the function interface compatible with the one documented in the PHP manual. See http://www.php.net/manual/en/ref.image.php < --- lib/gdlib.php | 107 +++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/lib/gdlib.php b/lib/gdlib.php index 3b026b79d785a..43dde392a073a 100644 --- a/lib/gdlib.php +++ b/lib/gdlib.php @@ -27,61 +27,62 @@ defined('MOODLE_INTERNAL') || die(); /** + * Copies a rectangular portion of the source image to another rectangle in the destination image * - * long description - * @global object - * @param object $dst_img - * @param object $src_img - * @param int $dst_x - * @param int $dst_y - * @param int $src_x - * @param int $src_y - * @param int $dst_w - * @param int $dst_h - * @param int $src_w - * @param int $src_h - * @return bool - * @todo Finish documenting this function + * This function calls imagecopyresampled() if it is available and GD version is 2 at least. + * Otherwise it reimplements the same behaviour. See the PHP manual page for more info. + * + * @link http://php.net/manual/en/function.imagecopyresampled.php + * @param resource $dst_img the destination GD image resource + * @param resource $src_img the source GD image resource + * @param int $dst_x vthe X coordinate of the upper left corner in the destination image + * @param int $dst_y the Y coordinate of the upper left corner in the destination image + * @param int $src_x the X coordinate of the upper left corner in the source image + * @param int $src_y the Y coordinate of the upper left corner in the source image + * @param int $dst_w the width of the destination rectangle + * @param int $dst_h the height of the destination rectangle + * @param int $src_w the width of the source rectangle + * @param int $src_h the height of the source rectangle + * @return bool tru on success, false otherwise */ -function ImageCopyBicubic($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) { - +function imagecopybicubic($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) { global $CFG; - if (function_exists('ImageCopyResampled') and $CFG->gdversion >= 2) { - return ImageCopyResampled($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, + if (function_exists('imagecopyresampled') and $CFG->gdversion >= 2) { + return imagecopyresampled($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h); } $totalcolors = imagecolorstotal($src_img); for ($i=0; $i<$totalcolors; $i++) { - if ($colors = ImageColorsForIndex($src_img, $i)) { - ImageColorAllocate($dst_img, $colors['red'], $colors['green'], $colors['blue']); + if ($colors = imagecolorsforindex($src_img, $i)) { + imagecolorallocate($dst_img, $colors['red'], $colors['green'], $colors['blue']); } } - $scaleX = ($src_w - 1) / $dst_w; - $scaleY = ($src_h - 1) / $dst_h; + $scalex = ($src_w - 1) / $dst_w; + $scaley = ($src_h - 1) / $dst_h; - $scaleX2 = $scaleX / 2.0; - $scaleY2 = $scaleY / 2.0; + $scalex2 = $scalex / 2.0; + $scaley2 = $scaley / 2.0; for ($j = 0; $j < $dst_h; $j++) { - $sY = $j * $scaleY; + $sy = $j * $scaley; for ($i = 0; $i < $dst_w; $i++) { - $sX = $i * $scaleX; + $sx = $i * $scalex; - $c1 = ImageColorsForIndex($src_img,ImageColorAt($src_img,(int)$sX,(int)$sY+$scaleY2)); - $c2 = ImageColorsForIndex($src_img,ImageColorAt($src_img,(int)$sX,(int)$sY)); - $c3 = ImageColorsForIndex($src_img,ImageColorAt($src_img,(int)$sX+$scaleX2,(int)$sY+$scaleY2)); - $c4 = ImageColorsForIndex($src_img,ImageColorAt($src_img,(int)$sX+$scaleX2,(int)$sY)); + $c1 = imagecolorsforindex($src_img, imagecolorat($src_img, (int)$sx, (int)$sy + $scaley2)); + $c2 = imagecolorsforindex($src_img, imagecolorat($src_img, (int)$sx, (int)$sy)); + $c3 = imagecolorsforindex($src_img, imagecolorat($src_img, (int)$sx + $scalex2, (int)$sy + $scaley2)); + $c4 = imagecolorsforindex($src_img, imagecolorat($src_img, (int)$sx + $scalex2, (int)$sy)); $red = (int) (($c1['red'] + $c2['red'] + $c3['red'] + $c4['red']) / 4); $green = (int) (($c1['green'] + $c2['green'] + $c3['green'] + $c4['green']) / 4); $blue = (int) (($c1['blue'] + $c2['blue'] + $c3['blue'] + $c4['blue']) / 4); - $color = ImageColorClosest ($dst_img, $red, $green, $blue); - ImageSetPixel ($dst_img, $i + $dst_x, $j + $dst_y, $color); + $color = imagecolorclosest($dst_img, $red, $green, $blue); + imagesetpixel($dst_img, $i + $dst_x, $j + $dst_y, $color); } } } @@ -106,7 +107,7 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil return false; } - $imageinfo = GetImageSize($originalfile); + $imageinfo = getimagesize($originalfile); if (empty($imageinfo)) { return false; @@ -119,24 +120,24 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil switch ($image->type) { case IMAGETYPE_GIF: - if (function_exists('ImageCreateFromGIF')) { - $im = ImageCreateFromGIF($originalfile); + if (function_exists('imagecreatefromgif')) { + $im = imagecreatefromgif($originalfile); } else { debugging('GIF not supported on this server'); return false; } break; case IMAGETYPE_JPEG: - if (function_exists('ImageCreateFromJPEG')) { - $im = ImageCreateFromJPEG($originalfile); + if (function_exists('imagecreatefromjpeg')) { + $im = imagecreatefromjpeg($originalfile); } else { debugging('JPEG not supported on this server'); return false; } break; case IMAGETYPE_PNG: - if (function_exists('ImageCreateFromPNG')) { - $im = ImageCreateFromPNG($originalfile); + if (function_exists('imagecreatefrompng')) { + $im = imagecreatefrompng($originalfile); } else { debugging('PNG not supported on this server'); return false; @@ -146,13 +147,13 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil return false; } - if (function_exists('ImagePng')) { - $imagefnc = 'ImagePng'; + if (function_exists('imagepng')) { + $imagefnc = 'imagepng'; $imageext = '.png'; $filters = PNG_NO_FILTER; $quality = 1; - } else if (function_exists('ImageJpeg')) { - $imagefnc = 'ImageJpeg'; + } else if (function_exists('imagejpeg')) { + $imagefnc = 'imagejpeg'; $imageext = '.jpg'; $filters = null; // not used $quality = 90; @@ -161,10 +162,10 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil return false; } - if (function_exists('ImageCreateTrueColor') and $CFG->gdversion >= 2) { - $im1 = ImageCreateTrueColor(100,100); - $im2 = ImageCreateTrueColor(35,35); - if ($image->type == IMAGETYPE_PNG and $imagefnc === 'ImagePng') { + if (function_exists('imagecreatetruecolor') and $CFG->gdversion >= 2) { + $im1 = imagecreatetruecolor(100, 100); + $im2 = imagecreatetruecolor(35, 35); + if ($image->type == IMAGETYPE_PNG and $imagefnc === 'imagepng') { imagealphablending($im1, false); $color = imagecolorallocatealpha($im1, 0, 0, 0, 127); imagefill($im1, 0, 0, $color); @@ -175,8 +176,8 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil imagesavealpha($im2, true); } } else { - $im1 = ImageCreate(100,100); - $im2 = ImageCreate(35,35); + $im1 = imagecreate(100, 100); + $im2 = imagecreate(35, 35); } $cx = $image->width / 2; @@ -188,8 +189,8 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil $half = floor($image->height / 2.0); } - ImageCopyBicubic($im1, $im, 0, 0, $cx-$half, $cy-$half, 100, 100, $half*2, $half*2); - ImageCopyBicubic($im2, $im, 0, 0, $cx-$half, $cy-$half, 35, 35, $half*2, $half*2); + imagecopybicubic($im1, $im, 0, 0, $cx - $half, $cy - $half, 100, 100, $half * 2, $half * 2); + imagecopybicubic($im2, $im, 0, 0, $cx - $half, $cy - $half, 35, 35, $half * 2, $half * 2); $fs = get_file_storage(); @@ -202,7 +203,7 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil return false; } $data = ob_get_clean(); - ImageDestroy($im1); + imagedestroy($im1); $icon['filename'] = 'f1'.$imageext; $fs->delete_area_files($context->id, $component, $filearea, $itemid); $fs->create_file_from_string($icon, $data); @@ -214,7 +215,7 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil return false; } $data = ob_get_clean(); - ImageDestroy($im2); + imagedestroy($im2); $icon['filename'] = 'f2'.$imageext; $fs->create_file_from_string($icon, $data); From f0a23f5322f51443f42b5b0b1198bb38e3555ad8 Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Wed, 18 Apr 2012 00:37:49 +0200 Subject: [PATCH 04/15] MDL-32471 introducing generate_image_thumbnail() function This helper function uses GD library to generate a thumbnail of the given image file. --- lib/gdlib.php | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/lib/gdlib.php b/lib/gdlib.php index 43dde392a073a..151a722e29a4b 100644 --- a/lib/gdlib.php +++ b/lib/gdlib.php @@ -222,3 +222,88 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil return true; } +/** + * Generates a thumbnail for the given image + * + * If the GD library has at least version 2 and PNG support is available, the returned data + * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function + * returns contents of a JPEG file with black background containing the thumbnail. + * + * @param string $filepath the full path to the original image file + * @param int $width the width of the requested thumbnail + * @param int $height the height of the requested thumbnail + * @return string|bool false if a problem occurs, the thumbnail image data otherwise + */ +function generate_image_thumbnail($filepath, $width, $height) { + global $CFG; + + if (empty($CFG->gdversion) or empty($filepath) or empty($width) or empty($height)) { + return false; + } + + $imageinfo = getimagesize($filepath); + + if (empty($imageinfo)) { + return false; + } + + $originalwidth = $imageinfo[0]; + $originalheight = $imageinfo[1]; + + if (empty($originalwidth) or empty($originalheight)) { + return false; + } + + $original = imagecreatefromstring(file_get_contents($filepath)); + + if (function_exists('imagepng')) { + $imagefnc = 'imagepng'; + $filters = PNG_NO_FILTER; + $quality = 1; + } else if (function_exists('imagejpeg')) { + $imagefnc = 'imagejpeg'; + $filters = null; + $quality = 90; + } else { + debugging('Neither JPEG nor PNG are supported at this server, please fix the system configuration.'); + return false; + } + + if (function_exists('imagecreatetruecolor') and $CFG->gdversion >= 2) { + $thumbnail = imagecreatetruecolor($width, $height); + if ($imagefnc === 'imagepng') { + imagealphablending($thumbnail, false); + imagefill($thumbnail, 0, 0, imagecolorallocatealpha($thumbnail, 0, 0, 0, 127)); + imagesavealpha($thumbnail, true); + } + } else { + $thumbnail = imagecreate($width, $height); + } + + $ratio = min($width / $originalwidth, $height / $originalheight); + + if ($ratio < 1) { + $targetwidth = floor($originalwidth * $ratio); + $targetheight = floor($originalheight * $ratio); + } else { + // do not enlarge the original file if it is smaller than the requested thumbnail size + $targetwidth = $originalwidth; + $targetheight = $originalheight; + } + + $dstx = floor(($width - $targetwidth) / 2); + $dsty = floor(($height - $targetheight) / 2); + + imagecopybicubic($thumbnail, $original, $dstx, $dsty, 0, 0, $targetwidth, $targetheight, $originalwidth, $originalheight); + + ob_start(); + if (!$imagefnc($thumbnail, null, $quality, $filters)) { + ob_end_clean(); + return false; + } + $data = ob_get_clean(); + imagedestroy($original); + imagedestroy($thumbnail); + + return $data; +} From c4d19c5a0714f7615be2df5b7d734cc6454271ee Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Wed, 18 Apr 2012 00:41:01 +0200 Subject: [PATCH 05/15] MDL-32471 introducing file_storage::get_file_preview() method --- lib/filestorage/file_storage.php | 107 +++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php index a60b7c2944ed7..e4fb0ec5b9a0a 100644 --- a/lib/filestorage/file_storage.php +++ b/lib/filestorage/file_storage.php @@ -152,6 +152,113 @@ public function get_file_instance(stdClass $file_record) { return new stored_file($this, $file_record, $this->filedir); } + /** + * Returns an image file that represent the given stored file as a preview + * + * At the moment, only GIF, JPEG and PNG files are supported to have previews. In the + * future, the support for other mimetypes can be added, too (eg. generate an image + * preview of PDF, text documents etc). + * + * @param stored_file $file the file we want to preview + * @param string $mode preview mode, eg. 'thumb' + * @return stored_file|bool false if unable to create the preview, stored file otherwise + */ + public function get_file_preview(stored_file $file, $mode) { + + $context = context_system::instance(); + $path = '/' . trim($mode, '/') . '/'; + $preview = $this->get_file($context->id, 'core', 'preview', 0, $path, $file->get_contenthash()); + + if (!$preview) { + $preview = $this->create_file_preview($file, $mode); + if (!$preview) { + return false; + } + } + + return $preview; + } + + /** + * Generates a preview image for the stored file + * + * @param stored_file $file the file we want to preview + * @param string $mode preview mode, eg. 'thumb' + * @return stored_file|bool the newly created preview file or false + */ + protected function create_file_preview(stored_file $file, $mode) { + + $mimetype = $file->get_mimetype(); + + if ($mimetype == 'image/gif' or $mimetype == 'image/jpeg' or $mimetype == 'image/png') { + // make a preview of the image + $data = $this->create_imagefile_preview($file, $mode); + + } else { + // unable to create the preview of this mimetype yet + return false; + } + + if (empty($data)) { + return false; + } + + // getimagesizefromstring() is available from PHP 5.4 but we need to support + // lower versions, so... + $tmproot = make_temp_directory('thumbnails'); + $tmpfilepath = $tmproot.'/'.$file->get_contenthash().'_thumb'; + file_put_contents($tmpfilepath, $data); + $imageinfo = getimagesize($tmpfilepath); + unlink($tmpfilepath); + + $context = context_system::instance(); + + $record = array( + 'contextid' => $context->id, + 'component' => 'core', + 'filearea' => 'preview', + 'itemid' => 0, + 'filepath' => '/' . trim($mode, '/') . '/', + 'filename' => $file->get_contenthash(), + ); + + if ($imageinfo) { + $record['mimetype'] = $imageinfo['mime']; + } + + return $this->create_file_from_string($record, $data); + } + + /** + * Generates a preview for the stored image file + * + * @param stored_file $file the image we want to preview + * @param string $mode preview mode, eg. 'thumb' + * @return string|bool false if a problem occurs, the thumbnail image data otherwise + */ + protected function create_imagefile_preview(stored_file $file, $mode) { + global $CFG; + require_once($CFG->libdir.'/gdlib.php'); + + $tmproot = make_temp_directory('thumbnails'); + $tmpfilepath = $tmproot.'/'.$file->get_contenthash(); + $file->copy_content_to($tmpfilepath); + + if ($mode == 'tinyicon') { + $data = generate_image_thumbnail($tmpfilepath, 16, 16); + + } else if ($mode == 'thumb') { + $data = generate_image_thumbnail($tmpfilepath, 90, 90); + + } else { + throw new file_exception('storedfileproblem', 'Invalid preview mode requested'); + } + + unlink($tmpfilepath); + + return $data; + } + /** * Fetch file using local file id. * From 82c224ee7a0bd87373ceba3bf5ed9eefe29514fc Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Wed, 18 Apr 2012 00:43:16 +0200 Subject: [PATCH 06/15] MDL-32471 send_stored_file() now supports the preview option If for any reason the preview image can't be generated for the given file (eg we do not support its mimetype yet), the function will send 404 Not Found HTTP header. This will be useful for the lazy loading of the file thumbnails as the JavaScript will simply ignore such response and will not replace the default icon for the file. In the future, a fallback thumbnail generator can be implemented that would generate some sort of default preview for all files (eg using the mimetype icon like some desktop OS do). --- lib/filelib.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/filelib.php b/lib/filelib.php index eebedb09b560e..ea0e4fcd8d698 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -1984,6 +1984,23 @@ function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownl $dontdie = true; } + if (!empty($options['preview'])) { + // replace the file with its preview + $fs = get_file_storage(); + $stored_file = $fs->get_file_preview($stored_file, $options['preview']); + if (!$stored_file) { + // unable to create a preview of the file + send_header_404(); + die(); + } else { + // preview images have fixed cache lifetime and they ignore forced download + // (they are generated by GD and therefore they are considered reasonably safe). + $lifetime = DAYSECS; + $filter = 0; + $forcedownload = false; + } + } + if (!$stored_file or $stored_file->is_directory()) { // nothing to serve if ($dontdie) { From 261cbbacc15ef1732a357d689908c91c15e0617a Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Wed, 18 Apr 2012 15:56:09 +0200 Subject: [PATCH 07/15] MDL-32471 preview thumbnails support for activity modules Activity module's xxx_pluginfile() now accepts the $options parameter that is to be passed to the send_stored_file() function. --- lib/filelib.php | 4 +-- mod/assignment/lib.php | 11 +++++--- .../type/online/assignment.class.php | 5 ++-- mod/assignment/type/upgrade.txt | 7 ++++++ .../type/upload/assignment.class.php | 7 +++--- .../type/uploadsingle/assignment.class.php | 4 +-- mod/data/lib.php | 5 ++-- mod/feedback/lib.php | 5 ++-- mod/folder/lib.php | 5 ++-- mod/forum/lib.php | 6 ++--- mod/glossary/lib.php | 5 ++-- mod/imscp/lib.php | 7 +++--- mod/lesson/lib.php | 5 ++-- mod/page/lib.php | 5 ++-- mod/quiz/lib.php | 10 +++++--- mod/resource/lib.php | 5 ++-- mod/scorm/lib.php | 5 ++-- mod/upgrade.txt | 3 +++ mod/wiki/lib.php | 6 +++-- mod/workshop/form/accumulative/lib.php | 5 ++-- mod/workshop/form/comments/lib.php | 5 ++-- mod/workshop/form/numerrors/lib.php | 5 ++-- mod/workshop/form/rubric/lib.php | 5 ++-- mod/workshop/lib.php | 25 ++++++++++--------- 24 files changed, 95 insertions(+), 60 deletions(-) create mode 100644 mod/assignment/type/upgrade.txt diff --git a/lib/filelib.php b/lib/filelib.php index ea0e4fcd8d698..1d4a019cb36e0 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -3946,10 +3946,10 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { $filefunctionold = $modname.'_pluginfile'; if (function_exists($filefunction)) { // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" - $filefunction($course, $cm, $context, $filearea, $args, $forcedownload); + $filefunction($course, $cm, $context, $filearea, $args, $forcedownload, array('preview' => $preview)); } else if (function_exists($filefunctionold)) { // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" - $filefunctionold($course, $cm, $context, $filearea, $args, $forcedownload); + $filefunctionold($course, $cm, $context, $filearea, $args, $forcedownload, array('preview' => $preview)); } send_file_not_found(); diff --git a/mod/assignment/lib.php b/mod/assignment/lib.php index a6c145eb03d84..52d4a41194cb5 100644 --- a/mod/assignment/lib.php +++ b/mod/assignment/lib.php @@ -1971,11 +1971,15 @@ function email_teachers($submission) { } /** + * Sends a file + * * @param string $filearea * @param array $args + * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ - function send_file($filearea, $args) { + function send_file($filearea, $args, $forcedownload, array $options=array()) { debugging('plugin does not implement file sending', DEBUG_DEVELOPER); return false; } @@ -3079,9 +3083,10 @@ function assignment_get_participants($assignmentid) { * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - just send the file */ -function assignment_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function assignment_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -3098,7 +3103,7 @@ function assignment_pluginfile($course, $cm, $context, $filearea, $args, $forced $assignmentclass = 'assignment_'.$assignment->assignmenttype; $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm, $course); - return $assignmentinstance->send_file($filearea, $args); + return $assignmentinstance->send_file($filearea, $args, $forcedownload, $options); } /** * Checks if a scale is being used by an assignment diff --git a/mod/assignment/type/online/assignment.class.php b/mod/assignment/type/online/assignment.class.php index 0985666d17037..6adf048044f25 100644 --- a/mod/assignment/type/online/assignment.class.php +++ b/mod/assignment/type/online/assignment.class.php @@ -375,7 +375,7 @@ function extend_settings_navigation($node) { } } - public function send_file($filearea, $args) { + public function send_file($filearea, $args, $forcedownload, $options) { global $USER; require_capability('mod/assignment:view', $this->context); @@ -391,7 +391,8 @@ public function send_file($filearea, $args) { } session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, true); + + send_stored_file($file, 60*60, 0, true, $options); } /** diff --git a/mod/assignment/type/upgrade.txt b/mod/assignment/type/upgrade.txt new file mode 100644 index 0000000000000..20a9f6f418818 --- /dev/null +++ b/mod/assignment/type/upgrade.txt @@ -0,0 +1,7 @@ +This file describes changes in the assignment type API and DB structures. +Information provided here is intended especially for developers. + +=== 2.3 === + +API changes: +* send_file() methods now accept $forcedownload and $options parameters diff --git a/mod/assignment/type/upload/assignment.class.php b/mod/assignment/type/upload/assignment.class.php index 0b537d5adb430..f4fee1d2cfca3 100644 --- a/mod/assignment/type/upload/assignment.class.php +++ b/mod/assignment/type/upload/assignment.class.php @@ -614,7 +614,7 @@ function upload_file($mform, $options) { die; } - function send_file($filearea, $args) { + function send_file($filearea, $args, $forcedownload, $options) { global $CFG, $DB, $USER; require_once($CFG->libdir.'/filelib.php'); @@ -638,7 +638,8 @@ function send_file($filearea, $args) { if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { return false; } - send_stored_file($file, 0, 0, true); // download MUST be forced - security! + + send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security! } else if ($filearea === 'response') { $submissionid = (int)array_shift($args); @@ -658,7 +659,7 @@ function send_file($filearea, $args) { if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { return false; } - send_stored_file($file, 0, 0, true); + send_stored_file($file, 0, 0, true, $options); } return false; diff --git a/mod/assignment/type/uploadsingle/assignment.class.php b/mod/assignment/type/uploadsingle/assignment.class.php index a3998d89dd6de..b7b3071a4daa9 100644 --- a/mod/assignment/type/uploadsingle/assignment.class.php +++ b/mod/assignment/type/uploadsingle/assignment.class.php @@ -300,7 +300,7 @@ function portfolio_exportable() { return true; } - function send_file($filearea, $args) { + function send_file($filearea, $args, $forcedownload, $options) { global $CFG, $DB, $USER; require_once($CFG->libdir.'/filelib.php'); @@ -329,7 +329,7 @@ function send_file($filearea, $args) { return false; } - send_stored_file($file, 0, 0, true); // download MUST be forced - security! + send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security! } function extend_settings_navigation($node) { diff --git a/mod/data/lib.php b/mod/data/lib.php index e6737d9bec8a5..d816cc3cff5b6 100644 --- a/mod/data/lib.php +++ b/mod/data/lib.php @@ -2963,9 +2963,10 @@ function mod_data_get_file_info($browser, $areas, $course, $cm, $context, $filea * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - justsend the file */ -function data_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function data_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -3028,7 +3029,7 @@ function data_pluginfile($course, $cm, $context, $filearea, $args, $forcedownloa } // finally send the file - send_stored_file($file, 0, 0, true); // download MUST be forced - security! + send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security! } return false; diff --git a/mod/feedback/lib.php b/mod/feedback/lib.php index 296655975a19c..2e95a1017a807 100644 --- a/mod/feedback/lib.php +++ b/mod/feedback/lib.php @@ -182,9 +182,10 @@ function feedback_update_instance($feedback) { * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - justsend the file */ -function feedback_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function feedback_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; if ($filearea === 'item' or $filearea === 'template') { @@ -273,7 +274,7 @@ function feedback_pluginfile($course, $cm, $context, $filearea, $args, $forcedow } // finally send the file - send_stored_file($file, 0, 0, true); // download MUST be forced - security! + send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security! return false; } diff --git a/mod/folder/lib.php b/mod/folder/lib.php index 9abc5c21376c7..365dd923aabe5 100644 --- a/mod/folder/lib.php +++ b/mod/folder/lib.php @@ -293,9 +293,10 @@ function folder_get_file_info($browser, $areas, $course, $cm, $context, $fileare * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - just send the file */ -function folder_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function folder_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -323,7 +324,7 @@ function folder_pluginfile($course, $cm, $context, $filearea, $args, $forcedownl // finally send the file // for folder module, we force download file all the time - send_stored_file($file, 86400, 0, true); + send_stored_file($file, 86400, 0, true, $options); } /** diff --git a/mod/forum/lib.php b/mod/forum/lib.php index 96fe4781eb564..9ad53b1003c24 100644 --- a/mod/forum/lib.php +++ b/mod/forum/lib.php @@ -4108,9 +4108,10 @@ function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - justsend the file */ -function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -4162,9 +4163,8 @@ function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownlo return false; } - // finally send the file - send_stored_file($file, 0, 0, true); // download MUST be forced - security! + send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security! } /** diff --git a/mod/glossary/lib.php b/mod/glossary/lib.php index a2da16dd75832..3f19d2081cda4 100644 --- a/mod/glossary/lib.php +++ b/mod/glossary/lib.php @@ -1538,9 +1538,10 @@ function mod_glossary_get_file_info($browser, $areas, $course, $cm, $context, $f * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - justsend the file */ -function glossary_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function glossary_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -1590,7 +1591,7 @@ function glossary_pluginfile($course, $cm, $context, $filearea, $args, $forcedow } // finally send the file - send_stored_file($file, 0, 0, true); // download MUST be forced - security! + send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security! } else if ($filearea === 'export') { require_login($course, false, $cm); diff --git a/mod/imscp/lib.php b/mod/imscp/lib.php index 517047a0b79cd..f57a22763eb88 100644 --- a/mod/imscp/lib.php +++ b/mod/imscp/lib.php @@ -337,9 +337,10 @@ function imscp_get_file_info($browser, $areas, $course, $cm, $context, $filearea * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - justsend the file */ -function imscp_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function imscp_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -367,7 +368,7 @@ function imscp_pluginfile($course, $cm, $context, $filearea, $args, $forcedownlo } // finally send the file - send_stored_file($file, 86400, 0, $forcedownload); + send_stored_file($file, 86400, 0, $forcedownload, $options); } else if ($filearea === 'backup') { if (!has_capability('moodle/course:managefiles', $context)) { @@ -383,7 +384,7 @@ function imscp_pluginfile($course, $cm, $context, $filearea, $args, $forcedownlo } // finally send the file - send_stored_file($file, 86400, 0, $forcedownload); + send_stored_file($file, 86400, 0, $forcedownload, $options); } else { return false; diff --git a/mod/lesson/lib.php b/mod/lesson/lib.php index 0157f0ee43d1e..b3252fcfe4155 100644 --- a/mod/lesson/lib.php +++ b/mod/lesson/lib.php @@ -876,9 +876,10 @@ function lesson_get_import_export_formats($type) { * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - justsend the file */ -function lesson_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function lesson_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -917,7 +918,7 @@ function lesson_pluginfile($course, $cm, $context, $filearea, $args, $forcedownl } // finally send the file - send_stored_file($file, 0, 0, $forcedownload); // download MUST be forced - security! + send_stored_file($file, 0, 0, $forcedownload, $options); // download MUST be forced - security! } /** diff --git a/mod/page/lib.php b/mod/page/lib.php index 6d960f8530ba2..4512f5e7fca5b 100644 --- a/mod/page/lib.php +++ b/mod/page/lib.php @@ -360,9 +360,10 @@ function page_get_file_info($browser, $areas, $course, $cm, $context, $filearea, * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - just send the file */ -function page_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function page_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; require_once("$CFG->libdir/resourcelib.php"); @@ -418,7 +419,7 @@ function page_pluginfile($course, $cm, $context, $filearea, $args, $forcedownloa } // finally send the file - send_stored_file($file, 86400, 0, $forcedownload); + send_stored_file($file, 86400, 0, $forcedownload, $options); } } diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index 8720e25318317..1e29c52859686 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -1655,9 +1655,10 @@ function quiz_extend_settings_navigation($settings, $quiznode) { * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - justsend the file */ -function quiz_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function quiz_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -1687,7 +1688,7 @@ function quiz_pluginfile($course, $cm, $context, $filearea, $args, $forcedownloa if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { return false; } - send_stored_file($file, 0, 0, true); + send_stored_file($file, 0, 0, true, $options); } /** @@ -1704,10 +1705,11 @@ function quiz_pluginfile($course, $cm, $context, $filearea, $args, $forcedownloa * @param int $slot the id of a question in this quiz attempt. * @param array $args the remaining bits of the file path. * @param bool $forcedownload whether the user must be forced to download the file. + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - justsend the file */ function mod_quiz_question_pluginfile($course, $context, $component, - $filearea, $qubaid, $slot, $args, $forcedownload) { + $filearea, $qubaid, $slot, $args, $forcedownload, array $options=array()) { global $CFG; require_once($CFG->dirroot . '/mod/quiz/locallib.php'); @@ -1739,7 +1741,7 @@ function mod_quiz_question_pluginfile($course, $context, $component, send_file_not_found(); } - send_stored_file($file, 0, 0, $forcedownload); + send_stored_file($file, 0, 0, $forcedownload, $options); } /** diff --git a/mod/resource/lib.php b/mod/resource/lib.php index 2b109b60e2220..978f2048c5413 100644 --- a/mod/resource/lib.php +++ b/mod/resource/lib.php @@ -382,9 +382,10 @@ function resource_get_file_info($browser, $areas, $course, $cm, $context, $filea * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - just send the file */ -function resource_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function resource_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB; require_once("$CFG->libdir/resourcelib.php"); @@ -443,7 +444,7 @@ function resource_pluginfile($course, $cm, $context, $filearea, $args, $forcedow } // finally send the file - send_stored_file($file, 86400, $filter, $forcedownload); + send_stored_file($file, 86400, $filter, $forcedownload, $options); } /** diff --git a/mod/scorm/lib.php b/mod/scorm/lib.php index 2ad8a133713f0..98490316dc1df 100644 --- a/mod/scorm/lib.php +++ b/mod/scorm/lib.php @@ -927,9 +927,10 @@ function scorm_get_file_info($browser, $areas, $course, $cm, $context, $filearea * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - just send the file */ -function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG; if ($context->contextlevel != CONTEXT_MODULE) { @@ -968,7 +969,7 @@ function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownlo } // finally send the file - send_stored_file($file, $lifetime, 0, false); + send_stored_file($file, $lifetime, 0, false, $options); } /** diff --git a/mod/upgrade.txt b/mod/upgrade.txt index 62b3918fccc55..3419e8200a743 100644 --- a/mod/upgrade.txt +++ b/mod/upgrade.txt @@ -7,6 +7,9 @@ information provided here is intended especially for developers. required changes in code: * define the capability mod/xxx:addinstance (and the corresponding lang string) (unless your mod is a MOD_ARCHETYPE_SYSTEM). +* xxx_pluginfile() is now given the 7th parameter (hopefully the last one) that + contains additional options for the file serving. The array should be re-passed + to send_stored_file(). === 2.2 === diff --git a/mod/wiki/lib.php b/mod/wiki/lib.php index 9dbd3f110e8ad..ff84aeaab4631 100644 --- a/mod/wiki/lib.php +++ b/mod/wiki/lib.php @@ -445,8 +445,10 @@ function wiki_scale_used_anywhere($scaleid) { * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving + * @return bool false if the file was not found, just send the file otherwise and do not return anything */ -function wiki_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function wiki_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG; if ($context->contextlevel != CONTEXT_MODULE) { @@ -477,7 +479,7 @@ function wiki_pluginfile($course, $cm, $context, $filearea, $args, $forcedownloa $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400; - send_stored_file($file, $lifetime, 0); + send_stored_file($file, $lifetime, 0, $options); } } diff --git a/mod/workshop/form/accumulative/lib.php b/mod/workshop/form/accumulative/lib.php index 2a266281c009e..6a3a54db808be 100644 --- a/mod/workshop/form/accumulative/lib.php +++ b/mod/workshop/form/accumulative/lib.php @@ -38,9 +38,10 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function workshopform_accumulative_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload) { +function workshopform_accumulative_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) { global $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -72,7 +73,7 @@ function workshopform_accumulative_pluginfile($course, $cm, $context, $filearea, } // finally send the file - send_stored_file($file); + send_stored_file($file, 0, 0, $forcedownload, $options); } /** diff --git a/mod/workshop/form/comments/lib.php b/mod/workshop/form/comments/lib.php index 1e7723f1059bd..408ecf427457e 100644 --- a/mod/workshop/form/comments/lib.php +++ b/mod/workshop/form/comments/lib.php @@ -38,9 +38,10 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function workshopform_comments_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload) { +function workshopform_comments_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) { global $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -72,7 +73,7 @@ function workshopform_comments_pluginfile($course, $cm, $context, $filearea, arr } // finally send the file - send_stored_file($file); + send_stored_file($file, 0, 0, $forcedownload, $options); } /** diff --git a/mod/workshop/form/numerrors/lib.php b/mod/workshop/form/numerrors/lib.php index 2fd0ce905499a..3e461dd88a360 100644 --- a/mod/workshop/form/numerrors/lib.php +++ b/mod/workshop/form/numerrors/lib.php @@ -38,9 +38,10 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function workshopform_numerrors_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload) { +function workshopform_numerrors_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) { global $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -72,7 +73,7 @@ function workshopform_numerrors_pluginfile($course, $cm, $context, $filearea, ar } // finally send the file - send_stored_file($file); + send_stored_file($file, 0, 0, $forcedownload, $options); } /** diff --git a/mod/workshop/form/rubric/lib.php b/mod/workshop/form/rubric/lib.php index 1361c66ebfb4d..eb6425d9f8dc9 100644 --- a/mod/workshop/form/rubric/lib.php +++ b/mod/workshop/form/rubric/lib.php @@ -38,9 +38,10 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function workshopform_rubric_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload) { +function workshopform_rubric_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) { global $DB; if ($context->contextlevel != CONTEXT_MODULE) { @@ -72,7 +73,7 @@ function workshopform_rubric_pluginfile($course, $cm, $context, $filearea, array } // finally send the file - send_stored_file($file); + send_stored_file($file, 0, 0, $forcedownload, $options); } /** diff --git a/mod/workshop/lib.php b/mod/workshop/lib.php index 7dc9a6e35b16f..655c5ddc39c92 100644 --- a/mod/workshop/lib.php +++ b/mod/workshop/lib.php @@ -1221,15 +1221,16 @@ function workshop_get_file_areas($course, $cm, $context) { * @package mod_workshop * @category files * - * @param stdClass $course - * @param stdClass $cm - * @param stdClass $context - * @param string $filearea - * @param array $args - * @param bool $forcedownload - * @return void this should never return to the caller + * @param stdClass $course the course object + * @param stdClass $cm the course module object + * @param stdClass $context the workshop's context + * @param string $filearea the name of the file area + * @param array $args extra arguments (itemid, path) + * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving + * @return bool false if the file not found, just send the file otherwise and do not return anything */ -function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload) { +function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) { global $DB, $CFG; if ($context->contextlevel != CONTEXT_MODULE) { @@ -1246,7 +1247,7 @@ function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $fo array_shift($args); // we do not use itemids here $relativepath = implode('/', $args); - $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath"; // beware, slashes are not used here! + $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath"; $fs = get_file_storage(); if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { @@ -1256,7 +1257,7 @@ function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $fo $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400; // finally send the file - send_stored_file($file, $lifetime, 0); + send_stored_file($file, $lifetime, 0, $forcedownload, $options); } if ($filearea === 'instructreviewers') { @@ -1277,7 +1278,7 @@ function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $fo $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400; // finally send the file - send_stored_file($file, $lifetime, 0); + send_stored_file($file, $lifetime, 0, $forcedownload, $options); } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') { $itemid = (int)array_shift($args); @@ -1296,7 +1297,7 @@ function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $fo } // finally send the file // these files are uploaded by students - forcing download for security reasons - send_stored_file($file, 0, 0, true); + send_stored_file($file, 0, 0, true, $options); } return false; From 957fc845b618ceaa422252ee0c0269f07a44f08e Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Tue, 24 Apr 2012 14:01:53 +0200 Subject: [PATCH 08/15] MDL-32471 preview thumbnails support for blocks block_xxx_pluginfile() now accepts the $options parameter that is to be passed to the send_stored_file() function. --- blocks/html/lib.php | 5 +++-- blocks/upgrade.txt | 6 ++++++ lib/filelib.php | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/blocks/html/lib.php b/blocks/html/lib.php index 17d98cecb2823..a2555c3f88e8a 100644 --- a/blocks/html/lib.php +++ b/blocks/html/lib.php @@ -28,9 +28,10 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function block_html_pluginfile($course, $birecord_or_cm, $context, $filearea, $args, $forcedownload) { +function block_html_pluginfile($course, $birecord_or_cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $SCRIPT; if ($context->contextlevel != CONTEXT_BLOCK) { @@ -64,7 +65,7 @@ function block_html_pluginfile($course, $birecord_or_cm, $context, $filearea, $a } session_get_instance()->write_close(); - send_stored_file($file, 60*60, 0, $forcedownload); + send_stored_file($file, 60*60, 0, $forcedownload, $options); } /** diff --git a/blocks/upgrade.txt b/blocks/upgrade.txt index a87cbf8d1ff64..b7e288e9c9bb2 100644 --- a/blocks/upgrade.txt +++ b/blocks/upgrade.txt @@ -1,6 +1,12 @@ This files describes API changes in /blocks/* - activity modules, information provided here is intended especially for developers. +=== 2.3 === + +required changes in code: +* block_xxx_pluginfile() is now given the 7th parameter (hopefully the last one) that + contains additional options for the file serving. The array should be re-passed + to send_stored_file(). === 2.0 === diff --git a/lib/filelib.php b/lib/filelib.php index 1d4a019cb36e0..31093981ad6f3 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -3976,7 +3976,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { $filefunction = $component.'_pluginfile'; if (function_exists($filefunction)) { // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" - $filefunction($course, $birecord, $context, $filearea, $args, $forcedownload); + $filefunction($course, $birecord, $context, $filearea, $args, $forcedownload, array('preview' => $preview)); } send_file_not_found(); From 7a00d4385db7864b456528d70a1a0b20655a9c22 Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Tue, 24 Apr 2012 14:29:44 +0200 Subject: [PATCH 09/15] MDL-32471 preview thumbnails support for other component types This commit affects mostly qtypes. Other standard components either do not have pluginfile handler or have been already updated to the new API. --- lib/filelib.php | 3 ++- lib/questionlib.php | 19 +++++++++++-------- mod/quiz/report/statistics/lib.php | 5 +++-- question/previewlib.php | 5 +++-- question/type/calculated/lib.php | 5 +++-- question/type/calculatedmulti/lib.php | 5 +++-- question/type/calculatedsimple/lib.php | 5 +++-- question/type/essay/lib.php | 5 +++-- question/type/match/lib.php | 5 +++-- question/type/multichoice/lib.php | 5 +++-- question/type/numerical/lib.php | 5 +++-- question/type/shortanswer/lib.php | 5 +++-- question/type/truefalse/lib.php | 5 +++-- question/type/upgrade.txt | 3 +++ 14 files changed, 49 insertions(+), 31 deletions(-) diff --git a/lib/filelib.php b/lib/filelib.php index 31093981ad6f3..cb30f23a572ee 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -3981,6 +3981,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { send_file_not_found(); + // ======================================================================================================================== } else if (strpos($component, '_') === false) { // all core subsystems have to be specified above, no more guessing here! send_file_not_found(); @@ -3996,7 +3997,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { $filefunction = $component.'_pluginfile'; if (function_exists($filefunction)) { // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" - $filefunction($course, $cm, $context, $filearea, $args, $forcedownload); + $filefunction($course, $cm, $context, $filearea, $args, $forcedownload, array('preview' => $preview)); } send_file_not_found(); diff --git a/lib/questionlib.php b/lib/questionlib.php index 0cab3184067ac..e49a09ce5c17a 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -1719,8 +1719,9 @@ function question_rewrite_questiontext_preview_urls($questiontext, $contextid, * @param int $questionid the question id * @param array $args the remaining file arguments (file path). * @param bool $forcedownload whether the user must be forced to download the file. + * @param array $options additional options affecting the file serving */ -function question_send_questiontext_file($questionid, $args, $forcedownload) { +function question_send_questiontext_file($questionid, $args, $forcedownload, $options) { global $DB; $question = $DB->get_record_sql(' @@ -1735,7 +1736,7 @@ function question_send_questiontext_file($questionid, $args, $forcedownload) { send_file_not_found(); } - send_stored_file($file, 0, 0, $forcedownload); + send_stored_file($file, 0, 0, $forcedownload, $options); } /** @@ -1759,8 +1760,9 @@ function question_send_questiontext_file($questionid, $args, $forcedownload) { * @param string $filearea the name of the file area. * @param array $args the remaining bits of the file path. * @param bool $forcedownload whether the user must be forced to download the file. + * @param array $options additional options affecting the file serving */ -function question_pluginfile($course, $context, $component, $filearea, $args, $forcedownload) { +function question_pluginfile($course, $context, $component, $filearea, $args, $forcedownload, array $options=array()) { global $DB, $CFG; if ($filearea === 'questiontext_preview') { @@ -1768,7 +1770,7 @@ function question_pluginfile($course, $context, $component, $filearea, $args, $f $questionid = array_shift($args); component_callback($component, 'questiontext_preview_pluginfile', array( - $context, $questionid, $args, $forcedownload)); + $context, $questionid, $args, $forcedownload, $options)); send_file_not_found(); } @@ -1841,7 +1843,7 @@ function question_pluginfile($course, $context, $component, $filearea, $args, $f if ($module === 'core_question_preview') { require_once($CFG->dirroot . '/question/previewlib.php'); return question_preview_question_pluginfile($course, $context, - $component, $filearea, $qubaid, $slot, $args, $forcedownload); + $component, $filearea, $qubaid, $slot, $args, $forcedownload, $options); } else { $dir = get_component_directory($module); @@ -1856,7 +1858,7 @@ function question_pluginfile($course, $context, $component, $filearea, $args, $f } $filefunction($course, $context, $component, $filearea, $qubaid, $slot, - $args, $forcedownload); + $args, $forcedownload, $options); send_file_not_found(); } @@ -1871,8 +1873,9 @@ function question_pluginfile($course, $context, $component, $filearea, $args, $f * @param int $questionid the question id * @param array $args remaining file args * @param bool $forcedownload + * @param array $options additional options affecting the file serving */ -function core_question_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload) { +function core_question_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload, array $options=array()) { global $DB; // Verify that contextid matches the question. @@ -1889,7 +1892,7 @@ function core_question_questiontext_preview_pluginfile($context, $questionid, $a question_require_capability_on($question, 'use'); - question_send_questiontext_file($questionid, $args, $forcedownload); + question_send_questiontext_file($questionid, $args, $forcedownload, $options); } /** diff --git a/mod/quiz/report/statistics/lib.php b/mod/quiz/report/statistics/lib.php index 34026f851f4b0..c9af11b25fdcd 100644 --- a/mod/quiz/report/statistics/lib.php +++ b/mod/quiz/report/statistics/lib.php @@ -36,8 +36,9 @@ * @param int $questionid the question id * @param array $args remaining file args * @param bool $forcedownload + * @param array $options additional options affecting the file serving */ -function quiz_statistics_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload) { +function quiz_statistics_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload, array $options=array()) { global $CFG; require_once($CFG->dirroot . '/mod/quiz/locallib.php'); @@ -48,7 +49,7 @@ function quiz_statistics_questiontext_preview_pluginfile($context, $questionid, // validate questionid, becuase of the complexity of random quetsions. require_capability('quiz/statistics:view', $context); - question_send_questiontext_file($questionid, $args, $forcedownload); + question_send_questiontext_file($questionid, $args, $forcedownload, $options); } /** diff --git a/question/previewlib.php b/question/previewlib.php index 3bf47b26c12b6..c3ffc191c30bc 100644 --- a/question/previewlib.php +++ b/question/previewlib.php @@ -224,10 +224,11 @@ public function get_url_params() { * @param int $slot the relevant slot within the usage. * @param array $args the remaining bits of the file path. * @param bool $forcedownload whether the user must be forced to download the file. + * @param array $options additional options affecting the file serving * @return bool false if file not found, does not return if found - justsend the file */ function question_preview_question_pluginfile($course, $context, $component, - $filearea, $qubaid, $slot, $args, $forcedownload) { + $filearea, $qubaid, $slot, $args, $forcedownload, $options) { global $USER, $DB, $CFG; $quba = question_engine::load_questions_usage_by_activity($qubaid); @@ -255,7 +256,7 @@ function question_preview_question_pluginfile($course, $context, $component, send_file_not_found(); } - send_stored_file($file, 0, 0, $forcedownload); + send_stored_file($file, 0, 0, $forcedownload, $options); } /** diff --git a/question/type/calculated/lib.php b/question/type/calculated/lib.php index ca7559ae8befb..57ba58f37e833 100644 --- a/question/type/calculated/lib.php +++ b/question/type/calculated/lib.php @@ -38,10 +38,11 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function qtype_calculated_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function qtype_calculated_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG; require_once($CFG->libdir . '/questionlib.php'); - question_pluginfile($course, $context, 'qtype_calculated', $filearea, $args, $forcedownload); + question_pluginfile($course, $context, 'qtype_calculated', $filearea, $args, $forcedownload, $options); } diff --git a/question/type/calculatedmulti/lib.php b/question/type/calculatedmulti/lib.php index f78a1668a8c3c..d91370f0c2388 100644 --- a/question/type/calculatedmulti/lib.php +++ b/question/type/calculatedmulti/lib.php @@ -38,12 +38,13 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ function qtype_calculatedmulti_pluginfile($course, $cm, $context, $filearea, $args, - $forcedownload) { + $forcedownload, array $options=array()) { global $DB, $CFG; require_once($CFG->libdir . '/questionlib.php'); question_pluginfile($course, $context, 'qtype_calculatedmulti', $filearea, $args, - $forcedownload); + $forcedownload, $options); } diff --git a/question/type/calculatedsimple/lib.php b/question/type/calculatedsimple/lib.php index d95bddbbe4cf2..4002e965b65ac 100644 --- a/question/type/calculatedsimple/lib.php +++ b/question/type/calculatedsimple/lib.php @@ -39,12 +39,13 @@ * @param string $filearea * @param array $args * @param bool $forcedownload + * @param array $options additional options affecting the file serving * @return bool */ function qtype_calculatedsimple_pluginfile($course, $cm, $context, $filearea, - $args, $forcedownload) { + $args, $forcedownload, array $options=array()) { global $CFG; require_once($CFG->libdir . '/questionlib.php'); question_pluginfile($course, $context, 'qtype_calculatedsimple', $filearea, - $args, $forcedownload); + $args, $forcedownload, $options); } diff --git a/question/type/essay/lib.php b/question/type/essay/lib.php index 38e6557f653b8..0aa4551797cda 100644 --- a/question/type/essay/lib.php +++ b/question/type/essay/lib.php @@ -38,10 +38,11 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function qtype_essay_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function qtype_essay_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG; require_once($CFG->libdir . '/questionlib.php'); - question_pluginfile($course, $context, 'qtype_essay', $filearea, $args, $forcedownload); + question_pluginfile($course, $context, 'qtype_essay', $filearea, $args, $forcedownload, $options); } diff --git a/question/type/match/lib.php b/question/type/match/lib.php index f997cc41f6ce7..ff31bc4ec23fd 100644 --- a/question/type/match/lib.php +++ b/question/type/match/lib.php @@ -37,10 +37,11 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function qtype_match_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function qtype_match_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $DB, $CFG; require_once($CFG->libdir . '/questionlib.php'); - question_pluginfile($course, $context, 'qtype_match', $filearea, $args, $forcedownload); + question_pluginfile($course, $context, 'qtype_match', $filearea, $args, $forcedownload, $options); } diff --git a/question/type/multichoice/lib.php b/question/type/multichoice/lib.php index 2b0b8857a9c96..9d5b0e20cb8ee 100644 --- a/question/type/multichoice/lib.php +++ b/question/type/multichoice/lib.php @@ -38,10 +38,11 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function qtype_multichoice_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function qtype_multichoice_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG; require_once($CFG->libdir . '/questionlib.php'); - question_pluginfile($course, $context, 'qtype_multichoice', $filearea, $args, $forcedownload); + question_pluginfile($course, $context, 'qtype_multichoice', $filearea, $args, $forcedownload, $options); } diff --git a/question/type/numerical/lib.php b/question/type/numerical/lib.php index e31de0adafa71..b1b4ec2669398 100644 --- a/question/type/numerical/lib.php +++ b/question/type/numerical/lib.php @@ -38,10 +38,11 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function qtype_numerical_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function qtype_numerical_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG; require_once($CFG->libdir . '/questionlib.php'); - question_pluginfile($course, $context, 'qtype_numerical', $filearea, $args, $forcedownload); + question_pluginfile($course, $context, 'qtype_numerical', $filearea, $args, $forcedownload, $options); } diff --git a/question/type/shortanswer/lib.php b/question/type/shortanswer/lib.php index e508d68a6414e..606085aab654d 100644 --- a/question/type/shortanswer/lib.php +++ b/question/type/shortanswer/lib.php @@ -37,10 +37,11 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function qtype_shortanswer_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function qtype_shortanswer_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $DB, $CFG; require_once($CFG->libdir . '/questionlib.php'); - question_pluginfile($course, $context, 'qtype_shortanswer', $filearea, $args, $forcedownload); + question_pluginfile($course, $context, 'qtype_shortanswer', $filearea, $args, $forcedownload, $options); } diff --git a/question/type/truefalse/lib.php b/question/type/truefalse/lib.php index a3c4620c4233a..2e71c33be0aa2 100644 --- a/question/type/truefalse/lib.php +++ b/question/type/truefalse/lib.php @@ -37,10 +37,11 @@ * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving * @return bool */ -function qtype_truefalse_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { +function qtype_truefalse_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { global $CFG; require_once($CFG->libdir . '/questionlib.php'); - question_pluginfile($course, $context, 'qtype_truefalse', $filearea, $args, $forcedownload); + question_pluginfile($course, $context, 'qtype_truefalse', $filearea, $args, $forcedownload, $options); } diff --git a/question/type/upgrade.txt b/question/type/upgrade.txt index 0281c6f08a03a..f399642cdc234 100644 --- a/question/type/upgrade.txt +++ b/question/type/upgrade.txt @@ -7,6 +7,9 @@ This files describes API changes for question type plugins. import and export, then you will probably get PHP strict syntax notices in developer debug mode until you change the method signature to include qformat_xml $format. That is, you need to specify the argument type. +* qtype_xxx_pluginfile() is now given the 7th parameter (hopefully the last + one) that contains additional options for the file serving. The array should + be re-passed to question_pluginfile() as is. === 2.2 === From 9120a46257411d67c568900f75a0d1445f9f1b67 Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Tue, 24 Apr 2012 15:14:09 +0200 Subject: [PATCH 10/15] MDL-32471 remove orphaned preview files via cron --- lib/filestorage/file_storage.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php index e4fb0ec5b9a0a..bf059a82d27ad 100644 --- a/lib/filestorage/file_storage.php +++ b/lib/filestorage/file_storage.php @@ -1413,6 +1413,22 @@ public function cron() { $rs->close(); mtrace('done.'); + // remove orphaned preview files (that is files in the core preview filearea without + // the existing original file) + mtrace('Deleting orphaned preview files... ', ''); + $sql = "SELECT p.* + FROM {files} p + LEFT JOIN {files} o ON (p.filename = o.contenthash) + WHERE p.contextid = ? AND p.component = 'core' AND p.filearea = 'preview' AND p.itemid = 0 + AND p.filename <> '.' AND o.id IS NULL"; + $syscontext = context_system::instance(); + $rs = $DB->get_recordset_sql($sql, array($syscontext->id)); + foreach ($rs as $orphan) { + $this->get_file_instance($orphan)->delete(); + } + $rs->close(); + mtrace('done.'); + // remove trash pool files once a day // if you want to disable purging of trash put $CFG->fileslastcleanup=time(); into config.php if (empty($CFG->fileslastcleanup) or $CFG->fileslastcleanup < time() - 60*60*24) { From fe68aac7d9882d3bb2f42489365484d15c0fa395 Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Tue, 24 Apr 2012 16:10:46 +0200 Subject: [PATCH 11/15] MDL-32471 using === for string comparison Thanks to Petr Skoda for spotting this during the peer-review. --- lib/filestorage/file_storage.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php index bf059a82d27ad..05d3e28c14db3 100644 --- a/lib/filestorage/file_storage.php +++ b/lib/filestorage/file_storage.php @@ -190,7 +190,7 @@ protected function create_file_preview(stored_file $file, $mode) { $mimetype = $file->get_mimetype(); - if ($mimetype == 'image/gif' or $mimetype == 'image/jpeg' or $mimetype == 'image/png') { + if ($mimetype === 'image/gif' or $mimetype === 'image/jpeg' or $mimetype === 'image/png') { // make a preview of the image $data = $this->create_imagefile_preview($file, $mode); @@ -244,10 +244,10 @@ protected function create_imagefile_preview(stored_file $file, $mode) { $tmpfilepath = $tmproot.'/'.$file->get_contenthash(); $file->copy_content_to($tmpfilepath); - if ($mode == 'tinyicon') { + if ($mode === 'tinyicon') { $data = generate_image_thumbnail($tmpfilepath, 16, 16); - } else if ($mode == 'thumb') { + } else if ($mode === 'thumb') { $data = generate_image_thumbnail($tmpfilepath, 90, 90); } else { From 8f110835c11eeac725258196491fab352f2c9874 Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Wed, 25 Apr 2012 11:25:56 +0200 Subject: [PATCH 12/15] MDL-32471 adding preview generation unit tests The test image is a public domain file from http://commons.wikimedia.org/wiki/File:Easter_eggs_-_onion_decoration.jpg --- lib/filestorage/tests/file_storage_test.php | 80 +++++++++++++++++++ lib/filestorage/tests/fixtures/testimage.jpg | Bin 0 -> 28626 bytes phpunit.xml.dist | 3 + 3 files changed, 83 insertions(+) create mode 100644 lib/filestorage/tests/file_storage_test.php create mode 100644 lib/filestorage/tests/fixtures/testimage.jpg diff --git a/lib/filestorage/tests/file_storage_test.php b/lib/filestorage/tests/file_storage_test.php new file mode 100644 index 0000000000000..ba31f8043a9d5 --- /dev/null +++ b/lib/filestorage/tests/file_storage_test.php @@ -0,0 +1,80 @@ +. + +/** + * Unit tests for /lib/filestorage/file_storage.php + * + * @package core + * @category test + * @copyright 2012 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/filelib.php'); + +class filestoragelib_testcase extends advanced_testcase { + + /** + * Local files can be added to the filepool + */ + public function test_create_file_from_pathname() { + global $CFG; + + $this->resetAfterTest(false); + + $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg'; + $syscontext = context_system::instance(); + $filerecord = array( + 'contextid' => $syscontext->id, + 'component' => 'core', + 'filearea' => 'unittest', + 'itemid' => 0, + 'filepath' => '/images/', + 'filename' => 'testimage.jpg', + ); + + $fs = get_file_storage(); + $fs->create_file_from_pathname($filerecord, $filepath); + + $this->assertTrue($fs->file_exists($syscontext->id, 'core', 'unittest', 0, '/images/', 'testimage.jpg')); + + return $fs->get_file($syscontext->id, 'core', 'unittest', 0, '/images/', 'testimage.jpg'); + } + + /** + * Local images can be added to the filepool and their preview can be obtained + * + * @depends test_create_file_from_pathname + */ + public function test_get_file_preview(stored_file $file) { + global $CFG; + + $this->resetAfterTest(true); + $fs = get_file_storage(); + + $previewtinyicon = $fs->get_file_preview($file, 'tinyicon'); + $this->assertInstanceOf('stored_file', $previewtinyicon); + $this->assertEquals('6b9864ae1536a8eeef54e097319175a8be12f07c', $previewtinyicon->get_filename()); + + $previewtinyicon = $fs->get_file_preview($file, 'thumb'); + $this->assertInstanceOf('stored_file', $previewtinyicon); + $this->assertEquals('6b9864ae1536a8eeef54e097319175a8be12f07c', $previewtinyicon->get_filename()); + } +} diff --git a/lib/filestorage/tests/fixtures/testimage.jpg b/lib/filestorage/tests/fixtures/testimage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..15e05d1c32dba9b541c041e829545bf83bb6aa58 GIT binary patch literal 28626 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 E*|BZcW&i*H literal 0 HcmV?d00001 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f0838ee6ba82f..be46f6bf50573 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -29,6 +29,9 @@ lib/ajax/tests lib/form/tests + + lib/filestorage/tests + lib/grade/tests grade/tests From 94d104172e070b68331e4af4949e8c54964afeb8 Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Wed, 25 Apr 2012 11:53:57 +0200 Subject: [PATCH 13/15] MDL-32471 just a trivial change to prevent eventual issues with various preview modes --- lib/filestorage/file_storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php index 05d3e28c14db3..f22efd24927e4 100644 --- a/lib/filestorage/file_storage.php +++ b/lib/filestorage/file_storage.php @@ -206,7 +206,7 @@ protected function create_file_preview(stored_file $file, $mode) { // getimagesizefromstring() is available from PHP 5.4 but we need to support // lower versions, so... $tmproot = make_temp_directory('thumbnails'); - $tmpfilepath = $tmproot.'/'.$file->get_contenthash().'_thumb'; + $tmpfilepath = $tmproot.'/'.$file->get_contenthash().'_'.$mode; file_put_contents($tmpfilepath, $data); $imageinfo = getimagesize($tmpfilepath); unlink($tmpfilepath); From f7eec6ce79428a4597979c15b8a39dfe8dc23a6f Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Wed, 25 Apr 2012 13:44:36 +0200 Subject: [PATCH 14/15] MDL-32471 improved SQL to prevent potential full table scans --- lib/filestorage/file_storage.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php index f22efd24927e4..463f9d4f76337 100644 --- a/lib/filestorage/file_storage.php +++ b/lib/filestorage/file_storage.php @@ -1420,11 +1420,14 @@ public function cron() { FROM {files} p LEFT JOIN {files} o ON (p.filename = o.contenthash) WHERE p.contextid = ? AND p.component = 'core' AND p.filearea = 'preview' AND p.itemid = 0 - AND p.filename <> '.' AND o.id IS NULL"; + AND o.id IS NULL"; $syscontext = context_system::instance(); $rs = $DB->get_recordset_sql($sql, array($syscontext->id)); foreach ($rs as $orphan) { - $this->get_file_instance($orphan)->delete(); + $file = $this->get_file_instance($orphan); + if (!$file->is_directory()) { + $file->delete(); + } } $rs->close(); mtrace('done.'); From b861b6093298015db7a1037e1f9d3dee044f0ef1 Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Wed, 25 Apr 2012 13:48:12 +0200 Subject: [PATCH 15/15] MDL-32471 fixed strict standards in the declaration of send_file() in assignment types All subtypes declaration should be compatible with that of assignment_base::send_file() --- mod/assignment/type/online/assignment.class.php | 2 +- mod/assignment/type/upload/assignment.class.php | 2 +- mod/assignment/type/uploadsingle/assignment.class.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mod/assignment/type/online/assignment.class.php b/mod/assignment/type/online/assignment.class.php index 6adf048044f25..be5a238ca28a7 100644 --- a/mod/assignment/type/online/assignment.class.php +++ b/mod/assignment/type/online/assignment.class.php @@ -375,7 +375,7 @@ function extend_settings_navigation($node) { } } - public function send_file($filearea, $args, $forcedownload, $options) { + public function send_file($filearea, $args, $forcedownload, array $options=array()) { global $USER; require_capability('mod/assignment:view', $this->context); diff --git a/mod/assignment/type/upload/assignment.class.php b/mod/assignment/type/upload/assignment.class.php index f4fee1d2cfca3..54c4b716c6a04 100644 --- a/mod/assignment/type/upload/assignment.class.php +++ b/mod/assignment/type/upload/assignment.class.php @@ -614,7 +614,7 @@ function upload_file($mform, $options) { die; } - function send_file($filearea, $args, $forcedownload, $options) { + function send_file($filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB, $USER; require_once($CFG->libdir.'/filelib.php'); diff --git a/mod/assignment/type/uploadsingle/assignment.class.php b/mod/assignment/type/uploadsingle/assignment.class.php index b7b3071a4daa9..39b41b7917bba 100644 --- a/mod/assignment/type/uploadsingle/assignment.class.php +++ b/mod/assignment/type/uploadsingle/assignment.class.php @@ -300,7 +300,7 @@ function portfolio_exportable() { return true; } - function send_file($filearea, $args, $forcedownload, $options) { + function send_file($filearea, $args, $forcedownload, array $options=array()) { global $CFG, $DB, $USER; require_once($CFG->libdir.'/filelib.php');