/** * resize an image object * * this function drops the reference to any currently resized version, * saves the resized image together with the original image in the page's * shared folder and updates the object file to use the resized version. * @param array $args arguments * key 'name' name of the objects * key 'width' width in px * key 'height' height in px * @return array response * true if the client is advised to reload the image, false if not */ function image_resize($args) { // check for gd if (!_gd_available()) { return response('Host does not have gd', 500); } // set requested width & height if (($width = @intval($args['width'])) == 0) { return response('Required argument "width" is zero or does not exist', 400); } if (($height = @intval($args['height'])) == 0) { return response('Required argument "height" is zero or does not exist', 400); } load_modules('glue'); // resolve symlinks $ret = object_get_symlink($args); if ($ret['#error']) { return $ret; } elseif ($ret['#data'] !== false) { log_msg('debug', 'image_resize: resolved object ' . quot($args['name']) . ' into ' . quot($ret['#data'])); $args['name'] = $ret['#data']; } // load object $obj = load_object($args); if ($obj['#error']) { return $obj; } else { $obj = $obj['#data']; } if (@intval($obj['image-file-width']) == 0 || @intval($obj['image-file-height']) == 0) { return response('Original dimensions are not available', 500); } // set pagename $pn = array_shift(expl('.', $obj['name'])); // resizing might not be necessary at all if (!empty($obj['image-resized-file']) && @intval($obj['image-resized-width']) == $width && @intval($obj['image-resized-height'] == $height)) { log_msg('debug', 'image_resize: width and height match the current resized file, no resize necessary'); return response(false); } // else remove any currently resized file if (!empty($obj['image-resized-file'])) { log_msg('info', 'image_resize: dropping reference to previous resized file ' . quot($obj['image-resized-file'])); delete_upload(array('pagename' => $pn, 'file' => $obj['image-resized-file'], 'max_cnt' => 1)); unset($obj['image-resized-file']); unset($obj['image-resized-width']); unset($obj['image-resized-height']); // update object file as well $ret = object_remove_attr(array('name' => $obj['name'], 'attr' => array('image-resized-file', 'image-resized-width', 'image-resized-height'))); if ($ret['#error']) { return $ret; } $was_resized = true; } else { $was_resized = false; } // check if width or height are larger than the original if (@intval($obj['image-file-width']) <= $width || @intval($obj['image-file-height']) <= $height) { log_msg('debug', 'image_resize: dimensions requested are larger or equal than the original file is, no resize necessary'); // the client need not reload the the image if we were using the // original before if (!$was_resized) { return response(false); } else { return response(true); } } // check if we really have a source image if (empty($obj['image-file-mime']) && empty($obj['image-file'])) { return response(false); } // TODO (later): make this a generic function // load source file $ext = filext($obj['image-file']); $fn = CONTENT_DIR . '/' . $pn . '/shared/' . $obj['image-file']; if ($obj['image-file-mime'] == 'image/jpeg' || in_array($ext, array('jpg', 'jpeg'))) { $orig = @imagecreatefromjpeg($fn); $dest_ext = 'jpg'; } elseif ($obj['image-file-mime'] == 'image/png' || $ext == 'png') { $orig = @imagecreatefrompng($fn); $dest_ext = 'png'; } elseif (is_ani($fn)) { // animated images shall not be resized log_msg('debug', 'image_resize: animated image, not resizing'); return response(true); } elseif ($obj['image-file-mime'] == 'image/gif' || $ext == 'gif') { $orig = @imagecreatefromgif($fn); // save gifs as png // TODO (later): check for animated gif (see php.net/manual/en/function.imagecreatefromgif.php) $dest_ext = 'png'; } else { return response('Unsupported source file format ' . quot($obj['image-file']), 500); } if ($orig === false) { return response('Error loading source file ' . quot($obj['image-file']), 500); } // get source file dimensions $orig_size = @getimagesize($fn); // create resized image if (($resized = @imagecreatetruecolor($width, $height)) === false) { @imagedestroy($orig); return response('Error creating the resized image', 500); } // preserve any alpha channel @imagealphablending($resized, false); @imagesavealpha($resized, true); // try to resize if (!@imagecopyresampled($resized, $orig, 0, 0, 0, 0, $width, $height, $orig_size[0], $orig_size[1])) { @imagedestroy($resized); @imagedestroy($orig); return response('Error resizing the source image', 500); } // setup destination filename $a = expl('.', $obj['image-file']); if (1 < count($a)) { // throw the previous extension away $fn = CONTENT_DIR . '/' . $pn . '/shared/' . implode('.', array_slice($a, 0, -1)) . '-' . $width . 'x' . $height . '.' . $dest_ext; } else { $fn = CONTENT_DIR . '/' . $pn . '/shared/' . $a[0] . '-' . $width . 'x' . $height . '.' . $dest_ext; } $m = umask(0111); if ($dest_ext == 'jpg') { $ret = @imagejpeg($resized, $fn, IMAGE_JPEG_QUAL); } else { if ($dest_ext == 'png') { // preserve any alpha channel @imagealphablending($resized, false); @imagesavealpha($resized, true); $ret = @imagepng($resized, $fn, IMAGE_PNG_QUAL); } } umask($m); // destroy images again @imagedestroy($resized); @imagedestroy($orig); if (!$ret) { return response('Error saving the resized image', 500); } else { log_msg('info', 'image_resize: created a resized image of ' . quot($obj['name']) . ' -> ' . quot(basename($fn))); } // the code above can take a while, so read in the object anew via // update_object() $update = array(); $update['name'] = $obj['name']; $update['image-resized-file'] = basename($fn); $update['image-resized-width'] = $width; $update['image-resized-height'] = $height; // we change width and height here as well since we are racing with the // save_object from the frontend after resize $update['object-width'] = $width . 'px'; $update['object-height'] = $height . 'px'; return update_object($update); }
/** * serve a resource associated with an object * * the function might not return (e.g. when a module calls serve_file()). * @param string $s object (e.g. page.rev.obj) * @param bool $dl download file * @return bool */ function serve_resource($s, $dl) { load_modules('glue'); // resolve symlinks $ret = object_get_symlink(array('name' => $s)); if ($ret['#error'] == false && $ret['#data'] !== false) { log_msg('debug', 'controller: resolved resource ' . quot($s) . ' into ' . quot($ret['#data'])); $s = $ret['#data']; } $obj = load_object(array('name' => $s)); if ($obj['#error']) { return false; } else { $obj = $obj['#data']; } $ret = invoke_hook_while('serve_resource', false, array('obj' => $obj, 'dl' => $dl)); // this is probably not needed as the module will most likely call // serve_file() on success, which does not return foreach ($ret as $key => $val) { if ($val !== false) { return true; } } return false; }
/** * create a snapshot from a page * * @param array $args arguments * key 'page' page to shapshot (i.e. page.rev) * key 'rev' (optional) new revision name (i.e. rev2) (if empty or not set * a revision starting with 'auto-' and the current date will be * created) * @return array response (holding the page of the newly created revision * if successful) */ function snapshot($args) { if (empty($args['page'])) { return response('Required argument "page" missing or empty', 400); } if (!page_exists($args['page'])) { return response('Page ' . quot($args['page']) . ' does not exist', 404); } // setup revision name $a = expl('.', $args['page']); if (empty($args['rev'])) { $args['rev'] = 'auto-' . date('YmdHis'); } elseif (page_exists($a[0] . '.' . $args['rev'])) { return response('Revision ' . quot($args['rev']) . ' already exists', 400); } elseif (!valid_pagename($a[0] . '.' . $args['rev'])) { return response('Invalid revision ' . quot($args['rev']), 400); } // create revision $dest = CONTENT_DIR . '/' . $a[0] . '/' . $args['rev']; $m = umask(00); if (!@mkdir($dest)) { umask($m); return response('Error creating directory ' . quot($dest), 500); } umask($m); // copy files // we go through the files one by one in order to spot symlinks hiding $src = CONTENT_DIR . '/' . str_replace('.', '/', $args['page']); $files = scandir($src); foreach ($files as $f) { if ($f == '.' || $f == '..') { continue; } elseif (is_dir($src . '/' . $f) && substr($f, 0, 1) == '.') { // skip directories that start with a dot (like .svn) without a warning continue; } elseif (is_dir($src . '/' . $f)) { log_msg('warn', 'snapshot: skipping ' . quot($src . '/' . $f) . ' as we don\'t support directories inside pages'); } elseif (is_link($src . '/' . $f) && is_file($src . '/' . $f)) { // a proper symlink, copy content $s = @file_get_contents($src . '/' . $f); $m = umask(0111); if (!@file_put_contents($dest . '/' . $f, $s)) { log_msg('error', 'snapshot: error writing to ' . quot($dest . '/' . $f) . ', skipping file'); } else { log_msg('debug', 'snapshot: copied the content of symlink ' . quot($args['page'] . '.' . $f)); } umask($m); // load the newly created snapshot and give modules a chance to // copy referenced files as well $dest_name = $a[0] . '.' . $args['rev'] . '.' . $f; $dest_obj = load_object(array('name' => $dest_name)); if ($dest_obj['#error']) { log_msg('error', 'snapshot: error loading snapshotted object ' . quot($dest_name) . ', skipping hook'); } else { $dest_obj = $dest_obj['#data']; // get the source object's target $src_name = $args['page'] . '.' . $f; $src_target = object_get_symlink(array('name' => $src_name)); if ($src_target['#error']) { log_msg('error', 'snapshot: error getting the symlink target of source object ' . quot($src_name) . ', skipping hook'); } else { $src_target = $src_target['#data']; // hook invoke_hook('snapshot_symlink', array('obj' => $dest_obj, 'origin' => implode('.', array_slice(expl('.', $src_target), 0, 2)))); } } } elseif (is_file($src . '/' . $f)) { // copy file $m = umask(0111); if (!@copy($src . '/' . $f, $dest . '/' . $f)) { log_msg('error', 'snapshot: error copying ' . quot($src . '/' . $f) . ' to ' . quot($dest . '/' . $f) . ', skipping file'); } umask($m); } } log_msg('info', 'snapshot: created snapshot ' . quot($a[0] . '.' . $args['rev']) . ' from ' . quot($args['page'])); return response($a[0] . '.' . $args['rev']); }