public function movie_thumbnails_are_jpgs_test() { $movie = test::random_movie(); $name = legal_file::change_extension($movie->name, "jpg"); $_SERVER["REQUEST_URI"] = url::file("var/thumbs/{$name}"); $controller = new File_Proxy_Controller(); $this->assert_same($movie->thumb_path(), $controller->__call("", array())); }
/** * Create a merged list of all photo and movie filename filters, * (e.g. "*.gif"), based on allowed extensions. */ static function get_filters() { $filters = array(); foreach (legal_file::get_extensions() as $extension) { array_push($filters, "*." . $extension, "*." . strtoupper($extension)); } return $filters; }
public function add_photo($id) { $album = ORM::factory("item", $id); access::required("view", $album); access::required("add", $album); access::verify_csrf(); // The Flash uploader not call /start directly, so simulate it here for now. if (!batch::in_progress()) { batch::start(); } $form = $this->_get_add_form($album); // Uploadify adds its own field to the form, so validate that separately. $file_validation = new Validation($_FILES); $file_validation->add_rules("Filedata", "upload::valid", "upload::required", "upload::type[" . implode(",", legal_file::get_extensions()) . "]"); if ($form->validate() && $file_validation->validate()) { $temp_filename = upload::save("Filedata"); Event::add("system.shutdown", create_function("", "unlink(\"{$temp_filename}\");")); try { $item = ORM::factory("item"); $item->name = substr(basename($temp_filename), 10); // Skip unique identifier Kohana adds $item->title = item::convert_filename_to_title($item->name); $item->parent_id = $album->id; $item->set_data_file($temp_filename); // Remove double extensions from the filename - they'll be disallowed in the model but if // we don't do it here then it'll result in a failed upload. $item->name = legal_file::smash_extensions($item->name); $path_info = @pathinfo($temp_filename); if (array_key_exists("extension", $path_info) && in_array(strtolower($path_info["extension"]), legal_file::get_movie_extensions())) { $item->type = "movie"; $item->save(); log::success("content", t("Added a movie"), html::anchor("movies/{$item->id}", t("view movie"))); } else { $item->type = "photo"; $item->save(); log::success("content", t("Added a photo"), html::anchor("photos/{$item->id}", t("view photo"))); } module::event("add_photos_form_completed", $item, $form); } catch (Exception $e) { // The Flash uploader has no good way of reporting complex errors, so just keep it simple. Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); // Ugh. I hate to use instanceof, But this beats catching the exception separately since // we mostly want to treat it the same way as all other exceptions if ($e instanceof ORM_Validation_Exception) { Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1)); } header("HTTP/1.1 500 Internal Server Error"); print "ERROR: " . $e->getMessage(); return; } print "FILEID: {$item->id}"; } else { header("HTTP/1.1 400 Bad Request"); print "ERROR: " . t("Invalid upload"); } }
public function smash_extensions_test() { $this->assert_equal("foo_bar.jpg", legal_file::smash_extensions("foo.bar.jpg")); $this->assert_equal("foo_bar_baz.jpg", legal_file::smash_extensions("foo.bar.baz.jpg")); $this->assert_equal("foo_bar_baz.jpg", legal_file::smash_extensions("foo.bar.baz.jpg")); $this->assert_equal("foo_bar_baz.jpg", legal_file::smash_extensions("...foo...bar..baz...jpg")); $this->assert_equal("/path/to/foo_bar.jpg", legal_file::smash_extensions("/path/to/foo.bar.jpg")); $this->assert_equal("/path/to.to/foo_bar.jpg", legal_file::smash_extensions("/path/to.to/foo.bar.jpg")); $this->assert_equal("foo_bar-12345678.jpg", legal_file::smash_extensions("foo.bar-12345678.jpg")); }
/** * Return the width, height, mime_type and extension of the given image file. */ static function get_file_metadata($file_path) { $image_info = getimagesize($file_path); if ($image_info) { $width = $image_info[0]; $height = $image_info[1]; $mime_type = $image_info["mime"]; $extension = image_type_to_extension($image_info[2], false); return array($width, $height, $mime_type, $extension); } else { // getimagesize failed - use legal_file mapping instead. $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION)); $mime_type = legal_file::get_photo_types_by_extension($extension); return array(0, 0, $mime_type, $extension); } }
public function render() { $v = new View("form_uploadify.html"); $v->album = $this->data["album"]; $v->script_data = $this->data["script_data"]; $v->simultaneous_upload_limit = module::get_var("gallery", "simultaneous_upload_limit"); $v->movies_allowed = movie::allow_uploads(); $v->extensions = legal_file::get_filters(); $v->suhosin_session_encrypt = (bool) ini_get("suhosin.session.encrypt"); list($toolkit_max_filesize_bytes, $toolkit_max_filesize) = graphics::max_filesize(); $upload_max_filesize = trim(ini_get("upload_max_filesize")); $upload_max_filesize_bytes = num::convert_to_bytes($upload_max_filesize); if ($upload_max_filesize_bytes < $toolkit_max_filesize_bytes) { $v->size_limit_bytes = $upload_max_filesize_bytes; $v->size_limit = $upload_max_filesize; } else { $v->size_limit_bytes = $toolkit_max_filesize_bytes; $v->size_limit = $toolkit_max_filesize; } return $v; }
/** * Resize an image. Valid options are width, height and master. Master is one of the Image * master dimension constants. * * @param string $input_file * @param string $output_file * @param array $options * @param Item_Model $item (optional) */ static function resize($input_file, $output_file, $options, $item = null) { graphics::init_toolkit(); $temp_file = system::temp_filename("resize_", pathinfo($output_file, PATHINFO_EXTENSION)); module::event("graphics_resize", $input_file, $temp_file, $options, $item); if (@filesize($temp_file) > 0) { // A graphics_resize event made an image - move it to output_file and use it. @rename($temp_file, $output_file); } else { // No events made an image - proceed with standard process. if (@filesize($input_file) == 0) { throw new Exception("@todo EMPTY_INPUT_FILE"); } list($input_width, $input_height, $input_mime, $input_extension) = photo::get_file_metadata($input_file); if ($input_width && $input_height && (empty($options["width"]) || empty($options["height"]) || empty($options["master"]) || max($input_width, $input_height) <= min($options["width"], $options["height"]))) { // Photo dimensions well-defined, but options not well-defined or would upscale the image. // Do not resize. Check mimes to see if we can copy the file or if we need to convert it. // (checking mimes avoids needlessly converting jpg to jpeg, etc.) $output_mime = legal_file::get_photo_types_by_extension(pathinfo($output_file, PATHINFO_EXTENSION)); if ($input_mime && $output_mime && $input_mime == $output_mime) { // Mimes well-defined and identical - copy input to output copy($input_file, $output_file); } else { // Mimes not well-defined or not the same - convert input to output $image = Image::factory($input_file)->quality(module::get_var("gallery", "image_quality"))->save($output_file); } } else { // Resize the image. This also implicitly converts its format if needed. $image = Image::factory($input_file)->resize($options["width"], $options["height"], $options["master"])->quality(module::get_var("gallery", "image_quality")); if (graphics::can("sharpen")) { $image->sharpen(module::get_var("gallery", "image_sharpen")); } $image->save($output_file); } } module::event("graphics_resize_completed", $input_file, $output_file, $options, $item); }
public function add() { access::verify_csrf(); $form = watermark::get_add_form(); // For TEST_MODE, we want to simulate a file upload. Because this is not a true upload, Forge's // validation logic will correctly reject it. So, we skip validation when we're running tests. if (TEST_MODE || $form->validate()) { $file = $_POST["file"]; // Forge prefixes files with "uploadfile-xxxxxxx" for uniqueness $name = preg_replace("/uploadfile-[^-]+-(.*)/", '$1', basename($file)); try { list($width, $height, $mime_type, $extension) = photo::get_file_metadata($file); // Sanitize filename, which ensures a valid extension. This renaming prevents the issues // addressed in ticket #1855, where an image that looked valid (header said jpg) with a // php extension was previously accepted without changing its extension. $name = legal_file::sanitize_filename($name, $extension, "photo"); } catch (Exception $e) { message::error(t("Invalid or unidentifiable image file")); system::delete_later($file); return; } rename($file, VARPATH . "modules/watermark/{$name}"); module::set_var("watermark", "name", $name); module::set_var("watermark", "width", $width); module::set_var("watermark", "height", $height); module::set_var("watermark", "mime_type", $mime_type); module::set_var("watermark", "position", $form->add_watermark->position->value); module::set_var("watermark", "transparency", $form->add_watermark->transparency->value); $this->_update_graphics_rules(); system::delete_later($file); message::success(t("Watermark saved")); log::success("watermark", t("Watermark saved")); json::reply(array("result" => "success", "location" => url::site("admin/watermarks"))); } else { // rawurlencode the results because the JS code that uploads the file buffers it in an // iframe which entitizes the HTML and makes it difficult for the JS to process. If we url // encode it now, it passes through cleanly. See ticket #797. json::reply(array("result" => "error", "html" => rawurlencode((string) $form))); } // Override the application/json mime type. The dialog based HTML uploader uses an iframe to // buffer the reply, and on some browsers (Firefox 3.6) it does not know what to do with the // JSON that it gets back so it puts up a dialog asking the user what to do with it. So force // the encoding type back to HTML for the iframe. // See: http://jquery.malsup.com/form/#file-upload header("Content-Type: text/html; charset=" . Kohana::CHARSET); }
/** * Rebuild the thumb and resize for the given item. * @param Item_Model $item */ static function generate($item) { if ($item->is_album()) { if (!($cover = $item->album_cover())) { // This album has no cover; there's nothing to generate. Because of an old bug, it's // possible that there's an album cover item id that points to an invalid item. In that // case, just null out the album cover item id. It's not optimal to do that at this low // level, but it's not trivial to find these cases quickly in an upgrade script and if we // don't do this, the album may be permanently marked as "needs rebuilding" // // ref: http://sourceforge.net/apps/trac/gallery/ticket/1172 // http://gallery.menalto.com/node/96926 if ($item->album_cover_item_id) { $item->album_cover_item_id = null; $item->save(); } return; } $input_file = $cover->file_path(); $input_item = $cover; } else { $input_file = $item->file_path(); $input_item = $item; } if ($item->thumb_dirty) { $ops["thumb"] = $item->thumb_path(); } if ($item->resize_dirty && !$item->is_album() && !$item->is_movie()) { $ops["resize"] = $item->resize_path(); } if (empty($ops)) { $item->thumb_dirty = 0; $item->resize_dirty = 0; $item->save(); return; } try { foreach ($ops as $target => $output_file) { if ($input_item->is_movie()) { // Convert the movie filename to a JPG first, delete anything that might already be there $output_file = legal_file::change_extension($output_file, "jpg"); unlink($output_file); // Run movie_extract_frame events, which can either: // - generate an output file, bypassing the ffmpeg-based movie::extract_frame // - add to the options sent to movie::extract_frame (e.g. change frame extract time, // add de-interlacing arguments to ffmpeg... see movie helper for more info) // Note that the args are similar to those of the events in gallery_graphics $movie_options_wrapper = new stdClass(); $movie_options_wrapper->movie_options = array(); module::event("movie_extract_frame", $input_file, $output_file, $movie_options_wrapper, $input_item); // If no output_file generated by events, run movie::extract_frame with movie_options clearstatcache(); if (@filesize($output_file) == 0) { try { movie::extract_frame($input_file, $output_file, $movie_options_wrapper->movie_options); } catch (Exception $e) { // Didn't work, likely because of MISSING_FFMPEG - copy missing_movie instead copy(MODPATH . "gallery/images/missing_movie.jpg", $output_file); } } $working_file = $output_file; } else { $working_file = $input_file; } foreach (self::_get_rules($target) as $rule) { $args = array($working_file, $output_file, unserialize($rule->args), $item); call_user_func_array($rule->operation, $args); $working_file = $output_file; } } if (!empty($ops["thumb"])) { if (file_exists($item->thumb_path())) { $item->thumb_dirty = 0; } else { copy(MODPATH . "gallery/images/missing_photo.png", $item->thumb_path()); } $dims = getimagesize($item->thumb_path()); $item->thumb_width = $dims[0]; $item->thumb_height = $dims[1]; } if (!empty($ops["resize"])) { if (file_exists($item->resize_path())) { $item->resize_dirty = 0; } else { copy(MODPATH . "gallery/images/missing_photo.png", $item->resize_path()); } $dims = getimagesize($item->resize_path()); $item->resize_width = $dims[0]; $item->resize_height = $dims[1]; } $item->save(); } catch (Exception $e) { // Something went wrong rebuilding the image. Leave it dirty and move on. // @todo we should handle this better. Kohana_Log::add("error", "Caught exception rebuilding image: {$item->title}\n" . $e->getMessage() . "\n" . $e->getTraceAsString()); throw $e; } }
/** * Sanitize a filename for a given type (given as "photo" or "movie") and a target file format * (given as an extension). This returns a completely legal and valid filename, * or throws an exception if the type or extension given is invalid or illegal. It tries to * maintain the filename's original extension even if it's not identical to the given extension * (e.g. don't change "JPG" or "jpeg" to "jpg"). * * Note: it is not okay if the extension given is legal but does not match the type (e.g. if * extension is "mp4" and type is "photo", it will throw an exception) * * @param string $filename (with no directory) * @param string $extension (can be uppercase or lowercase) * @param string $type (as "photo" or "movie") * @return string sanitized filename (or null if bad extension argument) */ static function sanitize_filename($filename, $extension, $type) { // Check if the type is valid - if so, get the mime types of the // original and target extensions; if not, throw an exception. $original_extension = pathinfo($filename, PATHINFO_EXTENSION); switch ($type) { case "photo": $mime_type = legal_file::get_photo_types_by_extension($extension); $original_mime_type = legal_file::get_photo_types_by_extension($original_extension); break; case "movie": $mime_type = legal_file::get_movie_types_by_extension($extension); $original_mime_type = legal_file::get_movie_types_by_extension($original_extension); break; default: throw new Exception("@todo INVALID_TYPE"); } // Check if the target extension is blank or invalid - if so, throw an exception. if (!$extension || !$mime_type) { throw new Exception("@todo ILLEGAL_EXTENSION"); } // Check if the mime types of the original and target extensions match - if not, fix it. if (!$original_extension || $mime_type != $original_mime_type) { $filename = legal_file::change_extension($filename, $extension); } // It should be a filename without a directory - remove all slashes (and backslashes). $filename = str_replace("/", "_", $filename); $filename = str_replace("\\", "_", $filename); // Remove extra dots from the filename. This will also remove extraneous underscores. $filename = legal_file::smash_extensions($filename); // It's possible that the filename has no base (e.g. ".jpg") - if so, give it a generic one. if (empty($filename) || substr($filename, 0, 1) == ".") { $filename = $type . $filename; // e.g. "photo.jpg" or "movie.mp4" } return $filename; }
public function __call($function, $args) { // Force zlib compression off. Image and movie files are already compressed and // recompressing them is CPU intensive. if (ini_get("zlib.output_compression")) { ini_set("zlib.output_compression", "Off"); } // request_uri: gallery3/var/albums/foo/bar.jpg?m=1234 $request_uri = rawurldecode(Input::instance()->server("REQUEST_URI")); // get rid of query parameters // request_uri: gallery3/var/albums/foo/bar.jpg $request_uri = preg_replace("/\\?.*/", "", $request_uri); // var_uri: gallery3/var/ $var_uri = url::file("var/"); // Make sure that the request is for a file inside var $offset = strpos(rawurldecode($request_uri), $var_uri); if ($offset !== 0) { throw new Kohana_404_Exception(); } // file_uri: albums/foo/bar.jpg $file_uri = substr($request_uri, strlen($var_uri)); // type: albums // path: foo/bar.jpg list($type, $path) = explode("/", $file_uri, 2); if ($type != "resizes" && $type != "albums" && $type != "thumbs") { throw new Kohana_404_Exception(); } // If the last element is .album.jpg, pop that off since it's not a real item $path = preg_replace("|/.album.jpg\$|", "", $path); $item = item::find_by_path($path); if (!$item->loaded()) { // We didn't turn it up. If we're looking for a .jpg then it's it's possible that we're // requesting the thumbnail for a movie. In that case, the movie file would // have been converted to a .jpg. So try some alternate types: if (preg_match('/.jpg$/', $path)) { foreach (legal_file::get_movie_extensions() as $ext) { $movie_path = preg_replace('/.jpg$/', ".{$ext}", $path); $item = item::find_by_path($movie_path); if ($item->loaded()) { break; } } } } if (!$item->loaded()) { throw new Kohana_404_Exception(); } // Make sure we have access to the item if (!access::can("view", $item)) { throw new Kohana_404_Exception(); } // Make sure we have view_full access to the original if ($type == "albums" && !access::can("view_full", $item)) { throw new Kohana_404_Exception(); } // Don't try to load a directory if ($type == "albums" && $item->is_album()) { throw new Kohana_404_Exception(); } if ($type == "albums") { $file = $item->file_path(); } else { if ($type == "resizes") { $file = $item->resize_path(); } else { $file = $item->thumb_path(); } } if (!file_exists($file)) { throw new Kohana_404_Exception(); } header("Content-Length: " . filesize($file)); header("Pragma:"); // Check that the content hasn't expired or it wasn't changed since cached expires::check(2592000, $item->updated); // We don't need to save the session for this request Session::instance()->abort_save(); expires::set(2592000, $item->updated); // 30 days // Dump out the image. If the item is a movie, then its thumbnail will be a JPG. if ($item->is_movie() && $type != "albums") { header("Content-Type: image/jpeg"); } else { header("Content-Type: {$item->mime_type}"); } // Don't use Kohana::close_buffers(false) here because that only closes all the buffers // that Kohana started. We want to close *all* buffers at this point because otherwise we're // going to buffer up whatever file we're proxying (and it may be very large). This may // affect embedding or systems with PHP's output_buffering enabled. while (ob_get_level()) { Kohana_Log::add("error", "" . print_r(ob_get_level(), 1)); if (!@ob_end_clean()) { // ob_end_clean() can return false if the buffer can't be removed for some reason // (zlib output compression buffers sometimes cause problems). break; } } readfile($file); }
/** * Add photo or movie from upload. Once we have a valid file, this generates an item model * from it. It returns the item model on success, and throws an exception and adds log entries * on failure. * @TODO: consider moving this to a common controller which is extended by various uploaders. * * @param int parent album id * @param string temp file path (analogous to $_FILES[...]["tmp_name"]) * @param string filename (analogous to $_FILES[...]["name"]) * @return object new item model */ private function _add_item($album_id, $tmp_name, $name) { $extension = pathinfo($name, PATHINFO_EXTENSION); try { $item = ORM::factory("item"); $item->name = $name; $item->title = item::convert_filename_to_title($name); $item->parent_id = $album_id; $item->set_data_file($tmp_name); if (!$extension) { throw new Exception(t("Uploaded file has no extension")); } else { if (legal_file::get_photo_extensions($extension)) { $item->type = "photo"; $item->save(); log::success("content", t("Added a photo"), html::anchor("photos/{$item->id}", t("view photo"))); } else { if (movie::allow_uploads() && legal_file::get_movie_extensions($extension)) { $item->type = "movie"; $item->save(); log::success("content", t("Added a movie"), html::anchor("movies/{$item->id}", t("view movie"))); } else { throw new Exception(t("Uploaded file has illegal extension")); } } } } catch (Exception $e) { // Log errors then re-throw exception. Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); // If we have a validation error, add an additional log entry. if ($e instanceof ORM_Validation_Exception) { Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1)); } throw $e; } return $item; }
/** * Return the width, height, mime_type, extension and duration of the given movie file. * Metadata is first generated using ffmpeg (or set to defaults if it fails), * then can be modified by other modules using movie_get_file_metadata events. * * This function and its use cases are symmetric to those of photo::get_file_metadata. * * @param string $file_path * @return array array($width, $height, $mime_type, $extension, $duration) * * Use cases in detail: * Input is standard movie type (flv/mp4/m4v) * -> return metadata from ffmpeg * Input is *not* standard movie type that is supported by ffmpeg (e.g. avi, mts...) * -> return metadata from ffmpeg * Input is *not* standard movie type that is *not* supported by ffmpeg but is legal * -> return zero width, height, and duration; mime type and extension according to legal_file * Input is illegal, unidentifiable, unreadable, or does not exist * -> throw exception * Note: movie_get_file_metadata events can change any of the above cases (except the last one). */ static function get_file_metadata($file_path) { if (!is_readable($file_path)) { throw new Exception("@todo UNREADABLE_FILE"); } $metadata = new stdClass(); $ffmpeg = movie::find_ffmpeg(); if (!empty($ffmpeg)) { // ffmpeg found - use it to get width, height, and duration. $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($file_path) . " 2>&1"; $result = `{$cmd}`; if (preg_match("/Stream.*?Video:.*?, (\\d+)x(\\d+)/", $result, $matches_res)) { if (preg_match("/Stream.*?Video:.*? \\[.*?DAR (\\d+):(\\d+).*?\\]/", $result, $matches_dar) && $matches_dar[1] >= 1 && $matches_dar[2] >= 1) { // DAR is defined - determine width based on height and DAR // (should always be int, but adding round to be sure) $matches_res[1] = round($matches_res[2] * $matches_dar[1] / $matches_dar[2]); } list($metadata->width, $metadata->height) = array($matches_res[1], $matches_res[2]); } else { list($metadata->width, $metadata->height) = array(0, 0); } if (preg_match("/Duration: (\\d+:\\d+:\\d+\\.\\d+)/", $result, $matches)) { $metadata->duration = movie::hhmmssdd_to_seconds($matches[1]); } else { if (preg_match("/duration.*?:.*?(\\d+)/", $result, $matches)) { $metadata->duration = $matches[1]; } else { $metadata->duration = 0; } } } else { // ffmpeg not found - set width, height, and duration to zero. $metadata->width = 0; $metadata->height = 0; $metadata->duration = 0; } $extension = pathinfo($file_path, PATHINFO_EXTENSION); if (!$extension || !($metadata->mime_type = legal_file::get_movie_types_by_extension($extension))) { // Extension is empty or illegal. $metadata->extension = null; $metadata->mime_type = null; } else { // Extension is legal (and mime is already set above). $metadata->extension = strtolower($extension); } // Run movie_get_file_metadata events which can modify the class. module::event("movie_get_file_metadata", $file_path, $metadata); // If the post-events results are invalid, throw an exception. Note that, unlike photos, having // zero width and height isn't considered invalid (as is the case when FFmpeg isn't installed). if (!$metadata->mime_type || !$metadata->extension || $metadata->mime_type != legal_file::get_movie_types_by_extension($metadata->extension)) { throw new Exception("@todo ILLEGAL_OR_UNINDENTIFIABLE_FILE"); } return array($metadata->width, $metadata->height, $metadata->mime_type, $metadata->extension, $metadata->duration); }
/** * Make sure that the type is valid. */ public function valid_field(Validation $v, $field) { switch ($field) { case "mime_type": if ($this->is_movie()) { $legal_values = legal_file::get_movie_types(); } else { if ($this->is_photo()) { $legal_values = legal_file::get_photo_types(); } } break; case "sort_column": if (!array_key_exists($this->sort_column, $this->object)) { $v->add_error($field, "invalid"); } break; case "sort_order": $legal_values = array("ASC", "DESC", "asc", "desc"); break; case "type": $legal_values = array("album", "photo", "movie"); break; default: $v->add_error($field, "unvalidated_field"); break; } if (isset($legal_values) && !in_array($this->{$field}, $legal_values)) { $v->add_error($field, "invalid"); } }
/** * Return the width, height, mime_type and extension of the given image file. * Metadata is first generated using getimagesize (or the legal_file mapping if it fails), * then can be modified by other modules using photo_get_file_metadata events. * * This function and its use cases are symmetric to those of photo::get_file_metadata. * * @param string $file_path * @return array array($width, $height, $mime_type, $extension) * * Use cases in detail: * Input is standard photo type (jpg/png/gif) * -> return metadata from getimagesize() * Input is *not* standard photo type that is supported by getimagesize (e.g. tif, bmp...) * -> return metadata from getimagesize() * Input is *not* standard photo type that is *not* supported by getimagesize but is legal * -> return metadata if found by photo_get_file_metadata events * Input is illegal, unidentifiable, unreadable, or does not exist * -> throw exception * Note: photo_get_file_metadata events can change any of the above cases (except the last one). */ static function get_file_metadata($file_path) { if (!is_readable($file_path)) { throw new Exception("@todo UNREADABLE_FILE"); } $metadata = new stdClass(); if ($image_info = getimagesize($file_path)) { // getimagesize worked - use its results. $metadata->width = $image_info[0]; $metadata->height = $image_info[1]; $metadata->mime_type = $image_info["mime"]; $metadata->extension = image_type_to_extension($image_info[2], false); // We prefer jpg instead of jpeg (which is returned by image_type_to_extension). if ($metadata->extension == "jpeg") { $metadata->extension = "jpg"; } } else { // getimagesize failed - try to use legal_file mapping instead. $extension = pathinfo($file_path, PATHINFO_EXTENSION); if (!$extension || !($metadata->mime_type = legal_file::get_photo_types_by_extension($extension))) { // Extension is empty or illegal. $metadata->extension = null; $metadata->mime_type = null; } else { // Extension is legal (and mime is already set above). $metadata->extension = strtolower($extension); } $metadata->width = 0; $metadata->height = 0; } // Run photo_get_file_metadata events which can modify the class. module::event("photo_get_file_metadata", $file_path, $metadata); // If the post-events results are invalid, throw an exception. if (!$metadata->width || !$metadata->height || !$metadata->mime_type || !$metadata->extension || $metadata->mime_type != legal_file::get_photo_types_by_extension($metadata->extension)) { throw new Exception("@todo ILLEGAL_OR_UNINDENTIFIABLE_FILE"); } return array($metadata->width, $metadata->height, $metadata->mime_type, $metadata->extension); }
/** * Create a default list of allowed movie MIME types and then let modules modify it. * Can be used to add legal alternatives for default MIME types. * (e.g. flv maps to video/x-flv by default, but video/flv is still legal). */ static function get_movie_types() { $types_wrapper = new stdClass(); $types_wrapper->types = array_values(legal_file::get_movie_types_by_extension()); $types_wrapper->types[] = "video/flv"; module::event("legal_movie_types", $types_wrapper); return $types_wrapper->types; }
/** * This is the task code that adds photos and albums. It first examines all the target files * and creates a set of Server_Add_Entry_Models, then runs through the list of models and adds * them one at a time. */ static function add($task) { $mode = $task->get("mode", "init"); $start = microtime(true); switch ($mode) { case "init": $task->set("mode", "build-file-list"); $task->set("dirs_scanned", 0); $task->percent_complete = 0; $task->status = t("Starting up"); batch::start(); break; case "build-file-list": // 0% to 10% // We can't fit an arbitrary number of paths in a task, so store them in a separate table. // Don't use an iterator here because we can't get enough control over it when we're dealing // with a deep hierarchy and we don't want to go over our time quota. $paths = unserialize(module::get_var("server_add", "authorized_paths")); $dirs_scanned = $task->get("dirs_scanned"); while (microtime(true) - $start < 0.5) { // Process every directory that doesn't yet have a parent id, these are the // paths that we're importing. $entry = ORM::factory("server_add_entry")->where("task_id", "=", $task->id)->where("is_directory", "=", 1)->where("checked", "=", 0)->order_by("id", "ASC")->find(); if ($entry->loaded()) { $child_paths = glob(preg_quote($entry->path) . "/*"); if (!$child_paths) { $child_paths = glob("{$entry->path}/*"); } foreach ($child_paths as $child_path) { if (!is_dir($child_path)) { $ext = strtolower(pathinfo($child_path, PATHINFO_EXTENSION)); if (!legal_file::get_extensions($ext) || !filesize($child_path)) { // Not importable, skip it. continue; } } $child_entry = ORM::factory("server_add_entry"); $child_entry->task_id = $task->id; $child_entry->path = $child_path; $child_entry->parent_id = $entry->id; // null if the parent was a staging dir $child_entry->is_directory = is_dir($child_path); $child_entry->save(); } // We've processed this entry, mark it as done. $entry->checked = 1; $entry->save(); $dirs_scanned++; } } // We have no idea how long this can take because we have no idea how deep the tree // hierarchy rabbit hole goes. Leave ourselves room here for 100 iterations and don't go // over 10% in percent_complete. $task->set("dirs_scanned", $dirs_scanned); $task->percent_complete = min($task->percent_complete + 0.1, 10); $task->status = t2("Scanned one directory", "Scanned %count directories", $dirs_scanned); if (!$entry->loaded()) { $task->set("mode", "add-files"); $task->set("total_files", ORM::factory("server_add_entry")->where("task_id", "=", $task->id)->count_all()); $task->percent_complete = 10; } break; case "add-files": // 10% to 100% $completed_files = $task->get("completed_files", 0); $total_files = $task->get("total_files"); // Ordering by id ensures that we add them in the order that we created the entries, which // will create albums first. Ignore entries which already have an Item_Model attached, // they're done. $entries = ORM::factory("server_add_entry")->where("task_id", "=", $task->id)->where("item_id", "IS", null)->order_by("id", "ASC")->limit(10)->find_all(); if ($entries->count() == 0) { // Out of entries, we're done. $task->set("mode", "done"); } $owner_id = identity::active_user()->id; foreach ($entries as $entry) { if (microtime(true) - $start > 0.5) { break; } // Look up the parent item for this entry. By now it should exist, but if none was // specified, then this belongs as a child of the current item. $parent_entry = ORM::factory("server_add_entry", $entry->parent_id); if (!$parent_entry->loaded()) { $parent = ORM::factory("item", $task->get("item_id")); } else { $parent = ORM::factory("item", $parent_entry->item_id); } $name = basename($entry->path); $title = item::convert_filename_to_title($name); if ($entry->is_directory) { $album = ORM::factory("item"); $album->type = "album"; $album->parent_id = $parent->id; $album->name = $name; $album->title = $title; $album->owner_id = $owner_id; $album->sort_order = $parent->sort_order; $album->sort_column = $parent->sort_column; $album->save(); $entry->item_id = $album->id; } else { try { $extension = strtolower(pathinfo($name, PATHINFO_EXTENSION)); if (legal_file::get_photo_extensions($extension)) { $photo = ORM::factory("item"); $photo->type = "photo"; $photo->parent_id = $parent->id; $photo->set_data_file($entry->path); $photo->name = $name; $photo->title = $title; $photo->owner_id = $owner_id; $photo->save(); $entry->item_id = $photo->id; } else { if (legal_file::get_movie_extensions($extension)) { $movie = ORM::factory("item"); $movie->type = "movie"; $movie->parent_id = $parent->id; $movie->set_data_file($entry->path); $movie->name = $name; $movie->title = $title; $movie->owner_id = $owner_id; $movie->save(); $entry->item_id = $movie->id; } else { // This should never happen, because we don't add stuff to the list that we can't // process. But just in, case.. set this to a non-null value so that we skip this // entry. $entry->item_id = 0; $task->log("Skipping unknown file type: {$entry->path}"); } } } catch (Exception $e) { // This can happen if a photo file is invalid, like a BMP masquerading as a .jpg $entry->item_id = 0; $task->log("Skipping invalid file: {$entry->path}"); } } $completed_files++; $entry->save(); } $task->set("completed_files", $completed_files); $task->status = t("Adding photos / albums (%completed of %total)", array("completed" => $completed_files, "total" => $total_files)); $task->percent_complete = $total_files ? 10 + 100 * ($completed_files / $total_files) : 100; break; case "done": batch::stop(); $task->done = true; $task->state = "success"; $task->percent_complete = 100; ORM::factory("server_add_entry")->where("task_id", "=", $task->id)->delete_all(); message::info(t2("Successfully added one photo / album", "Successfully added %count photos / albums", $task->get("completed_files"))); } }
/** * the main optimize function * * the function arguments are the same format as other graphics rules. the only "option" is $target, hence why it's renamed in the function def. * * NOTE: unlike other graphics transformations, this only uses the output file! if it isn't already there, we don't do anything. * among other things, this means that the original, full-size images are never touched. */ static function optimize($input_file, $output_file, $target, $item = null) { // see if output file exists and is writable if (is_writable($output_file)) { // see if input is a supported file type. if not, return without doing anything. $image_info = getimagesize($input_file); // [0]=w, [1]=h, [2]=type (1=GIF, 2=JPG, 3=PNG) switch ($image_info[2]) { case 1: $type_old = "gif"; $convert = module::get_var("image_optimizer", "convert_" . $target . "_gif"); break; case 2: $type_old = "jpg"; $convert = 0; // no conversion possible here... break; case 3: $type_old = "png"; $convert = module::get_var("image_optimizer", "convert_" . $target . "_png"); break; default: // not a supported file type return; } } else { // file doesn't exist or isn't writable return; } // set new file type $type = $convert ? $convert : $type_old; // convert image type (if applicable). this isn't necessarily lossless. if ($convert) { $output_file_new = legal_file::change_extension($output_file, $type); // perform conversion using standard Gallery toolkit (GD/ImageMagick/GraphicsMagick) // note: if input was a GIF, this will kill animation $image = Image::factory($output_file)->quality(module::get_var("gallery", "image_quality"))->save($output_file_new); // if filenames are different, move the new on top of the old if ($output_file != $output_file_new) { /** * HACK ALERT! Gallery3 is still broken with regard to treating thumb/resizes with proper extensions. This doesn't try to fix that. * Normal Gallery setup: * photo thumb -> keep photo type, keep photo extension * album thumb -> keep source photo thumb type, change extension to jpg (i.e. ".album.jpg" even for png/gif) * Also, missing_photo.png is similarly altered... * * Anyway, to avoid many rewrites of core functions (and not-easily-reversible database changes), this module also forces the extension to stay the same. * With image optimizer conversion: * photo thumb -> change type, keep photo extension (i.e. "photo.png" photo becomes "photo.png" thumb even if type has changed) * album thumb -> keep source photo thumb type, change extension to jpg (i.e. ".album.jpg" even for png/gif) */ rename($output_file_new, $output_file); } } // get module variables $configstatus = module::get_var("image_optimizer", "configstatus_" . $type); $path = module::get_var("image_optimizer", "path_" . $type); $opt = module::get_var("image_optimizer", "optlevel_" . $target . "_" . $type); $meta = module::get_var("image_optimizer", "metastrip_" . $target); $prog = module::get_var("image_optimizer", "progressive_" . $target); // make sure the toolkit is configured correctly and we want to use it - if not, return without doing anything. if ($configstatus) { if (!$prog && !$meta && !$opt) { // nothing to do! return; } } else { // not configured correctly return; } /** * do the actual optimization */ // set parameters switch ($type) { case "jpg": $exec_args = $opt ? " -optimize" : ""; $exec_args .= $meta ? " -copy none" : " -copy all"; $exec_args .= $prog ? " -progressive" : ""; $exec_args .= " -outfile "; break; case "png": $exec_args = $opt ? " -o" . $opt : ""; $exec_args .= $meta ? " -strip all" : ""; $exec_args .= $prog ? " -i 1" : ""; $exec_args .= " -quiet -out "; break; case "gif": $exec_args = $opt ? " --optimize=3" : ""; // levels 1 and 2 don't really help us $exec_args .= $meta ? " --no-comments --no-extensions --no-names" : " --same-comments --same-extensions --same-names"; $exec_args .= $prog ? " --interlace" : " --same-interlace"; $exec_args .= " --careful --output "; break; } // run it - from output_file to tmp_file. $tmp_file = image_optimizer::make_temp_name($output_file); exec(escapeshellcmd($path) . $exec_args . escapeshellarg($tmp_file) . " " . escapeshellarg($output_file), $exec_output, $exec_status); if ($exec_status || !filesize($tmp_file)) { // either a blank/nonexistant file or an error - do nothing to the output, but log an error and delete the temp (if any) Kohana_Log::add("error", "image_optimizer optimization failed on " . $output_file); unlink($tmp_file); } else { // worked - move temp to output rename($tmp_file, $output_file); } }
public function add() { access::verify_csrf(); $form = watermark::get_add_form(); // For TEST_MODE, we want to simulate a file upload. Because this is not a true upload, Forge's // validation logic will correctly reject it. So, we skip validation when we're running tests. if (TEST_MODE || $form->validate()) { $file = $_POST["file"]; // Forge prefixes files with "uploadfile-xxxxxxx" for uniqueness $name = preg_replace("/uploadfile-[^-]+-(.*)/", '$1', basename($file)); try { list($width, $height, $mime_type, $extension) = photo::get_file_metadata($file); // Sanitize filename, which ensures a valid extension. This renaming prevents the issues // addressed in ticket #1855, where an image that looked valid (header said jpg) with a // php extension was previously accepted without changing its extension. $name = legal_file::sanitize_filename($name, $extension, "photo"); } catch (Exception $e) { message::error(t("Invalid or unidentifiable image file")); system::delete_later($file); return; } rename($file, VARPATH . "modules/watermark/{$name}"); module::set_var("watermark", "name", $name); module::set_var("watermark", "width", $width); module::set_var("watermark", "height", $height); module::set_var("watermark", "mime_type", $mime_type); module::set_var("watermark", "position", $form->add_watermark->position->value); module::set_var("watermark", "transparency", $form->add_watermark->transparency->value); $this->_update_graphics_rules(); system::delete_later($file); message::success(t("Watermark saved")); log::success("watermark", t("Watermark saved")); json::reply(array("result" => "success", "location" => url::site("admin/watermarks"))); } else { json::reply(array("result" => "error", "html" => (string) $form)); } // Override the application/json mime type for iframe compatibility. See ticket #2022. header("Content-Type: text/plain; charset=" . Kohana::CHARSET); }
public function add() { access::verify_csrf(); $form = watermark::get_add_form(); if ($form->validate()) { $file = $_POST["file"]; $pathinfo = pathinfo($file); // Forge prefixes files with "uploadfile-xxxxxxx" for uniqueness $name = preg_replace("/uploadfile-[^-]+-(.*)/", '$1', $pathinfo["basename"]); $name = legal_file::smash_extensions($name); if (!($image_info = getimagesize($file)) || !in_array($image_info[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG))) { message::error(t("Unable to identify this image file")); @unlink($file); return; } if (!in_array($pathinfo["extension"], legal_file::get_photo_extensions())) { switch ($image_info[2]) { case IMAGETYPE_GIF: $name = legal_file::change_extension($name, "gif"); break; case IMAGETYPE_JPEG: $name = legal_file::change_extension($name, "jpg"); break; case IMAGETYPE_PNG: $name = legal_file::change_extension($name, "png"); break; } } rename($file, VARPATH . "modules/watermark/{$name}"); module::set_var("watermark", "name", $name); module::set_var("watermark", "width", $image_info[0]); module::set_var("watermark", "height", $image_info[1]); module::set_var("watermark", "mime_type", $image_info["mime"]); module::set_var("watermark", "position", $form->add_watermark->position->value); module::set_var("watermark", "transparency", $form->add_watermark->transparency->value); $this->_update_graphics_rules(); @unlink($file); message::success(t("Watermark saved")); log::success("watermark", t("Watermark saved")); json::reply(array("result" => "success", "location" => url::site("admin/watermarks"))); } else { // rawurlencode the results because the JS code that uploads the file buffers it in an // iframe which entitizes the HTML and makes it difficult for the JS to process. If we url // encode it now, it passes through cleanly. See ticket #797. json::reply(array("result" => "error", "html" => rawurlencode((string) $form))); } // Override the application/json mime type. The dialog based HTML uploader uses an iframe to // buffer the reply, and on some browsers (Firefox 3.6) it does not know what to do with the // JSON that it gets back so it puts up a dialog asking the user what to do with it. So force // the encoding type back to HTML for the iframe. // See: http://jquery.malsup.com/form/#file-upload header("Content-Type: text/html; charset=" . Kohana::CHARSET); }
/** * Sanitize a filename into something presentable as an item title * @param string $filename * @return string title */ static function convert_filename_to_title($filename) { $title = strtr($filename, "_", " "); $exts = legal_file::get_extensions(); foreach ($exts as $ext) { $title = preg_replace("/\\." . $ext . "\$/", "", $title); } $title = preg_replace("/ +/", " ", $title); return $title; }
public function sanitize_filename_with_corrections_test() { $this->assert_equal("foo_bar", legal_file::sanitize_dirname("/foo/bar/")); $this->assert_equal("foo_bar", legal_file::sanitize_dirname("\\foo\\bar\\")); $this->assert_equal(".foo..bar", legal_file::sanitize_dirname(".foo..bar.")); $this->assert_equal("foo_bar", legal_file::sanitize_dirname("_foo__bar_")); $this->assert_equal("album", legal_file::sanitize_dirname("_")); $this->assert_equal("album", legal_file::sanitize_dirname(null)); }
static function cron() { $owner_id = 2; $debug = !empty($_SERVER['argv']) && isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == "debug"; // Login as Admin $debug and print "Starting user session\n"; $session = Session::instance(); $session->delete("user"); auth::login(IdentityProvider::instance()->admin_user()); // check if some folders are still unprocessed from previous run $entry = ORM::factory("folder_sync_entry")->where("is_directory", "=", 1)->where("checked", "=", 0)->order_by("id", "ASC")->find(); if (!$entry->loaded()) { $debug and print "Adding default folders\n"; $paths = unserialize(module::get_var("folder_sync", "authorized_paths")); foreach (array_keys($paths) as $path) { if (folder_sync::is_valid_path($path)) { $path = rtrim($path, "/"); $debug and print " * {$path}\n"; $entry = ORM::factory("folder_sync_entry")->where("is_directory", "=", 1)->where("path", "=", $path)->find(); if ($entry && $entry->loaded()) { $entry->checked = 0; $entry->save(); } else { $entry = ORM::factory("folder_sync_entry"); $entry->path = $path; $entry->is_directory = 1; $entry->parent_id = null; $entry->item_id = module::get_var("folder_sync", "destination_album_id", 1); $entry->md5 = ''; $entry->save(); } } } } // Scan and add files $debug and print "Starting the loop\n"; $done = false; $limit = 500; while (!$done && $limit > 0) { $debug and print "Loop started: Limit = {$limit}\n"; $entry = ORM::factory("folder_sync_entry")->where("is_directory", "=", 1)->where("checked", "=", 0)->order_by("id", "ASC")->find(); if ($entry->loaded()) { // get the parrent $parent = ORM::factory("item", $entry->item_id); if (!$parent->loaded()) { $debug and print "Deleting entry #{$entry->id} pointing to missing item #{$entry->item_id}\n"; //$entry->delete(); //continue; } $debug and print "Scanning folder: {$entry->path}\n"; $child_paths = glob(preg_quote($entry->path) . "/*"); if (!$child_paths) { $child_paths = glob("{$entry->path}/*"); } foreach ($child_paths as $child_path) { $name = basename($child_path); $title = item::convert_filename_to_title($name); $debug and print "Found {$child_path}..."; if (is_dir($child_path)) { $debug and print "folder\n"; $entry_exists = ORM::factory("folder_sync_entry")->where("is_directory", "=", 1)->where("path", "=", $child_path)->find(); if ($entry_exists && $entry_exists->loaded()) { $debug and print "Folder is already imported, marked to re-sync.\n"; $entry_exists->checked = 0; $entry_exists->save(); } else { $debug and print "Adding new folder.\n"; $album = ORM::factory("item"); $album->type = "album"; $album->parent_id = $parent->id; $album->name = $name; $album->title = $title; $album->owner_id = $owner_id; $album->sort_order = $parent->sort_order; $album->sort_column = $parent->sort_column; $album->save(); $child_entry = ORM::factory("folder_sync_entry"); $child_entry->path = $child_path; $child_entry->parent_id = $entry->id; $child_entry->item_id = $album->id; $child_entry->is_directory = 1; $child_entry->md5 = ""; $child_entry->save(); } } else { $debug and print "file\n"; $ext = strtolower(pathinfo($child_path, PATHINFO_EXTENSION)); if (!in_array($ext, legal_file::get_extensions()) || !filesize($child_path)) { // Not importable, skip it. $debug and print "File is incompatible. Skipping.\n"; continue; } // check if file was already imported $entry_exists = ORM::factory("folder_sync_entry")->where("is_directory", "=", 0)->where("path", "=", $child_path)->find(); if ($entry_exists && $entry_exists->loaded()) { $debug and print "Image is already imported..."; if (empty($entry_exists->added) || empty($entry_exists->md5) || $entry_exists->added != filemtime($child_path) || $entry_exists->md5 != md5_file($child_path)) { $item = ORM::factory("item", $entry_exists->item_id); if ($item->loaded()) { $item->set_data_file($child_path); $debug and print "updating.\n"; try { $item->save(); } catch (ORM_Validation_Exception $e) { print "Error saving the image (ID = {$item->id}) with the new data file.\n"; exit; } } else { $debug and print "deleting.\n"; $entry_exists->delete(); } } else { $debug and print "skipping.\n"; } // since it's an update, don't count too much towards the limit $limit -= 0.25; } else { if (in_array($ext, legal_file::get_photo_extensions())) { $debug and print "Adding new photo.\n"; $item = ORM::factory("item"); $item->type = "photo"; $item->parent_id = $parent->id; $item->set_data_file($child_path); $item->name = $name; $item->title = $title; $item->owner_id = $owner_id; $item->save(); } else { if (in_array($ext, legal_file::get_movie_extensions())) { $debug and print "Adding new video.\n"; $item = ORM::factory("item"); $item->type = "movie"; $item->parent_id = $parent->id; $item->set_data_file($child_path); $item->name = $name; $item->title = $title; $item->owner_id = $owner_id; $item->save(); } } $entry_exists = ORM::factory("folder_sync_entry"); $entry_exists->path = $child_path; $entry_exists->parent_id = $entry->id; // null if the parent was a staging dir $entry_exists->is_directory = 0; $entry_exists->md5 = md5_file($child_path); $entry_exists->added = filemtime($child_path); $entry_exists->item_id = $item->id; $entry_exists->save(); $limit--; } } // Did we hit the limit? if ($limit <= 0) { $debug and print "Reached the limit. Exiting.\n"; exit; } } // We've processed this entry unless we reached a limit. if ($limit > 0) { $entry->checked = 1; $entry->save(); } } else { $done = true; $debug and print "All folders are processed. Exiting.\n"; } } // process deletes if (module::get_var("folder_sync", "process_deletes", false)) { $entries = ORM::factory("folder_sync_entry")->order_by("id", "ASC")->find_all(); foreach ($entries as $entry) { if (!file_exists($entry->path) && $entry->item_id > 1) { $item = ORM::factory("item", $entry->item_id); if ($item->loaded()) { $item->delete(); } } } } exit; }
static function upgrade($version) { $db = Database::instance(); if ($version == 1) { module::set_var("gallery", "date_format", "Y-M-d"); module::set_var("gallery", "date_time_format", "Y-M-d H:i:s"); module::set_var("gallery", "time_format", "H:i:s"); module::set_version("gallery", $version = 2); } if ($version == 2) { module::set_var("gallery", "show_credits", 1); module::set_version("gallery", $version = 3); } if ($version == 3) { $db->query("CREATE TABLE {caches} (\n `id` varchar(255) NOT NULL,\n `tags` varchar(255),\n `expiration` int(9) NOT NULL,\n `cache` text,\n PRIMARY KEY (`id`),\n KEY (`tags`))\n DEFAULT CHARSET=utf8;"); module::set_version("gallery", $version = 4); } if ($version == 4) { Cache::instance()->delete_all(); $db->query("ALTER TABLE {caches} MODIFY COLUMN `cache` LONGBLOB"); module::set_version("gallery", $version = 5); } if ($version == 5) { Cache::instance()->delete_all(); $db->query("ALTER TABLE {caches} DROP COLUMN `id`"); $db->query("ALTER TABLE {caches} ADD COLUMN `key` varchar(255) NOT NULL"); $db->query("ALTER TABLE {caches} ADD COLUMN `id` int(9) NOT NULL auto_increment PRIMARY KEY"); module::set_version("gallery", $version = 6); } if ($version == 6) { module::clear_var("gallery", "version"); module::set_version("gallery", $version = 7); } if ($version == 7) { $groups = identity::groups(); $permissions = ORM::factory("permission")->find_all(); foreach ($groups as $group) { foreach ($permissions as $permission) { // Update access intents $db->query("ALTER TABLE {access_intents} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) DEFAULT NULL"); // Update access cache if ($permission->name === "view") { $db->query("ALTER TABLE {items} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) DEFAULT FALSE"); } else { $db->query("ALTER TABLE {access_caches} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) NOT NULL DEFAULT FALSE"); } } } module::set_version("gallery", $version = 8); } if ($version == 8) { $db->query("ALTER TABLE {items} CHANGE COLUMN `left` `left_ptr` INT(9) NOT NULL;"); $db->query("ALTER TABLE {items} CHANGE COLUMN `right` `right_ptr` INT(9) NOT NULL;"); module::set_version("gallery", $version = 9); } if ($version == 9) { $db->query("ALTER TABLE {items} ADD KEY `weight` (`weight` DESC);"); module::set_version("gallery", $version = 10); } if ($version == 10) { module::set_var("gallery", "image_sharpen", 15); module::set_version("gallery", $version = 11); } if ($version == 11) { $db->query("ALTER TABLE {items} ADD COLUMN `relative_url_cache` varchar(255) DEFAULT NULL"); $db->query("ALTER TABLE {items} ADD COLUMN `slug` varchar(255) DEFAULT NULL"); // This is imperfect since some of the slugs may contain invalid characters, but it'll do // for now because we don't want a lengthy operation here. $db->query("UPDATE {items} SET `slug` = `name`"); // Flush all path caches because we're going to start urlencoding them. $db->query("UPDATE {items} SET `relative_url_cache` = NULL, `relative_path_cache` = NULL"); module::set_version("gallery", $version = 12); } if ($version == 12) { if (module::get_var("gallery", "active_site_theme") == "default") { module::set_var("gallery", "active_site_theme", "wind"); } if (module::get_var("gallery", "active_admin_theme") == "admin_default") { module::set_var("gallery", "active_admin_theme", "admin_wind"); } module::set_version("gallery", $version = 13); } if ($version == 13) { // Add rules for generating our thumbnails and resizes Database::instance()->query("UPDATE {graphics_rules} SET `operation` = CONCAT('gallery_graphics::', `operation`);"); module::set_version("gallery", $version = 14); } if ($version == 14) { $sidebar_blocks = block_manager::get_active("site_sidebar"); if (empty($sidebar_blocks)) { $available_blocks = block_manager::get_available_site_blocks(); foreach (array_keys(block_manager::get_available_site_blocks()) as $id) { $sidebar_blocks[] = explode(":", $id); } block_manager::set_active("site_sidebar", $sidebar_blocks); } module::set_version("gallery", $version = 15); } if ($version == 15) { module::set_var("gallery", "identity_provider", "user"); module::set_version("gallery", $version = 16); } // Convert block keys to an md5 hash of the module and block name if ($version == 16) { foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $location) { $blocks = block_manager::get_active($location); $new_blocks = array(); foreach ($blocks as $block) { $new_blocks[md5("{$block[0]}:{$block[1]}")] = $block; } block_manager::set_active($location, $new_blocks); } module::set_version("gallery", $version = 17); } // We didn't like md5 hashes so convert block keys back to random keys to allow duplicates. if ($version == 17) { foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $location) { $blocks = block_manager::get_active($location); $new_blocks = array(); foreach ($blocks as $block) { $new_blocks[random::int()] = $block; } block_manager::set_active($location, $new_blocks); } module::set_version("gallery", $version = 18); } // Rename blocks_site.sidebar to blocks_site_sidebar if ($version == 18) { $blocks = block_manager::get_active("site.sidebar"); block_manager::set_active("site_sidebar", $blocks); module::clear_var("gallery", "blocks_site.sidebar"); module::set_version("gallery", $version = 19); } // Set a default for the number of simultaneous uploads // Version 20 was reverted in 57adefc5baa7a2b0dfcd3e736e80c2fa86d3bfa2, so skip it. if ($version == 19 || $version == 20) { module::set_var("gallery", "simultaneous_upload_limit", 5); module::set_version("gallery", $version = 21); } // Update the graphics rules table so that the maximum height for resizes is 640 not 480. // Fixes ticket #671 if ($version == 21) { $resize_rule = ORM::factory("graphics_rule")->where("id", "=", "2")->find(); // make sure it hasn't been changed already $args = unserialize($resize_rule->args); if ($args["height"] == 480 && $args["width"] == 640) { $args["height"] = 640; $resize_rule->args = serialize($args); $resize_rule->save(); } module::set_version("gallery", $version = 22); } // Update slug values to be legal. We should have done this in the 11->12 upgrader, but I was // lazy. Mea culpa! if ($version == 22) { foreach (db::build()->from("items")->select("id", "slug")->where(db::expr("`slug` REGEXP '[^_A-Za-z0-9-]'"), "=", 1)->execute() as $row) { $new_slug = item::convert_filename_to_slug($row->slug); if (empty($new_slug)) { $new_slug = random::int(); } db::build()->update("items")->set("slug", $new_slug)->set("relative_url_cache", null)->where("id", "=", $row->id)->execute(); } module::set_version("gallery", $version = 23); } if ($version == 23) { $db->query("CREATE TABLE {failed_logins} (\n `id` int(9) NOT NULL auto_increment,\n `count` int(9) NOT NULL,\n `name` varchar(255) NOT NULL,\n `time` int(9) NOT NULL,\n PRIMARY KEY (`id`))\n DEFAULT CHARSET=utf8;"); module::set_version("gallery", $version = 24); } if ($version == 24) { foreach (array("logs", "tmp", "uploads") as $dir) { self::_protect_directory(VARPATH . $dir); } module::set_version("gallery", $version = 25); } if ($version == 25) { db::build()->update("items")->set("title", db::expr("`name`"))->and_open()->where("title", "IS", null)->or_where("title", "=", "")->close()->execute(); module::set_version("gallery", $version = 26); } if ($version == 26) { if (in_array("failed_logins", Database::instance()->list_tables())) { $db->query("RENAME TABLE {failed_logins} TO {failed_auths}"); } module::set_version("gallery", $version = 27); } if ($version == 27) { // Set the admin area timeout to 90 minutes module::set_var("gallery", "admin_area_timeout", 90 * 60); module::set_version("gallery", $version = 28); } if ($version == 28) { module::set_var("gallery", "credits", "Powered by <a href=\"%url\">%gallery_version</a>"); module::set_version("gallery", $version = 29); } if ($version == 29) { $db->query("ALTER TABLE {caches} ADD KEY (`key`);"); module::set_version("gallery", $version = 30); } if ($version == 30) { module::set_var("gallery", "maintenance_mode", 0); module::set_version("gallery", $version = 31); } if ($version == 31) { $db->query("ALTER TABLE {modules} ADD COLUMN `weight` int(9) DEFAULT NULL"); $db->query("ALTER TABLE {modules} ADD KEY (`weight`)"); db::update("modules")->set("weight", db::expr("`id`"))->execute(); module::set_version("gallery", $version = 32); } if ($version == 32) { $db->query("ALTER TABLE {items} ADD KEY (`left_ptr`)"); module::set_version("gallery", $version = 33); } if ($version == 33) { $db->query("ALTER TABLE {access_caches} ADD KEY (`item_id`)"); module::set_version("gallery", $version = 34); } if ($version == 34) { module::set_var("gallery", "visible_title_length", 15); module::set_version("gallery", $version = 35); } if ($version == 35) { module::set_var("gallery", "favicon_url", "lib/images/favicon.ico"); module::set_version("gallery", $version = 36); } if ($version == 36) { module::set_var("gallery", "email_from", "*****@*****.**"); module::set_var("gallery", "email_reply_to", "*****@*****.**"); module::set_var("gallery", "email_line_length", 70); module::set_var("gallery", "email_header_separator", serialize("\n")); module::set_version("gallery", $version = 37); } // Changed our minds and decided that the initial value should be empty // But don't just reset it blindly, only do it if the value is version 37 default if ($version == 37) { $email = module::get_var("gallery", "email_from", ""); if ($email == "*****@*****.**") { module::set_var("gallery", "email_from", ""); } $email = module::get_var("gallery", "email_reply_to", ""); if ($email == "*****@*****.**") { module::set_var("gallery", "email_reply_to", ""); } module::set_version("gallery", $version = 38); } if ($version == 38) { module::set_var("gallery", "show_user_profiles_to", "registered_users"); module::set_version("gallery", $version = 39); } if ($version == 39) { module::set_var("gallery", "extra_binary_paths", "/usr/local/bin:/opt/local/bin:/opt/bin"); module::set_version("gallery", $version = 40); } if ($version == 40) { module::clear_var("gallery", "_cache"); module::set_version("gallery", $version = 41); } if ($version == 41) { $db->query("TRUNCATE TABLE {caches}"); $db->query("ALTER TABLE {caches} DROP INDEX `key`, ADD UNIQUE `key` (`key`)"); module::set_version("gallery", $version = 42); } if ($version == 42) { $db->query("ALTER TABLE {items} CHANGE `description` `description` text DEFAULT NULL"); module::set_version("gallery", $version = 43); } if ($version == 43) { $db->query("ALTER TABLE {items} CHANGE `rand_key` `rand_key` DECIMAL(11, 10)"); module::set_version("gallery", $version = 44); } if ($version == 44) { $db->query("ALTER TABLE {messages} CHANGE `value` `value` text default NULL"); module::set_version("gallery", $version = 45); } if ($version == 45) { // Splice the upgrade_checker block into the admin dashboard at the top // of the page, but under the welcome block if it's in the first position. $blocks = block_manager::get_active("dashboard_center"); $index = count($blocks) && current($blocks) == array("gallery", "welcome") ? 1 : 0; array_splice($blocks, $index, 0, array(random::int() => array("gallery", "upgrade_checker"))); block_manager::set_active("dashboard_center", $blocks); module::set_var("gallery", "upgrade_checker_auto_enabled", true); module::set_version("gallery", $version = 46); } if ($version == 46) { module::set_var("gallery", "apple_touch_icon_url", "lib/images/apple-touch-icon.png"); module::set_version("gallery", $version = 47); } if ($version == 47 || $version == 48) { // Add configuration variable to set timezone. Defaults to the currently // used timezone (from PHP configuration). Note that in v48 we were // setting this value incorrectly, so we're going to stomp this value for v49. module::set_var("gallery", "timezone", null); module::set_version("gallery", $version = 49); } if ($version == 49) { // In v49 we changed the Item_Model validation code to disallow files with two dots in them, // but we didn't rename any files which fail to validate, so as soon as you do anything to // change those files (eg. as a side effect of getting the url or file path) it fails to // validate. Fix those here. This might be slow, but if it times out it can just pick up // where it left off. foreach (db::build()->from("items")->select("id")->where("type", "<>", "album")->where(db::expr("`name` REGEXP '\\\\..*\\\\.'"), "=", 1)->order_by("id", "asc")->execute() as $row) { set_time_limit(30); $item = ORM::factory("item", $row->id); $item->name = legal_file::smash_extensions($item->name); $item->save(); } module::set_version("gallery", $version = 50); } if ($version == 50) { // In v51, we added a lock_timeout variable so that administrators could edit the time out // from 1 second to a higher variable if their system runs concurrent parallel uploads for // instance. module::set_var("gallery", "lock_timeout", 1); module::set_version("gallery", $version = 51); } if ($version == 51) { // In v52, we added functions to the legal_file helper that map photo and movie file // extensions to their mime types (and allow extension of the list by other modules). During // this process, we correctly mapped m4v files to video/x-m4v, correcting a previous error // where they were mapped to video/mp4. This corrects the existing items. db::build()->update("items")->set("mime_type", "video/x-m4v")->where("name", "REGEXP", "\\.m4v\$")->execute(); module::set_version("gallery", $version = 52); } if ($version == 52) { // In v53, we added the ability to change the default time used when extracting frames from // movies. Previously we hard-coded this at 3 seconds, so we use that as the default. module::set_var("gallery", "movie_extract_frame_time", 3); module::set_version("gallery", $version = 53); } if ($version == 53) { // In v54, we changed how we check for name and slug conflicts in Item_Model. Previously, // we checked the whole filename. As a result, "foo.jpg" and "foo.png" were not considered // conflicting if their slugs were different (a rare case in practice since server_add and // uploader would give them both the same slug "foo"). Now, we check the filename without its // extension. This upgrade stanza fixes any conflicts where they were previously allowed. // This might be slow, but if it times out it can just pick up where it left off. // Find and loop through each conflict (e.g. "foo.jpg", "foo.png", and "foo.flv" are one // conflict; "bar.jpg", "bar.png", and "bar.flv" are another) foreach (db::build()->select_distinct(array("parent_base_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))")))->select(array("C" => "COUNT(\"*\")"))->from("items")->where("type", "<>", "album")->having("C", ">", 1)->group_by("parent_base_name")->execute() as $conflict) { list($parent_id, $base_name) = explode(":", $conflict->parent_base_name, 2); $base_name_escaped = Database::escape_for_like($base_name); // Loop through the items for each conflict foreach (db::build()->from("items")->select("id")->where("type", "<>", "album")->where("parent_id", "=", $parent_id)->where("name", "LIKE", "{$base_name_escaped}.%")->limit(1000000)->offset(1)->execute() as $row) { set_time_limit(30); $item = ORM::factory("item", $row->id); $item->name = $item->name; // this will force Item_Model to check for conflicts on save $item->save(); } } module::set_version("gallery", $version = 54); } if ($version == 54) { $db->query("ALTER TABLE {items} ADD KEY `relative_path_cache` (`relative_path_cache`)"); module::set_version("gallery", $version = 55); } if ($version == 55) { // In v56, we added the ability to change the default behavior regarding movie uploads. It // can be set to "always", "never", or "autodetect" to match the previous behavior where they // are allowed only if FFmpeg is found. module::set_var("gallery", "movie_allow_uploads", "autodetect"); module::set_version("gallery", $version = 56); } if ($version == 56) { // Cleanup possible instances where resize_dirty of albums or movies was set to 0. This is // unlikely to have occurred, and doesn't currently matter much since albums and movies don't // have resize images anyway. However, it may be useful to be consistent here going forward. db::build()->update("items")->set("resize_dirty", 1)->where("type", "<>", "photo")->execute(); module::set_version("gallery", $version = 57); } if ($version == 57) { // In v58 we changed the Item_Model validation code to disallow files or directories with // backslashes in them, and we need to fix any existing items that have them. This is // pretty unlikely, as having backslashes would have probably already caused other issues for // users, but we should check anyway. This might be slow, but if it times out it can just // pick up where it left off. foreach (db::build()->from("items")->select("id")->where(db::expr("`name` REGEXP '\\\\\\\\'"), "=", 1)->order_by("id", "asc")->execute() as $row) { set_time_limit(30); $item = ORM::factory("item", $row->id); $item->name = str_replace("\\", "_", $item->name); $item->save(); } module::set_version("gallery", $version = 58); } }
/** * Rebuild the thumb and resize for the given item. * @param Item_Model $item */ static function generate($item) { if ($item->is_album()) { if (!($cover = $item->album_cover())) { // This album has no cover; there's nothing to generate. Because of an old bug, it's // possible that there's an album cover item id that points to an invalid item. In that // case, just null out the album cover item id. It's not optimal to do that at this low // level, but it's not trivial to find these cases quickly in an upgrade script and if we // don't do this, the album may be permanently marked as "needs rebuilding" // // ref: http://sourceforge.net/apps/trac/gallery/ticket/1172 // http://gallery.menalto.com/node/96926 if ($item->album_cover_item_id) { $item->album_cover_item_id = null; $item->save(); } return; } $input_file = $cover->file_path(); $input_item = $cover; } else { $input_file = $item->file_path(); $input_item = $item; } if ($item->thumb_dirty) { $ops["thumb"] = $item->thumb_path(); } if ($item->resize_dirty && !$item->is_album() && !$item->is_movie()) { $ops["resize"] = $item->resize_path(); } if (empty($ops)) { $item->thumb_dirty = 0; $item->resize_dirty = 0; $item->save(); return; } try { foreach ($ops as $target => $output_file) { if ($input_item->is_movie()) { // Convert the movie to a JPG first $output_file = legal_file::change_extension($output_file, "jpg"); try { movie::extract_frame($input_file, $output_file); } catch (Exception $e) { // Assuming this is MISSING_FFMPEG for now copy(MODPATH . "gallery/images/missing_movie.jpg", $output_file); } $working_file = $output_file; } else { $working_file = $input_file; } foreach (self::_get_rules($target) as $rule) { $args = array($working_file, $output_file, unserialize($rule->args), $item); call_user_func_array($rule->operation, $args); $working_file = $output_file; } } if (!empty($ops["thumb"])) { if (file_exists($item->thumb_path())) { $item->thumb_dirty = 0; } else { copy(MODPATH . "gallery/images/missing_photo.png", $item->thumb_path()); } $dims = getimagesize($item->thumb_path()); $item->thumb_width = $dims[0]; $item->thumb_height = $dims[1]; } if (!empty($ops["resize"])) { if (file_exists($item->resize_path())) { $item->resize_dirty = 0; } else { copy(MODPATH . "gallery/images/missing_photo.png", $item->resize_path()); } $dims = getimagesize($item->resize_path()); $item->resize_width = $dims[0]; $item->resize_height = $dims[1]; } $item->save(); } catch (Exception $e) { // Something went wrong rebuilding the image. Leave it dirty and move on. // @todo we should handle this better. Kohana_Log::add("error", "Caught exception rebuilding image: {$item->title}\n" . $e->getMessage() . "\n" . $e->getTraceAsString()); throw $e; } }
public function find_by_path_with_flv_test() { $parent = test::random_album(); $flv = test::random_movie($parent); $flv_path = "{$parent->name}/{$flv->name}"; $jpg_path = legal_file::change_extension($flv_path, "jpg"); // Check normal operation. $this->assert_equal($flv->id, item::find_by_path($flv_path, "albums")->id); $this->assert_equal($flv->id, item::find_by_path($jpg_path, "thumbs")->id); $this->assert_equal($flv->id, item::find_by_path($flv_path)->id); // Check that we don't get false positives. $this->assert_equal(null, item::find_by_path($jpg_path, "albums")->id); $this->assert_equal(null, item::find_by_path($flv_path, "thumbs")->id); $this->assert_equal(null, item::find_by_path($jpg_path)->id); // Check normal operation without relative path cache. self::_remove_relative_path_caches(); $this->assert_equal($flv->id, item::find_by_path($flv_path, "albums")->id); self::_remove_relative_path_caches(); $this->assert_equal($flv->id, item::find_by_path($jpg_path, "thumbs")->id); self::_remove_relative_path_caches(); $this->assert_equal($flv->id, item::find_by_path($flv_path)->id); // Check that we don't get false positives without relative path cache. self::_remove_relative_path_caches(); $this->assert_equal(null, item::find_by_path($jpg_path, "albums")->id); $this->assert_equal(null, item::find_by_path($flv_path, "thumbs")->id); $this->assert_equal(null, item::find_by_path($jpg_path)->id); }
/** * Return the width, height, mime_type, extension and duration of the given movie file. */ static function get_file_metadata($file_path) { $ffmpeg = movie::find_ffmpeg(); if (empty($ffmpeg)) { throw new Exception("@todo MISSING_FFMPEG"); } $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($file_path) . " 2>&1"; $result = `{$cmd}`; if (preg_match("/Stream.*?Video:.*?, (\\d+)x(\\d+)/", $result, $matches_res)) { if (preg_match("/Stream.*?Video:.*? \\[.*?DAR (\\d+):(\\d+).*?\\]/", $result, $matches_dar) && $matches_dar[1] >= 1 && $matches_dar[2] >= 1) { // DAR is defined - determine width based on height and DAR // (should always be int, but adding round to be sure) $matches_res[1] = round($matches_res[2] * $matches_dar[1] / $matches_dar[2]); } list($width, $height) = array($matches_res[1], $matches_res[2]); } else { list($width, $height) = array(0, 0); } $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION)); $extension = $extension ? $extension : "flv"; // No extension? Assume FLV. $mime_type = legal_file::get_movie_types_by_extension($extension); $mime_type = $mime_type ? $mime_type : "video/x-flv"; // No MIME found? Default to video/x-flv. if (preg_match("/Duration: (\\d+):(\\d+):(\\d+\\.\\d+)/", $result, $matches)) { $duration = 3600 * $matches[1] + 60 * $matches[2] + $matches[3]; } else { if (preg_match("/duration.*?:.*?(\\d+)/", $result, $matches)) { $duration = $matches[1]; } else { $duration = 0; } } return array($width, $height, $mime_type, $extension, $duration); }