Fully enabled Inline Images.

A little detail: the "Add image" button was moved to a new table row, so it stands out more.
Known bugs/errors:
- When cropping an animated GIF inline (with a secondary JPG inline), although the images are correctly cropped, an empty error is displayed.
This commit is contained in:
Parziphal 2013-12-31 14:22:59 -05:00
parent 3ca7ad5e94
commit cf9cba40b6
13 changed files with 682 additions and 49 deletions

View File

@ -0,0 +1,164 @@
<?php
class InlineController extends ApplicationController
{
protected function filters()
{
return [
'member_only' => ['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]);
}
}
}
}

View File

@ -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 = '<img src="'.$url.'">';
@ -108,29 +113,30 @@ class ApplicationHelper extends Rails\ActionView\Helper
</div>
';
$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 .= '<script language="javascript">' . implode("\n", $list) . '</script>';
if ($num > 0) {
$text .= '<script type="text/javascript">' . implode("\n", $list) . '</script>';
}
return $text;
}

18
app/helpers/InlineHelper.php Executable file
View File

@ -0,0 +1,18 @@
<?php
class InlineHelper extends Rails\ActionView\Helper
{
public function inline_image_tag($image, array $options = [], array $tag_options = [])
{
if (!empty($options['user_sample']) && $image->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);
}
}

View File

@ -1,18 +1,34 @@
<?php
class Inline extends Rails\ActiveRecord\Base
{
protected function assotiacions()
protected function associations()
{
return [
'belongs_to' => [
'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()
];
}

View File

@ -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()
];
}

5
app/views/inline/_footer.php Executable file
View File

@ -0,0 +1,5 @@
<?php $this->contentFor('subnavbar', function(){ ?>
<li><?= $this->linkTo($this->t('.list'), ['action' => "index"]) ?></li>
<?= $this->content('footer') ?>
<!-- <li><?= $this->linkTo($this->t('.help'), ['controller' => "help", 'action' => "inlines"]) ?></li> -->
<?php }) ?>

50
app/views/inline/crop.php Executable file
View File

@ -0,0 +1,50 @@
<div id="set-avatar" class="page">
<?= $this->t('.crop_avatar') ?>
<p>
<div class="avatar-crop">
<?= $this->inline_image_tag($this->image, ['use_sample' => true], ['id' => "image"]) ?>
</div>
<?= $this->formTag([], ['id' => "crop", 'level' => 'member'], function(){ ?>
<?= $this->hiddenFieldTag("id", $this->image->id) ?>
<?= $this->hiddenFieldTag("left", 0) ?>
<?= $this->hiddenFieldTag("right", 0) ?>
<?= $this->hiddenFieldTag("top", 0) ?>
<?= $this->hiddenFieldTag("bottom", 0) ?>
<?php }) ?>
<script type="text/javascript" charset="utf-8">
function onEndCrop(coords, dimensions) {
$("left").value = (coords.x1 / $("image").width).toFixed(4);
$("right").value = (coords.x2 / $("image").width).toFixed(4);
$("top").value = (coords.y1 / $("image").height).toFixed(4);
$("bottom").value = (coords.y2 / $("image").height).toFixed(4);
}
// example with a preview of crop results, must have minimumm dimensions
var width = $("image").width;
var height = $("image").height;
var options =
{
displayOnInit: true,
onEndCrop: onEndCrop,
minWidth: 1,
minHeight: 1
}
'/* Default to a square selection. */'
if(width < height)
options.onloadCoords = { x1: width/4, y1: width/4, x2: width*2/4, y2: width*2/4 }
else
options.onloadCoords = { x1: height/4, y1: height/4, x2: height*2/4, y2: height*2/4 }
new Cropper.ImgWithPreview("image", options)
OnKey(13, {AlwaysAllowOpera: true}, function(e) {
$("crop").submit();
return true;
});
</script>
</div>

191
app/views/inline/edit.php Executable file
View File

@ -0,0 +1,191 @@
<?php
if ($this->inline->inline_images->size() > 0) {
list($block, $script, $inline_html_id) = $this->format_inline($this->inline, 0, "inline", "");
} else {
$block = $script = $inline_html_id = null;
}
?>
<div>
<?= $this->t('.tag', ['id' => $this->inline->id]) ?>
<?php if (current_user()->has_permission($this->inline)) : ?>
| <a href="#" onclick='InlineImage.expand("<?= $inline_html_id ?>"); return false;'><?= $this->t('.preview') ?></a>
<?php if ($this->inline->inline_images->size() < 9) : ?>
<span id="post-add-button">| <a href="#" onclick="show_post_add(); return false;"><?= $this->t('.add_image') ?></a></span>
<?php endif ?>
<?php endif ?>
<p>
<div id="post-add" style="display: none;">
<?= $this->formTag(['action' => "add_image"], ['level' => 'member', 'multipart' => true], function(){ ?>
<?= $this->hiddenFieldTag("id", $this->inline->id) ?>
<div id="posts">
<table class="form">
<tbody>
<tr>
<th width="15%"><label for="image_file"><?= $this->t('.file') ?></label></th>
<td width="85%"><?= $this->fileField("image", "file", ['size' => 50, 'tabindex' => 1]) ?></td>
</tr>
<tr>
<th>
<label for="image_source"><?= $this->t('.source') ?></label>
</th>
<td>
<?= $this->textField('image', 'source', ['size' => 50, 'tabindex' => 2]) ?>
</td>
</tr>
<tr>
<th></th>
<td>
<?= $this->submitTag($this->t('.add_do')) ?>
</td>
</tr>
</tbody>
</table>
</div>
<?php }) ?>
</div>
</div>
<div style="width: 100%; overflow: auto;">
<?= $block ?>
</div>
<div id="inline-edit">
<?php foreach ($this->inline->inline_images as $image) : ?>
<?= $this->formTag(['action' => "delete_image"], ['id' => "delete-image-" . $image->id], function() use ($image) { ?>
<?= $this->hiddenFieldTag("id", $image->id) ?>
<?php }) ?>
<?php endforeach ?>
<?= $this->formTag('#update', function() { ?>
<label for="inline_description"><?= $this->t('.description') ?>:</label>
<br>
<span id="inline-description">
<a id="inline-description-edit-button" href="#" onclick="show_edit_desc(); return false;">
<?= $this->format_text($this->inline->description) ?>
<?php if (!$this->inline->description) echo $this->t('.add_set_description_info') ?>
</a>
<br>
</span>
<div id="inline-description-edit" style="display: none;">
<?= $this->textArea('inline', 'description', ['size' => "40x5"]) ?>
</div>
<br>
<?php foreach ($this->inline->inline_images as $image) : ?>
<div id="inline-<?= $image->id ?>" class="inline-image-entry" style="margin-bottom: 1.5em;">
<div style="height: 1.6em;">
<span style="line-height: 1.6em;">
<?php if (current_user()->has_permission($this->inline)) : ?>
<?= $this->linkTo($this->t('.remove'), "#", ['onclick' => "if(confirm('" . $this->t('.remove_confirm') . "')) $('delete-image-" . $image->id . "').submit(); return false;"]) ?>
| <?= $this->linkToFunction($this->t('.move.up'), "orderShift(" . $image->id . ", -1)") ?>
| <?= $this->linkToFunction($this->t('.move.down'), "orderShift(" . $image->id . ", +1)") ?>
|
<a href="#" id="image-description-<?= $image->id ?>" onclick="show_edit_inline_desc(<?= $image->id ?>); return false;">
<?php if (!$image->description) : ?>
<?= $this->t('.add_image_description') ?>
<?php else: ?>
<?= $this->h($image->description) ?>
<?php endif ?>
</a>
<span id="image-description-edit-<?= $image->id ?>" style="display: none;">
<?= $this->textFieldTag("image[" . $image->id . "][description]", $image->description, ['size' => 40, 'disabled' => (!current_user()->has_permission($this->inline))]) ?>
</span>
<?php else: ?>
<?= $this->h($image->description) ?>
<?php endif ?>
</span>
</div>
<img style="display: inline" src="<?= $image->preview_url() ?>" width="<?= $image->preview_dimensions()['width'] ?>" height="<?= $image->preview_dimensions()['height'] ?>"></img>
<?= $this->hiddenFieldTag("image[" . $image->id . "][sequence]", $image->sequence, ['size' => 10, 'disabled' => (!current_user()->has_permission($this->inline)), 'class' => "inline-sequence"]) ?>
<div>
</div>
</div>
<?php endforeach ?>
<?= $this->hiddenFieldTag("id", $this->inline->id) ?>
<?= $this->submitTag($this->t('.save'), ['disabled' => (!current_user()->has_permission($this->inline))]) ?>
<?php }) ?>
</div>
</div>
<script>
<?= $script ?>
InlineImage.init();
function show_post_add()
{
$("post-add").show();
$("post-add-button").hide();
}
function show_edit_desc()
{
$("inline-description-edit").show();
$("inline-description").hide();
$("inline-description-edit").down("textarea").focus();
}
function show_edit_inline_desc(id)
{
$("image-description-" + id).hide();
$("image-description-edit-" + id).show();
$("image-description-edit-" + id).down("input").focus();
}
function orderShift(id, direction) {
var first_image = $("inline-" + id);
var second_image;
if(direction > 0)
{
var sibs = first_image.nextSiblings();
second_image = sibs[0];
}
else
{
second_image = first_image;
var sibs = first_image.previousSiblings();
first_image = sibs[0];
}
if(!first_image || !second_image)
return;
{
var swap = first_image.down(".inline-sequence").value;
first_image.down(".inline-sequence").value = second_image.down(".inline-sequence").value;
second_image.down(".inline-sequence").value = swap;
}
var parentNode = second_image.parentNode;
parentNode.removeChild(second_image);
parentNode.insertBefore(second_image, first_image);
}
</script>
<?= $this->formTag(['action' => "delete"], ['id' => "delete-group"], function(){ ?>
<?= $this->hiddenFieldTag("id", $this->inline->id) ?>
<?php }) ?>
<?= $this->formTag(['action' => "copy"], ['id' => "copy-group"], function() { ?>
<?= $this->hiddenFieldTag("id", $this->inline->id) ?>
<?php }) ?>
<?php $this->contentFor('subnavbar', function() { ?>
<?php if (current_user()->has_permission($this->inline)) : ?>
<li><?= $this->linkTo($this->t('.delete'), "#", ['onclick' => "if(confirm('" . $this->t('.delete_confirm') . "')) $('delete-group').submit(); return false;"]) ?></li>
<li><?= $this->linkTo($this->t('.crop'), ['action' => "crop", 'id' => $this->inline->id]) ?></li>
<?php endif ?>
<li><?= $this->linkTo($this->t('.copy'), "#", ['onclick' => "$('copy-group').submit(); return false;", 'level' => 'member']) ?></li>
<?php }) ?>
<?= $this->partial("footer") ?>

45
app/views/inline/index.php Executable file
View File

@ -0,0 +1,45 @@
<div id="pool-index">
<table width="100%" class="highlightable">
<thead>
<tr>
<th width="40px"><?= $this->t('.first_image') ?></th>
<th width="60%"><?= $this->t('.description2') ?></th>
<th width="*"><?= $this->t('.user') ?></th>
<th width="*"><?= $this->t('.images') ?></th>
<th width="*"><?= $this->t('.created') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($this->inlines as $p) : ?>
<tr class="<?= $this->cycle('even', 'odd') ?>" id="p<?= $p->id ?>">
<td>
<a href='<?= $this->urlFor(['#edit', 'id' => $p->id]) ?>'>
<?php if ($p->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']]) ?>
<?php else: ?>
(no images)
<?php endif ?>
</a>
</td>
<td><?= $this->h($p->description) ?></td>
<td><?= $this->linkTo($this->h($p->user->pretty_name()), ["user#show", 'id' => $p->user->id]) ?></td>
<td><?= $p->inline_images->size() ?></td>
<td><?= $this->t('time.x_ago', ['t' => $this->t($this->timeAgoInWords($p->created_at))]) ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
</div>
<div id="paginator">
<?= $this->willPaginate($this->inlines) ?>
</div>
<?= $this->formTag(["#create"], ['id' => "create-new"], function(){ ?>
<?php }) ?>
<?php $this->contentFor('subnavbar', function() { ?>
<li><?= $this->linkTo($this->t('.create'), "#", ['level' => 'member', 'onclick' => "$('create-new').submit(); return false;"]) ?></li>
<?php }) ?>
<?= $this->partial("footer") ?>

View File

@ -18,4 +18,10 @@ en:
notes_create_notice: To create a note, <br/>Shift + Click + Drag<br/> over the image. More info in the %{notes_help}. (%{close})
static13a: Moderate
static62a: Index
static62a: Index
# These ones are missing:
inline:
index:
description2: Image-set description
created: Created

View File

@ -0,0 +1,74 @@
<?php
return array (
0 =>
array (
'id' =>
array (
'type' => 'int(11)',
'default' => NULL,
),
'inline_id' =>
array (
'type' => 'int(11)',
'default' => NULL,
),
'md5' =>
array (
'type' => 'varchar(32)',
'default' => NULL,
),
'file_ext' =>
array (
'type' => 'varchar(4)',
'default' => NULL,
),
'description' =>
array (
'type' => 'text',
'default' => NULL,
),
'sequence' =>
array (
'type' => 'int(11)',
'default' => NULL,
),
'width' =>
array (
'type' => 'int(11)',
'default' => NULL,
),
'height' =>
array (
'type' => 'int(11)',
'default' => NULL,
),
'sample_width' =>
array (
'type' => 'int(11)',
'default' => NULL,
),
'sample_height' =>
array (
'type' => 'int(11)',
'default' => NULL,
),
'created_at' =>
array (
'type' => 'datetime',
'default' => NULL,
),
'updated_at' =>
array (
'type' => 'datetime',
'default' => NULL,
),
),
1 =>
array (
'pri' =>
array (
0 => 'id',
),
),
)
;

View File

@ -0,0 +1,39 @@
<?php
return array (
0 =>
array (
'id' =>
array (
'type' => 'int(11)',
'default' => NULL,
),
'user_id' =>
array (
'type' => 'int(11)',
'default' => NULL,
),
'description' =>
array (
'type' => 'text',
'default' => NULL,
),
'created_at' =>
array (
'type' => 'datetime',
'default' => NULL,
),
'updated_at' =>
array (
'type' => 'datetime',
'default' => NULL,
),
),
1 =>
array (
'pri' =>
array (
0 => 'id',
),
),
)
;

15
lib/Moebooru/TempfilePrefix.php Executable file
View File

@ -0,0 +1,15 @@
<?php
namespace Moebooru;
trait TempfilePrefix
{
public $tempfile_prefix;
public function tempfile_prefix()
{
if (!$this->tempfile_prefix) {
$this->tempfile_prefix = \Rails::publicPath() . '/data/temp-' . uniqid('tfp_');
}
return $this->tempfile_prefix;
}
}