diff --git a/app/controllers/InlineController.php b/app/controllers/InlineController.php new file mode 100755 index 0000000..696785b --- /dev/null +++ b/app/controllers/InlineController.php @@ -0,0 +1,164 @@ + ['only' => ['create', 'copy']] + ]; + } + + public function create() + { + # If this user already has an inline with no images, use it. + $inline = Inline::where("(SELECT count(*) FROM inline_images WHERE inline_images.inline_id = inlines.id) = 0 AND user_id = ?", current_user()->id)->first(); + + if (!$inline) { + $inline = Inline::create(['user_id' => current_user()->id]); + } + + $this->redirectTo(['#edit', 'id' => $inline->id]); + } + + public function index() + { + $query = Inline::none(); + if (!current_user()->is_anonymous()) { + $query->order('user_id = ' . current_user()->id . ' DESC'); + } + $query->order('created_at desc'); + + $this->inlines = $query->paginate($this->page_number(), 20); + + $this->respond_to_list('inlines'); + } + + public function delete() + { + $inline = Inline::find($this->params()->id); + + if (!current_user()->has_permission($inline)) { + $this->access_denied(); + return; + } + + $inline->destroy(); + $this->respond_to_success('Image group deleted', '#index'); + } + + public function addImage() + { + $inline = Inline::find($this->params()->id); + + if (!current_user()->has_permission($inline)) { + $this->access_denied(); + return; + } + + if ($this->request()->isPost()) { + $new_image = InlineImage::create(array_merge($this->params()->image ?: [], ['inline_id' => $inline->id, 'file' => $this->params()->files()->image['file']])); + if ($new_image->errors()->any()) { + $this->respond_to_error($new_image, ['#edit', 'id' => $inline->id]); + return; + } + + $this->redirectTo(['#edit', 'id' => $inline->id]); + } + } + + public function deleteImage() + { + $image = InlineImage::find($this->params()->id); + + $inline = $image->inline; + + if (!current_user()->has_permission($inline)) { + $this->access_denied(); + return; + } + + $image->destroy(); + $this->redirectTo(['#edit', 'id' => $inline->id]); + } + + public function update() + { + $inline = Inline::find($this->params()->id); + + if (!current_user()->has_permission($inline)) { + $this->access_denied(); + return; + } + + $inline->updateAttributes($this->params()->inline); + + $images = $this->params()->image ?: []; + foreach ($images as $id => $p) { + $image = InlineImage::where('id = ? AND inline_id = ?', $id, $inline->id)->first(); + if (isset($p['description'])) { + $image->description = $p['description']; + } + if (isset($p['sequence'])) { + $image->sequence = $p['sequence']; + } + if ($image->changedAttributes()) { + $image->save(); + } + } + + $inline->reload(); + $inline->renumber_sequences(); + + $this->notice('Image updated'); + $this->redirectTo(['#edit', 'id' => $inline->id]); + } + + # Create a copy of an inline image and all of its images. Allow copying from images + # owned by someone else. + public function copy() + { + $inline = Inline::find($this->params()->id); + + $new_inline = Inline::create([ + 'user_id' => current_user()->id, + 'description' => $inline->description + ]); + + foreach ($inline->inline_images as $image) { + $new_attributes = array_merge($image->attributes(), ['inline_id' => $new_inline->id]); + unset($new_attributes['id']); + $new_image = InlineImage::create($new_attributes); + } + + $this->respond_to_success('Image copied', ['#edit', 'id' => $new_inline->id]); + } + + public function edit() + { + $this->inline = Inline::find($this->params()->id); + } + + public function crop() + { + $this->inline = Inline::find($this->params()->id); + $image = $this->inline->inline_images->toArray(); + $this->image = array_shift($image); + if (!$this->image) { + throw new Rails\ActiveRecord\Exception\RecordNotFoundException( + "Inline images set #" . $this->inline->id . " doesn't have inline images" + ); + } + if (!current_user()->has_permission($this->inline)) { + $this->access_denied(); + return; + } + + if ($this->request()->isPost()) { + if ($this->inline->crop($this->params()->toArray())) { + $this->redirectTo(['#edit', 'id' => $this->inline->id]); + } else { + $this->respond_to_error($this->inline, ['#edit', 'id' => $this->inline->id]); + } + } + } +} diff --git a/app/helpers/ApplicationHelper.php b/app/helpers/ApplicationHelper.php index fd52d71..e0acaa2 100755 --- a/app/helpers/ApplicationHelper.php +++ b/app/helpers/ApplicationHelper.php @@ -91,7 +91,12 @@ class ApplicationHelper extends Rails\ActionView\Helper if (!$inline->inline_images) return ""; - $url = $inline->inline_images->first->preview_url(); + if ($inline->inline_images->any()) { + $url = $inline->inline_images[0]->preview_url(); + } else { + $url = ''; + } + if (!$preview_html) $preview_html = ''; @@ -108,29 +113,30 @@ class ApplicationHelper extends Rails\ActionView\Helper '; $inline_id = "inline-$id-$num"; - $script = 'InlineImage.register("'.$inline_id.'", '.to_json($inline).');'; + $script = 'InlineImage.register("' . $inline_id . '", ' . $inline->toJson() . ');'; return array($block, $script, $inline_id); } public function format_inlines($text, $id) { - $num = 0; + $num = 0; $list = []; - // preg_match('/image #(\d+)/i', $text, $m); - // foreach ($m as $t) { - // $i = Inline::find($m[1]); - // if ($i) { - // list($block, $script) = format_inline($i, $num, $id); - // $list[] = $script; - // $num++; - // return $block; - // } else - // return $t; - // } + $text = preg_replace_callback('/image #(\d+)/i', function($m) use (&$list, &$num, $id) { + $i = Inline::where(['id' => (int)$m[1]])->first(); + if ($i) { + list($block, $script) = $this->format_inline($i, $num, $id); + $list[] = $script; + $num++; + return $block; + } else { + return $m[0]; + } + }, $text); - if ($num > 0 ) - $text .= ''; + if ($num > 0) { + $text .= ''; + } return $text; } diff --git a/app/helpers/InlineHelper.php b/app/helpers/InlineHelper.php new file mode 100755 index 0000000..c81c0d6 --- /dev/null +++ b/app/helpers/InlineHelper.php @@ -0,0 +1,18 @@ +has_sample()) { + $url = $image->sample_url(); + $tag_options['width'] = $image->sample_width(); + $tag_options['height'] = $image->sample_height(); + } else { + $url = $image->file_url(); + $tag_options['width'] = $image->width; + $tag_options['height'] = $image->height; + } + + return $this->imageTag($url, $tag_options); + } +} diff --git a/app/models/Inline.php b/app/models/Inline.php index 48fae05..3011fdf 100755 --- a/app/models/Inline.php +++ b/app/models/Inline.php @@ -1,18 +1,34 @@ [ 'user' ], 'has_many' => [ - 'inline_images' => [function() { $this->order('sequence'); }, 'dependent' => 'destroy', 'class_name' => 'inlineImage'] + 'inline_images' => [function() { $this->order('sequence'); } /*Not yet supported: 'dependent' => 'destroy'*/, 'class_name' => 'InlineImage'] ] ]; } + protected function callbacks() + { + return [ + 'before_destroy' => [ + 'destroy_inline_images' + ] + ]; + } + + protected function destroy_inline_images() + { + foreach ($this->inline_images as $i) { + $i->destroy(); + } + } + # Sequence numbers must start at 1 and increase monotonically, to keep the UI simple. # If we've been given sequences with gaps or duplicates, sanitize them. public function renumber_sequences() @@ -65,7 +81,7 @@ class Inline extends Rails\ActiveRecord\Base try { # Create one crop for the image, and InlineImage will create the sample and preview from that. - Moebooru\Reizer::resize($image->file_ext, $image->file_path(), $new_image->tempfile_image_path(), $size, 95); + Moebooru\Resizer::resize($image->file_ext, $image->file_path(), $new_image->tempfile_image_path(), $size, 95); chmod($new_image->tempfile_image_path(), 0775); } catch (Exception $e) { if (is_file($new_image->tempfile_image_path())) { @@ -85,10 +101,10 @@ class Inline extends Rails\ActiveRecord\Base public function api_attributes() { return [ - 'id' => $this->id, - 'description' => $this->description, - 'user_id' => $this->user_id, - 'images' => $this->inline_images->toArray() + 'id' => (int)$this->id, + 'description' => (string)$this->description, + 'user_id' => (int)$this->user_id, + 'images' => $this->inline_images->asJson() ]; } diff --git a/app/models/InlineImage.php b/app/models/InlineImage.php index f487509..89d52b3 100755 --- a/app/models/InlineImage.php +++ b/app/models/InlineImage.php @@ -26,6 +26,8 @@ */ class InlineImage extends Rails\ActiveRecord\Base { + use Moebooru\TempfilePrefix; + public $source; public $received_file; @@ -64,17 +66,17 @@ class InlineImage extends Rails\ActiveRecord\Base public function tempfile_image_path() { - return $this->temfile_prefix . '.upload'; + return $this->tempfile_prefix() . '.upload'; } public function tempfile_sample_path() { - return $this->temfile_prefix . '-sample.upload'; + return $this->tempfile_prefix() . '-sample.upload'; } public function tempfile_preview_path() { - return $this->temfile_prefix . '-preview.upload'; + return $this->tempfile_prefix() . '-preview.upload'; } /** @@ -102,15 +104,15 @@ class InlineImage extends Rails\ActiveRecord\Base } /** - * @param string $f + * @param Rails\ActionDispatch\Http\UploadedFile $f */ public function setFile($f) { - if (!is_file($f) || !filesize($f)) { + if (!$f->size()) { return; } - copy($f, $this->tempfile_image_path()); + copy($f->tempName(), $this->tempfile_image_path()); $this->got_file(); } @@ -143,7 +145,7 @@ class InlineImage extends Rails\ActiveRecord\Base } if (!file_exists($this->tempfile_image_path())) { - $this->errors()->add('base', "No file received"); + $this->errors()->addToBase("No file received"); return false; } @@ -156,7 +158,7 @@ class InlineImage extends Rails\ActiveRecord\Base } if (!in_array($this->file_ext, ['jpg', 'png', 'gif'])) { - $this->errors()->add('file', 'is an invalid content type: ' . $this->file_ext); + $this->errors()->add('file', 'is an invalid content type: ' . $this->file_ext ?: 'unknown'); } return true; @@ -191,8 +193,10 @@ class InlineImage extends Rails\ActiveRecord\Base return true; } - # We can generate the sample image during upload or offline. Use tempfile_image_path - # if it exists, otherwise use file_path. + /** + * We can generate the sample image during upload or offline. Use tempfile_image_path + * if it exists, otherwise use file_path. + */ $path = $this->tempfile_image_path(); if (!is_file($path)) { $path = $this->file_path(); @@ -263,8 +267,8 @@ class InlineImage extends Rails\ActiveRecord\Base return true; } - if (!is_dir(dirname($file_path))) { - mkdir(dirname($file_path), 0777, true); + if (!is_dir(dirname($this->file_path()))) { + mkdir(dirname($this->file_path()), 0777, true); } rename($this->tempfile_image_path(), $this->file_path()); @@ -290,7 +294,7 @@ class InlineImage extends Rails\ActiveRecord\Base return; } $siblings = $this->inline->inline_images; - $max_sequence = max($siblings->getAttributes('sequence')) ?: 0; + $max_sequence = ($siblings->getAttributes('sequence') && max($siblings->getAttributes('sequence'))) ?: 0; $this->sequence = $max_sequence + 1; } @@ -390,19 +394,19 @@ class InlineImage extends Rails\ActiveRecord\Base public function api_attributes() { return [ - 'id' => $this->id, - 'sequence' => $this->sequence, - 'md5' => $this->md5, - 'width' => $this->width, - 'height' => $this->height, - 'sample_width' => $this->sample_width, - 'sample_height' => $this->sample_height, - 'preview_width' => $this->preview_dimensions()['width'], + 'id' => (int)$this->id, + 'sequence' => $this->sequence, + 'md5' => $this->md5, + 'width' => (int)$this->width, + 'height' => (int)$this->height, + 'sample_width' => $this->sample_width, + 'sample_height' => $this->sample_height, + 'preview_width' => $this->preview_dimensions()['width'], 'preview_height' => $this->preview_dimensions()['height'], - 'description' => $this->description, - 'file_url' => $this->file_url(), - 'sample_url' => $this->sample_url(), - 'preview_url' => $this->preview_url() + 'description' => (string)$this->description, + 'file_url' => $this->file_url(), + 'sample_url' => $this->sample_url(), + 'preview_url' => $this->preview_url() ]; } diff --git a/app/views/inline/_footer.php b/app/views/inline/_footer.php new file mode 100755 index 0000000..6d8cc00 --- /dev/null +++ b/app/views/inline/_footer.php @@ -0,0 +1,5 @@ +contentFor('subnavbar', function(){ ?> +
+ +
+ + +
+= $this->t('.first_image') ?> | += $this->t('.description2') ?> | += $this->t('.user') ?> | += $this->t('.images') ?> | += $this->t('.created') ?> | +
---|---|---|---|---|
+ $p->id]) ?>'> + inline_images->any()) : ?> + = $this->imageTag($p->inline_images[0]->preview_url(), ['alt' => "thumb", 'width' => $p->inline_images[0]->preview_dimensions()['width'], 'height' => $p->inline_images[0]->preview_dimensions()['height']]) ?> + + (no images) + + + | += $this->h($p->description) ?> | += $this->linkTo($this->h($p->user->pretty_name()), ["user#show", 'id' => $p->user->id]) ?> | += $p->inline_images->size() ?> | += $this->t('time.x_ago', ['t' => $this->t($this->timeAgoInWords($p->created_at))]) ?> | +