diff --git a/.gitignore b/.gitignore index c47758df..b1d70c05 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ thumbs data sql.log shimmie.log +!lib/images ext/admin ext/artists ext/autocomplete diff --git a/core/extension.class.php b/core/extension.class.php index 422edf53..9b514318 100644 --- a/core/extension.class.php +++ b/core/extension.class.php @@ -98,6 +98,7 @@ abstract class SimpleExtension implements Extension { public function receive_event(Event $event) { $name = get_class($event); + // this is rather clever.. $name = "on".str_replace("Event", "", $name); if(method_exists($this->_child, $name)) { $this->_child->$name($event); @@ -133,15 +134,50 @@ abstract class DataHandlerExtension implements Extension { if(is_null($this->theme)) $this->theme = get_theme_object($this); if(($event instanceof DataUploadEvent) && $this->supported_ext($event->type) && $this->check_contents($event->tmpname)) { + if(!move_upload_to_archive($event)) return; send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); - $image = $this->create_image_from_data(warehouse_path("images", $event->hash), $event->metadata); - if(is_null($image)) { - throw new UploadException("Data handler failed to create image object from data"); + + /* Check if we are replacing an image */ + if (array_key_exists('replace',$event->metadata) && isset($event->metadata['replace'])) + { + /* hax: This seems like such a dirty way to do this.. */ + + /* Validate things */ + $image_id = int_escape($event->metadata['replace']); + + /* Check to make sure the image exists. */ + $existing = Image::by_id($image_id); + + if(is_null($existing)) { + throw new UploadException("Image to replace does not exist!"); + } + if ($existing->hash === $event->metadata['hash']) { + throw new UploadException("The uploaded image is the same as the one to replace."); + } + + // even more hax.. + $event->metadata['tags'] = $existing->get_tag_list(); + $image = $this->create_image_from_data(warehouse_path("images", $event->metadata['hash']), $event->metadata); + + if(is_null($image)) { + throw new UploadException("Data handler failed to create image object from data"); + } + + $ire = new ImageReplaceEvent($image_id, $image); + send_event($ire); + $event->image_id = $image_id; + } + else + { + $image = $this->create_image_from_data(warehouse_path("images", $event->hash), $event->metadata); + if(is_null($image)) { + throw new UploadException("Data handler failed to create image object from data"); + } + $iae = new ImageAdditionEvent($event->user, $image); + send_event($iae); + $event->image_id = $iae->image->id; } - $iae = new ImageAdditionEvent($event->user, $image); - send_event($iae); - $event->image_id = $iae->image->id; } if(($event instanceof ThumbnailGenerationEvent) && $this->supported_ext($event->type)) { diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index 944d9e1c..e467e2db 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -457,6 +457,16 @@ class Image { unlink($this->get_thumb_filename()); } + /** + * This function removes an image (and thumbnail) from the DISK ONLY. + * It DOES NOT remove anything from the database. + */ + public function remove_image_only() { + log_info("core-image", "Removed Image File ({$this->hash})"); + unlink($this->get_image_filename()); + unlink($this->get_thumb_filename()); + } + /** * Someone please explain this * diff --git a/ext/image/main.php b/ext/image/main.php index 86a2c71a..1908d6f5 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -46,6 +46,32 @@ class ImageDeletionEvent extends Event { } } +/* + * ImageReplaceEvent: + * $id -- the ID of the image to replace + * $image -- the image object of the new image to use + * + * This function replaces an image. Effectively it only + * replaces the image file contents and leaves the tags + * and such the same. + */ +class ImageReplaceEvent extends Event { + var $id, $image; + + public function ImageReplaceEvent($id, Image $image) { + $this->id = $id; + $this->image = $image; + } +} + +class ImageReplaceException extends SCoreException { + var $error; + + public function __construct($error) { + $this->error = $error; + } +} + /* * ThumbnailGenerationEvent: @@ -129,6 +155,19 @@ class ImageIO extends SimpleExtension { } } } + if($event->page_matches("image_admin/replace")) { + global $page, $user; + if($user->is_admin() && isset($_POST['image_id']) && $user->check_auth_token()) { + $image = Image::by_id($_POST['image_id']); + if($image) { + $page->set_mode("redirect"); + $page->set_redirect(make_link('upload/replace/'.$image->id)); + } else { + /* Invalid image ID */ + throw new ImageReplaceException("Image to replace does not exist."); + } + } + } } public function onImageAdminBlockBuilding($event) { @@ -151,6 +190,15 @@ class ImageIO extends SimpleExtension { $event->image->delete(); } + public function onImageReplace($event) { + try { + $this->replace_image($event->id, $event->image); + } + catch(ImageReplaceException $e) { + throw new UploadException($e->error); + } + } + public function onUserPageBuilding($event) { $u_id = url_escape($event->display_user->id); $i_image_count = Image::count_images(array("user_id={$event->display_user->id}")); @@ -266,7 +314,8 @@ class ImageIO extends SimpleExtension { $image->tag_array = array(); send_event(new TagSetEvent($image, $tags_to_set)); } -// }}} +// }}} end add + // fetch image {{{ private function send_file($image_id, $type) { global $config; @@ -312,6 +361,58 @@ class ImageIO extends SimpleExtension { "The requested image was not found in the database")); } } -// }}} -} +// }}} end fetch + +// replace image {{{ + private function replace_image($id, $image) { + global $page; + global $user; + global $database; + global $config; + + /* Check to make sure the image exists. */ + $existing = Image::by_id($id); + + if(is_null($existing)) { + throw new ImageReplaceException("Image to replace does not exist!"); + } + + if(strlen(trim($image->source)) == 0) { + $image->source = $existing->get_source(); + } + if(!empty($image->source)) { + if(!preg_match("#^(https?|ftp)://#", $image->source)) { + throw new ImageReplaceException("Image's source isn't a valid URL"); + } + } + + /* + This step could be optional, ie: perhaps move the image somewhere + and have it stored in a 'replaced images' list that could be + inspected later by an admin? + */ + log_debug("image", "Removing image with hash ".$existing->hash); + $existing->remove_image_only(); // Actually delete the old image file from disk + + // Update the data in the database. + $database->Execute( + "UPDATE images SET + filename = :filename, filesize = :filesize, hash = :hash, + ext = :ext, width = :width, height = :height, source = :source + WHERE + id = :id + ", + array( + "filename"=>$image_new->filename, "filesize"=>$image->filesize, "hash"=>$image->hash, + "ext"=>$image->ext, "width"=>$image->width, "height"=>$image->height, "source"=>$image->source, + "id"=>$id + ) + ); + + log_info("image", "Replaced Image #{$id} with ({$image->hash})"); + } +// }}} end replace + + +} // end of class ImageIO ?> diff --git a/ext/image/theme.php b/ext/image/theme.php index 10bfcfe4..516a045d 100644 --- a/ext/image/theme.php +++ b/ext/image/theme.php @@ -27,6 +27,16 @@ class ImageIOTheme { "; } + + if($config->get_bool("upload_replace") && $user->is_admin()) { + $html .= " + ".make_form(make_link("image_admin/replace"))." + + + + "; + } + return $html; } } diff --git a/ext/upload/main.php b/ext/upload/main.php index ad5a8119..38ed4754 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -54,6 +54,7 @@ class Upload implements Extension { $config->set_default_int('upload_count', 3); $config->set_default_int('upload_size', '1MB'); $config->set_default_bool('upload_anon', false); + $config->set_default_bool('upload_replace', true); } if($event instanceof PostListBuildingEvent) { @@ -67,47 +68,113 @@ class Upload implements Extension { } } - if(($event instanceof PageRequestEvent) && $event->page_matches("upload")) { - if(count($_FILES) + count($_POST) > 0) { - $tags = Tag::explode($_POST['tags']); - $source = isset($_POST['source']) ? $_POST['source'] : null; - if($this->can_upload($user)) { - $ok = true; - foreach($_FILES as $file) { - $ok = $ok & $this->try_upload($file, $tags, $source); + if($event instanceof PageRequestEvent) { + + if ($event->page_matches("upload/replace")) + { + /* Upload & Replace Image Request */ + + if (!$config->get_bool("upload_replace")) { + throw new UploadException("Upload Replacing Images is not enabled."); + } + + // check if the user is an administrator and can upload files. + if (!$user->is_admin() && !$this->can_upload($user)) { + $this->theme->display_permission_denied($page); + } + else + { + if($is_full) { + throw new UploadException("Can not replace Image: disk nearly full"); } - foreach($_POST as $name => $value) { - if(substr($name, 0, 3) == "url" && strlen($value) > 0) { - $ok = $ok & $this->try_transload($value, $tags, $source); + // Try to get the image ID + $image_id = int_escape($event->get_arg(0)); + if (empty($image_id)) { + $image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null; + } + if (empty($image_id)) { + throw new UploadException("Can not replace Image: No valid Image ID given."); + } + + $image_old = Image::by_id($image_id); + if(is_null($image_old)) { + $this->theme->display_error($page, "Image not found", "No image in the database has the ID #$image_id"); + } + + if(count($_FILES) + count($_POST) > 0) + { + if (count($_FILES) > 1) { + throw new UploadException("Can not upload more than one image for replacing."); + } + + if (count($_FILES)) { + foreach($_FILES as $file) { + $ok = $this->try_upload($file, $tags, $source, $image_id); + break; // leave the foreach loop. + } + } else { + foreach($_POST as $name => $value) { + if(substr($name, 0, 3) == "url" && strlen($value) > 0) { + $ok = $this->try_transload($value, $tags, $source, $image_id); + break; // leave the foreach loop. + } + } + } + $this->theme->display_upload_status($page, $ok); + } + else if(!empty($_GET['url'])) + { + $url = $_GET['url']; + $ok = $this->try_transload($url, $tags, $url, $image_id); + $this->theme->display_upload_status($page, $ok); + } + else + { + $this->theme->display_replace_page($page, $image_id); + } + } // END of if admin / can_upload + } + else if ($event->page_matches("upload")) + { + if(!$this->can_upload($user)) { + $this->theme->display_permission_denied($page); + } else { + /* Regular Upload Image */ + if(count($_FILES) + count($_POST) > 0) + { + $tags = Tag::explode($_POST['tags']); + $source = isset($_POST['source']) ? $_POST['source'] : null; + $ok = true; + foreach($_FILES as $file) { + $ok = $ok & $this->try_upload($file, $tags, $source); + } + foreach($_POST as $name => $value) { + if(substr($name, 0, 3) == "url" && strlen($value) > 0) { + $ok = $ok & $this->try_transload($value, $tags, $source); + } + } + + $this->theme->display_upload_status($page, $ok); + } + else if(!empty($_GET['url'])) + { + $url = $_GET['url']; + $tags = array('tagme'); + if(!empty($_GET['tags']) && $_GET['tags'] != "null") { + $tags = Tag::explode($_GET['tags']); + } + $ok = $this->try_transload($url, $tags, $url); + $this->theme->display_upload_status($page, $ok); + } + else + { + if(!$is_full) { + $this->theme->display_page($page); } } - - $this->theme->display_upload_status($page, $ok); - } - else { - $this->theme->display_permission_denied($page); - } + } // END of if can_upload } - else if(!empty($_GET['url'])) { - if($this->can_upload($user)) { - $url = $_GET['url']; - $tags = array('tagme'); - if(!empty($_GET['tags']) && $_GET['tags'] != "null") { - $tags = Tag::explode($_GET['tags']); - } - $ok = $this->try_transload($url, $tags, $url); - $this->theme->display_upload_status($page, $ok); - } - else { - $this->theme->display_permission_denied($page); - } - } - else { - if(!$is_full) { - $this->theme->display_page($page); - } - } - } + } // END of if PageRequestEvent if($event instanceof SetupBuildingEvent) { $tes = array(); @@ -126,6 +193,7 @@ class Upload implements Extension { $sb->add_label("
PHP's Max Size Upload = ".ini_get('upload_max_filesize')."
"); $sb->add_shorthand_int_option("upload_size", "
Max size per file: "); $sb->add_bool_option("upload_anon", "
Allow anonymous uploads: "); + $sb->add_bool_option("upload_replace", "
Allow replacing images: "); $sb->add_choice_option("transload_engine", $tes, "
Transload: "); $event->panel->add_block($sb); } @@ -172,7 +240,7 @@ class Upload implements Extension { } } - private function try_upload($file, $tags, $source) { + private function try_upload($file, $tags, $source, $replace='') { global $page; global $config; global $user; @@ -188,15 +256,19 @@ class Upload implements Extension { if ($file['error'] !== UPLOAD_ERR_OK) { throw new UploadException($this->upload_error_message($file['error'])); } - + $pathinfo = pathinfo($file['name']); $metadata['filename'] = $pathinfo['basename']; $metadata['extension'] = $pathinfo['extension']; $metadata['tags'] = $tags; $metadata['source'] = $source; + /* check if we have been given an image ID to replace */ + if (!empty($replace)) { + $metadata['replace'] = $replace; + } + $event = new DataUploadEvent($user, $file['tmp_name'], $metadata); - send_event($event); if($event->image_id == -1) { throw new UploadException("File type not recognised"); @@ -213,7 +285,7 @@ class Upload implements Extension { return $ok; } - private function try_transload($url, $tags, $source) { + private function try_transload($url, $tags, $source, $replace='') { global $page; global $config; @@ -279,6 +351,12 @@ class Upload implements Extension { $metadata['extension'] = $pathinfo['extension']; $metadata['tags'] = $tags; $metadata['source'] = $source; + + /* check if we have been given an image ID to replace */ + if (!empty($replace)) { + $metadata['replace'] = $replace; + } + $event = new DataUploadEvent($user, $tmp_filename, $metadata); try { send_event($event); diff --git a/ext/upload/theme.php b/ext/upload/theme.php index aaf88dfe..df0e5868 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -75,6 +75,53 @@ class UploadTheme extends Themelet { $page->add_block(new Block("Upload", $html, "main", 20)); } + /* only allows 1 file to be uploaded - for replacing another image file */ + public function display_replace_page(Page $page, $image_id) { + global $config; + $tl_enabled = ($config->get_string("transload_engine", "none") != "none"); + + $upload_list = ''; + $width = $tl_enabled ? "35%" : "80%"; + $upload_list .= " + + File + + "; + if($tl_enabled) { + $upload_list .= " + URL + + "; + } + $upload_list .= " + + "; + + $max_size = $config->get_int('upload_size'); + $max_kb = to_shorthand_int($max_size); + + $image = Image::by_id($image_id); + $thumbnail = $this->build_thumb_html($image, null); + + $html = "

Replacing Image ID ".$image_id."
Please note: You will have to refresh the image page, or empty your browser cache.

" + .$thumbnail."
" + .make_form(make_link("upload/replace/".$image_id), "POST", $multipart=True)." + + + $upload_list + + +
Source
+ + (Max file size is $max_kb) + "; + + $page->set_title("Replace Image"); + $page->set_heading("Replace Image"); + $page->add_block(new NavBlock()); + $page->add_block(new Block("Upload Replacement Image", $html, "main", 20)); + } + public function display_upload_status(Page $page, $ok) { if($ok) { $page->set_mode("redirect"); diff --git a/lib/images/ui-bg_flat_0_aaaaaa_40x100.png b/lib/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 00000000..5b5dab2a Binary files /dev/null and b/lib/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/lib/images/ui-bg_flat_75_ffffff_40x100.png b/lib/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100644 index 00000000..ac8b229a Binary files /dev/null and b/lib/images/ui-bg_flat_75_ffffff_40x100.png differ diff --git a/lib/images/ui-bg_glass_55_fbf9ee_1x400.png b/lib/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 00000000..ad3d6346 Binary files /dev/null and b/lib/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/lib/images/ui-bg_glass_65_ffffff_1x400.png b/lib/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 00000000..42ccba26 Binary files /dev/null and b/lib/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/lib/images/ui-bg_glass_75_dadada_1x400.png b/lib/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 00000000..5a46b47c Binary files /dev/null and b/lib/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/lib/images/ui-bg_glass_75_e6e6e6_1x400.png b/lib/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 00000000..86c2baa6 Binary files /dev/null and b/lib/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/lib/images/ui-bg_glass_95_fef1ec_1x400.png b/lib/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 00000000..4443fdc1 Binary files /dev/null and b/lib/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/lib/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/lib/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 00000000..7c9fa6c6 Binary files /dev/null and b/lib/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/lib/images/ui-icons_222222_256x240.png b/lib/images/ui-icons_222222_256x240.png new file mode 100644 index 00000000..b273ff11 Binary files /dev/null and b/lib/images/ui-icons_222222_256x240.png differ diff --git a/lib/images/ui-icons_2e83ff_256x240.png b/lib/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 00000000..09d1cdc8 Binary files /dev/null and b/lib/images/ui-icons_2e83ff_256x240.png differ diff --git a/lib/images/ui-icons_454545_256x240.png b/lib/images/ui-icons_454545_256x240.png new file mode 100644 index 00000000..59bd45b9 Binary files /dev/null and b/lib/images/ui-icons_454545_256x240.png differ diff --git a/lib/images/ui-icons_888888_256x240.png b/lib/images/ui-icons_888888_256x240.png new file mode 100644 index 00000000..6d02426c Binary files /dev/null and b/lib/images/ui-icons_888888_256x240.png differ diff --git a/lib/images/ui-icons_cd0a0a_256x240.png b/lib/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 00000000..2ab019b7 Binary files /dev/null and b/lib/images/ui-icons_cd0a0a_256x240.png differ