From f3b6fde7a5c81f8ec7d9bdd381c2458ac3997185 Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" Date: Wed, 24 Aug 2011 20:53:53 -0400 Subject: [PATCH 1/6] Working on adding a "Replace Image" feature. --- core/extension.class.php | 16 ++++- core/imageboard.pack.php | 10 +++ ext/image/main.php | 112 +++++++++++++++++++++++++++++++- ext/upload/main.php | 136 +++++++++++++++++++++++++++++---------- ext/upload/theme.php | 48 ++++++++++++++ 5 files changed, 282 insertions(+), 40 deletions(-) diff --git a/core/extension.class.php b/core/extension.class.php index 422edf53..7addb8ae 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); @@ -139,9 +140,18 @@ abstract class DataHandlerExtension implements Extension { 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; + /* Check if we are replacing an image */ + if ( isset($event->metadata['replace'])) + { + $image_id = $event->metadata['replace']; + $ire = new ImageReplaceEvent($image_id, $image); + send_event($ire); + $event->image_id = $image_id; + } else { + $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..0adf2697 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -46,6 +46,30 @@ class ImageDeletionEvent extends Event { } } +/* + * ImageReplaceEvent: + * $image -- the new image to be used + * + * 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 $image; + + public function ImageReplaceEvent(Image $image) { + $this->image = $image; + } +} + +class ImageReplaceException extends SCoreException { + var $error; + + public function __construct($error) { + $this->error = $error; + } +} + /* * ThumbnailGenerationEvent: @@ -129,6 +153,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 */ + // fail silently? + } + } + } } public function onImageAdminBlockBuilding($event) { @@ -151,6 +188,15 @@ class ImageIO extends SimpleExtension { $event->image->delete(); } + public function onImageReplace($event) { + try { + $this->replace_image($event->image_old, $event->image_new); + } + 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 +312,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 +359,65 @@ 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; + + /* + * Validate things + */ + + /* 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 ($existing->hash === $image->hash) { + throw new ImageReplaceException("The uploaded image is the same as the one to replace."); + } + if(strlen(trim($image->source)) == 0) { + $image->source = null; + } + 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? + */ + $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"=>$existing->id + ) + ); + + log_info("image", "Replaced Image #{$existing->id} with ({$image->hash})"); + } +// }}} end replace + + +} // end of class ImageIO ?> diff --git a/ext/upload/main.php b/ext/upload/main.php index ad5a8119..b31de495 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,44 +68,99 @@ 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); - } - foreach($_POST as $name => $value) { - if(substr($name, 0, 3) == "url" && strlen($value) > 0) { - $ok = $ok & $this->try_transload($value, $tags, $source); - } - } + if($event instanceof PageRequestEvent) { + /* Upload and Replace Image */ + if ($event->page_matches("upload/replace")) + { + if($is_full) { + throw new UploadException("Can not replace Image: disk nearly full"); + } + $image_id = int_escape($event->get_arg(0)); + $image_old = Image::by_id($image_id); - $this->theme->display_upload_status($page, $ok); + if(is_null($image_old)) { + $this->theme->display_error($page, "Image not found", "No image in the database has the ID #$image_id"); } - else { - $this->theme->display_permission_denied($page); - } - } - 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']); + + if(count($_FILES) + count($_POST) > 0) + { + if (count($_FILES) + count($_POST) > 1) { + throw new UploadException("Can not upload more than one image for replacing."); + } + + if($this->can_upload($user)) { + /* This could be changed so that it doesn't use foreach loops.. */ + foreach($_FILES as $file) { + $ok = $ok & $this->try_upload($_FILES, $tags, $source, $image_id); + } + foreach($_POST as $name => $value) { + if(substr($name, 0, 3) == "url" && strlen($value) > 0) { + $ok = $ok & $this->try_transload($value, $tags, $source, $image_id); + } + } + /* Could replace with a page saying the image replace was successful? */ + $this->theme->display_upload_status($page, $ok); + } else { + $this->theme->display_permission_denied($page); } - $ok = $this->try_transload($url, $tags, $url); - $this->theme->display_upload_status($page, $ok); } - else { - $this->theme->display_permission_denied($page); + else if(!empty($_GET['url'])) + { + if($this->can_upload($user)) { + $url = $_GET['url']; + $ok = $this->try_transload($url, $tags, $url, $image_id); + /* Replace with a page saying the image replace was successful */ + $this->theme->display_upload_status($page, $ok); + } + else { + $this->theme->display_permission_denied($page); + } + } else { + $this->theme->display_replace_page($page); } } - else { - if(!$is_full) { - $this->theme->display_page($page); + /* Upload Image */ + else if ($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); + } + 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 { + $this->theme->display_permission_denied($page); + } + } + 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); + } } } } @@ -126,6 +182,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 +229,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; @@ -195,6 +252,11 @@ class Upload implements 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); @@ -213,7 +275,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 +341,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..99a0163b 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -75,6 +75,54 @@ 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) { + 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); + $html = make_form(make_link("upload/replace"), "POST", $multipart=True)." + + $upload_list + + +
Source
+ + (Max file size is $max_kb) + "; +/* + if($tl_enabled) { + $link = make_http(make_link("upload")); + $title = "Upload to " . $config->get_string('title'); + $html .= '

' . + $title . ' (Drag & drop onto your bookmarks toolbar, then click when looking at an image)'; + } +*/ + $page->set_title("Replace Image Upload"); + $page->set_heading("Replace Image Upload"); + $page->add_block(new NavBlock()); + $page->add_block(new Block("Replace Image Upload", $html, "main", 20)); + } + public function display_upload_status(Page $page, $ok) { if($ok) { $page->set_mode("redirect"); From 62cc7e0e5ea739be99db9d12dd25795f1af794dc Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" Date: Wed, 24 Aug 2011 21:23:18 -0400 Subject: [PATCH 2/6] Working on Replace Image feature. Added link to page, fixed foreach loop. --- ext/image/theme.php | 3 +++ ext/upload/main.php | 17 +++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/ext/image/theme.php b/ext/image/theme.php index 10bfcfe4..ddd00719 100644 --- a/ext/image/theme.php +++ b/ext/image/theme.php @@ -27,6 +27,9 @@ class ImageIOTheme { "; } + + $html .= "Replace Image"; + return $html; } } diff --git a/ext/upload/main.php b/ext/upload/main.php index b31de495..511f9271 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -84,18 +84,19 @@ class Upload implements Extension { if(count($_FILES) + count($_POST) > 0) { - if (count($_FILES) + count($_POST) > 1) { + if (count($_FILES) > 1) { throw new UploadException("Can not upload more than one image for replacing."); } if($this->can_upload($user)) { - /* This could be changed so that it doesn't use foreach loops.. */ - foreach($_FILES as $file) { - $ok = $ok & $this->try_upload($_FILES, $tags, $source, $image_id); - } - foreach($_POST as $name => $value) { - if(substr($name, 0, 3) == "url" && strlen($value) > 0) { - $ok = $ok & $this->try_transload($value, $tags, $source, $image_id); + if (count($_FILES)) { + $ok = $this->try_upload($_FILES, $tags, $source, $image_id); + } 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. + } } } /* Could replace with a page saying the image replace was successful? */ From 0d863c898ec027e2c03792c51c78efde66d6e8b6 Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" Date: Wed, 24 Aug 2011 21:57:32 -0400 Subject: [PATCH 3/6] These files are needed for the jQuery UI library. Modified the git ignore file accordingly. --- .gitignore | 1 + lib/images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes lib/images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 178 bytes lib/images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 120 bytes lib/images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 105 bytes lib/images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 111 bytes lib/images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 110 bytes lib/images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 119 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 101 bytes lib/images/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes lib/images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4369 bytes lib/images/ui-icons_454545_256x240.png | Bin 0 -> 4369 bytes lib/images/ui-icons_888888_256x240.png | Bin 0 -> 4369 bytes lib/images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4369 bytes 14 files changed, 1 insertion(+) create mode 100644 lib/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 lib/images/ui-bg_flat_75_ffffff_40x100.png create mode 100644 lib/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 lib/images/ui-bg_glass_65_ffffff_1x400.png create mode 100644 lib/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 lib/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 lib/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 lib/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 lib/images/ui-icons_222222_256x240.png create mode 100644 lib/images/ui-icons_2e83ff_256x240.png create mode 100644 lib/images/ui-icons_454545_256x240.png create mode 100644 lib/images/ui-icons_888888_256x240.png create mode 100644 lib/images/ui-icons_cd0a0a_256x240.png 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/lib/images/ui-bg_flat_0_aaaaaa_40x100.png b/lib/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 0000000000000000000000000000000000000000..5b5dab2ab7b1c50dea9cfe73dc5a269a92d2d4b4 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FscKIb$B>N1x91EQ4=4yQ7#`R^ z$vje}bP0l+XkK DSH>_4 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ac8b229af950c29356abf64a6c4aa894575445f0 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FsY*{5$B>N1x91EQ4=4yQYz+E8 zPo9&<{J;c_6SHRil>2s{Zw^OT)6@jj2u|u!(plXsM>LJD`vD!n;OXk;vd$@?2>^GI BH@yG= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ad3d6346e00f246102f72f2e026ed0491988b394 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnour0hLi978O6-<~(*I$*%ybaDOn z{W;e!B}_MSUQoPXhYd^Y6RUoS1yepnPx`2Kz)7OXQG!!=-jY=F+d2OOy?#DnJ32>z UEim$g7SJdLPgg&ebxsLQ09~*s;{X5v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..42ccba269b6e91bef12ad0fa18be651b5ef0ee68 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouqzpV=978O6-=0?FV^9z|eBtf= z|7WztIJ;WT>{+tN>ySr~=F{k$>;_x^_y?afmf9pRKH0)6?eSP?3s5hEr>mdKI;Vst E0O;M1& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5a46b47cb16631068aee9e0bd61269fc4e95e5cd GIT binary patch literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouq|7{B978O6lPf+wIa#m9#>Unb zm^4K~wN3Zq+uP{vDV26o)#~38k_!`W=^oo1w6ixmPC4R1b Tyd6G3lNdZ*{an^LB{Ts5`idse literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7c9fa6c6edcfcdd3e5b77e6f547b719e6fc66e30 GIT binary patch literal 101 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 literal 0 HcmV?d00001 diff --git a/lib/images/ui-icons_2e83ff_256x240.png b/lib/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..09d1cdc856c292c4ab6dd818c7543ac0828bd616 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/lib/images/ui-icons_454545_256x240.png b/lib/images/ui-icons_454545_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..59bd45b907c4fd965697774ce8c5fc6b2fd9c105 GIT binary patch literal 4369 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 oBo%DI*Kv;w;*%(i9W@f3_WCF#rGn literal 0 HcmV?d00001 diff --git a/lib/images/ui-icons_cd0a0a_256x240.png b/lib/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab019b73ec11a485fa09378f3a0e155194f6a5d GIT binary patch literal 4369 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 Date: Wed, 24 Aug 2011 23:55:44 -0400 Subject: [PATCH 4/6] Working on image replace. Getting unknown 404 errors. --- ext/image/main.php | 7 +- ext/image/theme.php | 9 ++- ext/upload/main.php | 179 +++++++++++++++++++++++++------------------ ext/upload/theme.php | 32 +++++--- 4 files changed, 139 insertions(+), 88 deletions(-) diff --git a/ext/image/main.php b/ext/image/main.php index 0adf2697..6bb5ded1 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -57,7 +57,8 @@ class ImageDeletionEvent extends Event { class ImageReplaceEvent extends Event { var $image; - public function ImageReplaceEvent(Image $image) { + public function ImageReplaceEvent($id, Image $image) { + $this->id = $id; $this->image = $image; } } @@ -162,7 +163,7 @@ class ImageIO extends SimpleExtension { $page->set_redirect(make_link('upload/replace/'.$image->id)); } else { /* Invalid image ID */ - // fail silently? + throw new ImageReplaceException("Image to replace does not exist."); } } } @@ -190,7 +191,7 @@ class ImageIO extends SimpleExtension { public function onImageReplace($event) { try { - $this->replace_image($event->image_old, $event->image_new); + $this->replace_image($event->id, $event->image); } catch(ImageReplaceException $e) { throw new UploadException($e->error); diff --git a/ext/image/theme.php b/ext/image/theme.php index ddd00719..5af59214 100644 --- a/ext/image/theme.php +++ b/ext/image/theme.php @@ -28,7 +28,14 @@ class ImageIOTheme { "; } - $html .= "Replace Image"; + if($config->get_bool("upload_replace")) { + $html .= " + ".make_form(make_link("image_admin/replace"))." + + + + "; + } return $html; } diff --git a/ext/upload/main.php b/ext/upload/main.php index 511f9271..e2f4790d 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -69,98 +69,131 @@ class Upload implements Extension { } if($event instanceof PageRequestEvent) { - /* Upload and Replace Image */ if ($event->page_matches("upload/replace")) { + /* Replace Image Request */ + if (!$config->get_bool("upload_replace")) { + throw new UploadException("Upload Replacing Images is not enabled."); + } if($is_full) { throw new UploadException("Can not replace Image: disk nearly full"); } + // Try to get the image ID $image_id = int_escape($event->get_arg(0)); - $image_old = Image::by_id($image_id); + 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($this->can_upload($user)) { - if (count($_FILES)) { - $ok = $this->try_upload($_FILES, $tags, $source, $image_id); - } 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. - } - } - } - /* Could replace with a page saying the image replace was successful? */ - $this->theme->display_upload_status($page, $ok); - } else { - $this->theme->display_permission_denied($page); - } - } - else if(!empty($_GET['url'])) - { - if($this->can_upload($user)) { - $url = $_GET['url']; - $ok = $this->try_transload($url, $tags, $url, $image_id); - /* Replace with a page saying the image replace was successful */ - $this->theme->display_upload_status($page, $ok); - } - else { - $this->theme->display_permission_denied($page); - } - } else { - $this->theme->display_replace_page($page); - } + $this->theme->display_replace_page($page, $image_id); + } - /* Upload Image */ else if ($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); - } - 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 { - $this->theme->display_permission_denied($page); - } + // 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; } - else if(!empty($_GET['url'])) + if (!empty($image_id)) { - 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); + /* Upload and Replace Image */ + if (!$config->get_bool("upload_replace")) { + throw new UploadException("Upload Replacing Images is not enabled."); } - else { - $this->theme->display_permission_denied($page); + if($is_full) { + throw new UploadException("Can not replace Image: disk nearly full"); + } + $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($this->can_upload($user)) { + if (count($_FILES)) { + $ok = $this->try_upload($_FILES, $tags, $source, $image_id); + } 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. + } + } + } + /* Could replace with a page saying the image replace was successful? */ + $this->theme->display_replace_upload_status($page, $ok); + } else { + $this->theme->display_permission_denied($page); + } + } + else if(!empty($_GET['url'])) + { + if($this->can_upload($user)) { + $url = $_GET['url']; + $ok = $this->try_transload($url, $tags, $url, $image_id); + /* Replace with a page saying the image replace was successful */ + $this->theme->display_replace_upload_status($page, $ok); + } + else { + $this->theme->display_permission_denied($page); + } } } - else { - if(!$is_full) { - $this->theme->display_page($page); + else + { + /* Regular Upload Image */ + 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); + } + 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 { + $this->theme->display_permission_denied($page); + } + } + 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); + } } } } diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 99a0163b..9d94b897 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -76,7 +76,7 @@ class UploadTheme extends Themelet { } /* only allows 1 file to be uploaded - for replacing another image file */ - public function display_replace_page(Page $page) { + public function display_replace_page(Page $page, $image_id) { global $config; $tl_enabled = ($config->get_string("transload_engine", "none") != "none"); @@ -99,7 +99,9 @@ class UploadTheme extends Themelet { $max_size = $config->get_int('upload_size'); $max_kb = to_shorthand_int($max_size); - $html = make_form(make_link("upload/replace"), "POST", $multipart=True)." + $html = "

Replacing Image ID# ".$image_id."

" + .make_form(make_link("upload"), "POST", $multipart=True)." + $upload_list @@ -108,15 +110,7 @@ class UploadTheme extends Themelet { (Max file size is $max_kb) "; -/* - if($tl_enabled) { - $link = make_http(make_link("upload")); - $title = "Upload to " . $config->get_string('title'); - $html .= '

' . - $title . ' (Drag & drop onto your bookmarks toolbar, then click when looking at an image)'; - } -*/ + $page->set_title("Replace Image Upload"); $page->set_heading("Replace Image Upload"); $page->add_block(new NavBlock()); @@ -135,6 +129,22 @@ class UploadTheme extends Themelet { } } + public function display_replace_upload_status(Page $page, $ok) { + if($ok) { + $page->set_title("GREAT SUCCESS!"); + $page->set_heading("GREAT SUCCESS!"); + $page->add_block(new NavBlock()); + /* + $page->set_mode("redirect"); + $page->set_redirect(make_link());*/ + } + else { + $page->set_title("poo"); + $page->set_heading("Upload Status"); + $page->add_block(new NavBlock()); + } + } + public function display_upload_error(Page $page, $title, $message) { $page->add_block(new Block($title, $message)); } From 17999cade82746321f3b8034b341699440f39750 Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" Date: Thu, 25 Aug 2011 21:35:59 -0400 Subject: [PATCH 5/6] Image Replace feature working, just needs more testing. --- core/extension.class.php | 40 ++++++++-- ext/image/main.php | 24 +++--- ext/upload/main.php | 166 ++++++++++++++++++--------------------- ext/upload/theme.php | 31 +++----- 4 files changed, 127 insertions(+), 134 deletions(-) diff --git a/core/extension.class.php b/core/extension.class.php index 7addb8ae..9b514318 100644 --- a/core/extension.class.php +++ b/core/extension.class.php @@ -134,20 +134,46 @@ 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 ( isset($event->metadata['replace'])) + if (array_key_exists('replace',$event->metadata) && isset($event->metadata['replace'])) { - $image_id = $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 { + } + 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; diff --git a/ext/image/main.php b/ext/image/main.php index 6bb5ded1..1908d6f5 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -48,14 +48,15 @@ class ImageDeletionEvent extends Event { /* * ImageReplaceEvent: - * $image -- the new image to be used + * $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 $image; + var $id, $image; public function ImageReplaceEvent($id, Image $image) { $this->id = $id; @@ -369,22 +370,15 @@ class ImageIO extends SimpleExtension { global $database; global $config; - /* - * Validate things - */ - - /* Check to make sure the image exists. */ + /* 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 ($existing->hash === $image->hash) { - throw new ImageReplaceException("The uploaded image is the same as the one to replace."); - } if(strlen(trim($image->source)) == 0) { - $image->source = null; + $image->source = $existing->get_source(); } if(!empty($image->source)) { if(!preg_match("#^(https?|ftp)://#", $image->source)) { @@ -396,10 +390,10 @@ class ImageIO extends SimpleExtension { 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 @@ -411,11 +405,11 @@ class ImageIO extends SimpleExtension { array( "filename"=>$image_new->filename, "filesize"=>$image->filesize, "hash"=>$image->hash, "ext"=>$image->ext, "width"=>$image->width, "height"=>$image->height, "source"=>$image->source, - "id"=>$existing->id + "id"=>$id ) ); - log_info("image", "Replaced Image #{$existing->id} with ({$image->hash})"); + log_info("image", "Replaced Image #{$id} with ({$image->hash})"); } // }}} end replace diff --git a/ext/upload/main.php b/ext/upload/main.php index e2f4790d..0ba3e539 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -69,9 +69,11 @@ class Upload implements Extension { } if($event instanceof PageRequestEvent) { + if ($event->page_matches("upload/replace")) { - /* Replace Image Request */ + /* Upload & Replace Image Request */ + if (!$config->get_bool("upload_replace")) { throw new UploadException("Upload Replacing Images is not enabled."); } @@ -87,113 +89,96 @@ class Upload implements Extension { 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"); } - - $this->theme->display_replace_page($page, $image_id); - - } - else if ($event->page_matches("upload")) - { - // 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)) + + if(count($_FILES) + count($_POST) > 0) { - /* Upload and Replace Image */ - if (!$config->get_bool("upload_replace")) { - throw new UploadException("Upload Replacing Images is not enabled."); + if (count($_FILES) > 1) { + throw new UploadException("Can not upload more than one image for replacing."); } - if($is_full) { - throw new UploadException("Can not replace Image: disk nearly full"); - } - $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($this->can_upload($user)) { - if (count($_FILES)) { - $ok = $this->try_upload($_FILES, $tags, $source, $image_id); - } 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. - } + if($this->can_upload($user)) { + 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. } } - /* Could replace with a page saying the image replace was successful? */ - $this->theme->display_replace_upload_status($page, $ok); - } else { - $this->theme->display_permission_denied($page); } + + $this->theme->display_upload_status($page, $ok); + } else { + $this->theme->display_permission_denied($page); } - else if(!empty($_GET['url'])) - { - if($this->can_upload($user)) { - $url = $_GET['url']; - $ok = $this->try_transload($url, $tags, $url, $image_id); - /* Replace with a page saying the image replace was successful */ - $this->theme->display_replace_upload_status($page, $ok); - } - else { - $this->theme->display_permission_denied($page); - } + } + else if(!empty($_GET['url'])) + { + if($this->can_upload($user)) { + $url = $_GET['url']; + $ok = $this->try_transload($url, $tags, $url, $image_id); + $this->theme->display_upload_status($page, $ok); + } + else { + $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; - if($this->can_upload($user)) { - $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_replace_page($page, $image_id); + } + } + else if ($event->page_matches("upload")) + { + /* Regular Upload Image */ + 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); + } + 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 { - $this->theme->display_permission_denied($page); - } - } - 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); - } + $this->theme->display_upload_status($page, $ok); } else { - if(!$is_full) { - $this->theme->display_page($page); + $this->theme->display_permission_denied($page); + } + } + 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); } } } @@ -279,7 +264,7 @@ 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']; @@ -292,7 +277,6 @@ class Upload implements Extension { } $event = new DataUploadEvent($user, $file['tmp_name'], $metadata); - send_event($event); if($event->image_id == -1) { throw new UploadException("File type not recognised"); diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 9d94b897..df0e5868 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -99,8 +99,13 @@ class UploadTheme extends Themelet { $max_size = $config->get_int('upload_size'); $max_kb = to_shorthand_int($max_size); - $html = "

Replacing Image ID# ".$image_id."

" - .make_form(make_link("upload"), "POST", $multipart=True)." + + $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)."
Source
$upload_list @@ -111,10 +116,10 @@ class UploadTheme extends Themelet { (Max file size is $max_kb) "; - $page->set_title("Replace Image Upload"); - $page->set_heading("Replace Image Upload"); + $page->set_title("Replace Image"); + $page->set_heading("Replace Image"); $page->add_block(new NavBlock()); - $page->add_block(new Block("Replace Image Upload", $html, "main", 20)); + $page->add_block(new Block("Upload Replacement Image", $html, "main", 20)); } public function display_upload_status(Page $page, $ok) { @@ -129,22 +134,6 @@ class UploadTheme extends Themelet { } } - public function display_replace_upload_status(Page $page, $ok) { - if($ok) { - $page->set_title("GREAT SUCCESS!"); - $page->set_heading("GREAT SUCCESS!"); - $page->add_block(new NavBlock()); - /* - $page->set_mode("redirect"); - $page->set_redirect(make_link());*/ - } - else { - $page->set_title("poo"); - $page->set_heading("Upload Status"); - $page->add_block(new NavBlock()); - } - } - public function display_upload_error(Page $page, $title, $message) { $page->add_block(new Block($title, $message)); } From c44ed439f8a851b242ab4bb4fd7a90bebd5702ca Mon Sep 17 00:00:00 2001 From: "green-ponies (jgen)" Date: Tue, 30 Aug 2011 13:57:14 -0400 Subject: [PATCH 6/6] Extra checking for the user being an admin before allowing image replacement. Also removed some duplicated code for efficiency. --- ext/image/theme.php | 2 +- ext/upload/main.php | 110 ++++++++++++++++++++------------------------ 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/ext/image/theme.php b/ext/image/theme.php index 5af59214..516a045d 100644 --- a/ext/image/theme.php +++ b/ext/image/theme.php @@ -28,7 +28,7 @@ class ImageIOTheme { "; } - if($config->get_bool("upload_replace")) { + if($config->get_bool("upload_replace") && $user->is_admin()) { $html .= " ".make_form(make_link("image_admin/replace"))." diff --git a/ext/upload/main.php b/ext/upload/main.php index 0ba3e539..38ed4754 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -77,30 +77,36 @@ class Upload implements Extension { if (!$config->get_bool("upload_replace")) { throw new UploadException("Upload Replacing Images is not enabled."); } - if($is_full) { - throw new UploadException("Can not replace Image: disk nearly full"); + + // 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); } - // 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) + else { - if (count($_FILES) > 1) { - throw new UploadException("Can not upload more than one image for replacing."); + if($is_full) { + throw new UploadException("Can not replace Image: disk nearly full"); } - if($this->can_upload($user)) { + // 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); @@ -114,36 +120,30 @@ class Upload implements Extension { } } } - $this->theme->display_upload_status($page, $ok); - } else { - $this->theme->display_permission_denied($page); } - } - else if(!empty($_GET['url'])) - { - if($this->can_upload($user)) { + else if(!empty($_GET['url'])) + { $url = $_GET['url']; $ok = $this->try_transload($url, $tags, $url, $image_id); - $this->theme->display_upload_status($page, $ok); + $this->theme->display_upload_status($page, $ok); } - else { - $this->theme->display_permission_denied($page); + else + { + $this->theme->display_replace_page($page, $image_id); } - } - else - { - $this->theme->display_replace_page($page, $image_id); - } + } // END of if admin / can_upload } else if ($event->page_matches("upload")) { - /* Regular Upload Image */ - if(count($_FILES) + count($_POST) > 0) - { - $tags = Tag::explode($_POST['tags']); - $source = isset($_POST['source']) ? $_POST['source'] : null; - if($this->can_upload($user)) { + 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); @@ -156,13 +156,8 @@ class Upload implements Extension { $this->theme->display_upload_status($page, $ok); } - else { - $this->theme->display_permission_denied($page); - } - } - else if(!empty($_GET['url'])) - { - if($this->can_upload($user)) { + else if(!empty($_GET['url'])) + { $url = $_GET['url']; $tags = array('tagme'); if(!empty($_GET['tags']) && $_GET['tags'] != "null") { @@ -171,18 +166,15 @@ class Upload implements Extension { $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); + } } - } - else - { - if(!$is_full) { - $this->theme->display_page($page); - } - } + } // END of if can_upload } - } + } // END of if PageRequestEvent if($event instanceof SetupBuildingEvent) { $tes = array();