Add manual image conversion for admin

This commit is contained in:
Kijin Sung 2024-12-16 01:50:44 +09:00
parent 818fd54b00
commit 84acc21817
12 changed files with 312 additions and 23 deletions

View file

@ -14,11 +14,14 @@
<action name="procFileGetList" type="controller" permission="root" />
<action name="dispFileAdminList" type="view" admin_index="true" menu_name="file" menu_index="true" />
<action name="dispFileAdminEdit" type="view" menu_name="file" />
<action name="dispFileAdminUploadConfig" type="view" menu_name="file" />
<action name="dispFileAdminDownloadConfig" type="view" menu_name="file" />
<action name="dispFileAdminOtherConfig" type="view" menu_name="file" />
<action name="procFileAdminAddCart" type="controller" />
<action name="procFileAdminEditFileName" type="controller" />
<action name="procFileAdminEditImage" type="controller" />
<action name="procFileAdminDeleteChecked" type="controller" ruleset="deleteChecked" />
<action name="procFileAdminInsertUploadConfig" type="controller" ruleset="insertConfig" />
<action name="procFileAdminInsertDownloadConfig" type="controller" />

View file

@ -296,6 +296,142 @@ class FileAdminController extends File
else $_SESSION['file_management'][$output->file_srl] = true;
}
}
/**
* Edit filename
*/
public function procFileAdminEditFileName()
{
$file_srl = Context::get('file_srl');
if (!$file_srl)
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
}
$file = FileModel::getFile($file_srl);
if (!$file)
{
throw new Rhymix\Framework\Exceptions\TargetNotFound;
}
$file_name = trim(utf8_normalize_spaces(utf8_clean(Context::get('file_name'))));
$file_name = Rhymix\Framework\Filters\FilenameFilter::clean($file_name);
if ($file_name === '')
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
}
$output = executeQuery('file.updateFileName', [
'file_srl' => $file_srl,
'source_filename' => $file_name,
]);
if (!$output->toBool())
{
return $output;
}
$this->setMessage('success_updated');
$this->setRedirectUrl(Context::get('success_return_url') ?: getNotEncodedUrl(['module' => 'admin', 'act' => 'dispFileAdminEdit', 'file_srl' => $file_srl]));
}
/**
* Edit image
*/
public function procFileAdminEditImage()
{
$file_srl = Context::get('file_srl');
if (!$file_srl)
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
}
$file = FileModel::getFile($file_srl);
if (!$file)
{
throw new Rhymix\Framework\Exceptions\TargetNotFound;
}
// Validate user input.
$width = intval(Context::get('new_width'));
$height = intval(Context::get('new_height'));
$format = Context::get('format');
$quality = intval(Context::get('quality'));
if ($width <= 0 || $height <= 0 || !in_array($format, ['jpg', 'png', 'webp']) || $quality <= 0 || $quality > 100)
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
}
// Generate filenames.
$uploaded_filename = FileHandler::getRealPath($file->uploaded_filename);
$temp_filename = \RX_BASEDIR . 'files/cache/temp/' . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $format;
$new_filename = preg_replace('/\.[a-z]+$/', '.' . $format, $file->source_filename);
$del_filename = null;
// Should this file be moved from binaries to images?
if (str_starts_with($uploaded_filename, \RX_BASEDIR . 'files/attach/binaries/'))
{
$del_filename = $uploaded_filename;
$uploaded_filename = preg_replace('!/files/attach/binaries/!', '/files/attach/images/', $uploaded_filename, 1) . '.' . $format;
}
// Resize the image using GD or ImageMagick.
$config = FileModel::getFileConfig();
$result = FileHandler::createImageFile(FileHandler::getRealPath($file->uploaded_filename), $temp_filename, $width, $height, $format, 'fill', $quality);
if (!$result && !empty($config->magick_command))
{
$command = vsprintf('%s %s -resize %dx%d -quality %d %s %s %s', [
\RX_WINDOWS ? escapeshellarg($config->magick_command) : $config->magick_command,
escapeshellarg(FileHandler::getRealPath($file->uploaded_filename)),
$width, $height, $quality,
'-auto-orient -strip',
'-limit memory 64MB -limit map 128MB -limit disk 1GB',
escapeshellarg($temp_filename),
]);
@exec($command, $output, $return_var);
$result = $return_var === 0 ? true : false;
}
// If successfully resized, replace original file and update the image size in DB.
if ($result && Rhymix\Framework\Storage::exists($temp_filename) && filesize($temp_filename) > 0)
{
$moved = Rhymix\Framework\Storage::move($temp_filename, $uploaded_filename);
if (!$moved)
{
throw new Rhymix\Framework\Exception;
}
if ($del_filename)
{
Rhymix\Framework\Storage::delete($del_filename);
}
clearstatcache(true, $uploaded_filename);
$filesize = filesize($uploaded_filename);
$relative_path = preg_replace('!^' . preg_quote(\RX_BASEDIR, '!') . '!', './', $uploaded_filename, 1);
$updated = executeQuery('file.updateFile', [
'file_srl' => $file_srl,
'module_srl' => $file->module_srl,
'upload_target_srl' => $file->upload_target_srl,
'source_filename' => $new_filename,
'uploaded_filename' => $relative_path,
'direct_download' => 'Y',
'mime_type' => Rhymix\Framework\MIME::getTypeByFilename($new_filename),
'original_type' => $file->original_type ?: $file->mime_type,
'is_cover' => $file->cover_image,
'file_size' => $filesize,
'width' => $width,
'height' => $height,
]);
if (!$updated->toBool())
{
return $updated;
}
}
else
{
throw new Rhymix\Framework\Exception;
}
$this->setMessage('success_updated');
$this->setRedirectUrl(Context::get('success_return_url') ?: getNotEncodedUrl(['module' => 'admin', 'act' => 'dispFileAdminEdit', 'file_srl' => $file_srl]));
}
}
/* End of file file.admin.controller.php */
/* Location: ./modules/file/file.admin.controller.php */

View file

@ -6,20 +6,12 @@
*/
class FileAdminView extends File
{
/**
* Initialization
* @return void
*/
function init()
{
}
/**
* Display output list (for administrator)
*
* @return Object
*/
function dispFileAdminList()
public function dispFileAdminList()
{
// Options to get a list
$args = new stdClass();
@ -213,11 +205,35 @@ class FileAdminView extends File
}
/**
* Upload config screen
*
* @return Object
* File edit screen
*/
function dispFileAdminUploadConfig()
public function dispFileAdminEdit()
{
$file_srl = intval(Context::get('file_srl'));
if (!$file_srl)
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
}
$file = FileModel::getFile($file_srl);
if (!$file)
{
throw new Rhymix\Framework\Exceptions\TargetNotFound;
}
$config = FileModel::getFileConfig();
Context::set('config', $config);
Context::set('file', $file);
Context::set('is_ffmpeg', function_exists('exec') && !empty($config->ffmpeg_command) && Rhymix\Framework\Storage::isExecutable($config->ffmpeg_command) && !empty($config->ffprobe_command) && Rhymix\Framework\Storage::isExecutable($config->ffprobe_command));
Context::set('is_magick', function_exists('exec') && !empty($config->magick_command) && Rhymix\Framework\Storage::isExecutable($config->magick_command));
$this->setTemplatePath($this->module_path . 'tpl');
$this->setTemplateFile('file_edit');
}
/**
* Upload config screen
*/
public function dispFileAdminUploadConfig()
{
$oFileModel = getModel('file');
$config = $oFileModel->getFileConfig();
@ -233,10 +249,8 @@ class FileAdminView extends File
/**
* Download config screen
*
* @return Object
*/
function dispFileAdminDownloadConfig()
public function dispFileAdminDownloadConfig()
{
$oFileModel = getModel('file');
$config = $oFileModel->getFileConfig();
@ -249,10 +263,8 @@ class FileAdminView extends File
/**
* Other config screen
*
* @return Object
*/
function dispFileAdminOtherConfig()
public function dispFileAdminOtherConfig()
{
$oFileModel = getModel('file');
$config = $oFileModel->getFileConfig();

View file

@ -132,3 +132,6 @@ $lang->msg_cannot_use_exec = 'The exec() function is disabled on this server.';
$lang->msg_cannot_use_ffmpeg = 'In order to use this feature, PHP must be able to execute \'ffmpeg\' and \'ffprobe\' commands.';
$lang->msg_cannot_use_exif = 'In order to use this feature, PHP must be installed with the \'exif\' extension.';
$lang->msg_need_magick = 'In order to handle AVIF and HEIC formats, PHP must be able to execute the \'magick\' command from ImageMagick 7.x or higher.';
$lang->image_conversion = 'Image conversion';
$lang->image_size = 'Image size';
$lang->image_format = 'Image format';

View file

@ -135,3 +135,6 @@ $lang->msg_cannot_use_exec = '이 서버에서 exec() 함수를 사용할 수
$lang->msg_cannot_use_ffmpeg = '이 기능을 사용하려면 PHP에서 ffmpeg 및 ffprobe 명령을 실행할 수 있어야 합니다.';
$lang->msg_cannot_use_exif = '이 기능을 사용하려면 PHP exif 확장모듈이 필요합니다.';
$lang->msg_need_magick = 'AVIF, HEIC 변환을 위해서는 PHP에서 ImageMagick 7.x 이상의 magick 명령을 실행할 수 있어야 합니다.';
$lang->image_conversion = '이미지 변환';
$lang->image_size = '이미지 크기';
$lang->image_format = '이미지 포맷';

View file

@ -3,11 +3,19 @@
<table name="files" />
</tables>
<columns>
<column name="module_srl" var="module_srl" filter="number" notnull="notnull" />
<column name="upload_target_srl" var="upload_target_srl" filter="number" notnull="notnull" />
<column name="upload_target_type" var="upload_target_type" />
<column name="module_srl" var="module_srl" filter="number" notnull="notnull" />
<column name="source_filename" var="source_filename" />
<column name="uploaded_filename" var="uploaded_filename" notnull="notnull" minlength="1" maxlength="250" />
<column name="cover_image" var="is_cover" default="N" />
<column name="direct_download" var="direct_download" />
<column name="mime_type" var="mime_type" />
<column name="original_type" var="original_type" />
<column name="file_size" var="file_size" />
<column name="width" var="width" />
<column name="height" var="height" />
<column name="duration" var="duration" />
</columns>
<conditions>
<condition operation="equal" column="file_srl" var="file_srl" filter="number" notnull="notnull" />

View file

@ -0,0 +1,11 @@
<query id="updateFileName" action="update">
<tables>
<table name="files" />
</tables>
<columns>
<column name="source_filename" var="source_filename" notnull="notnull" />
</columns>
<conditions>
<condition operation="equal" column="file_srl" var="file_srl" filter="number" notnull="notnull" />
</conditions>
</query>

View file

@ -2,4 +2,8 @@
margin-top: -10px;
border-bottom: 1px solid #ccc;
background: #f5f5f5;
}
}
.x_help-block .preset_size {
font-size: 12px;
}

View file

@ -0,0 +1,83 @@
<include target="header.html" />
<div cond="$XE_VALIDATOR_MESSAGE && $XE_VALIDATOR_ID == 'modules/file/tpl/edit/1'" class="message {$XE_VALIDATOR_MESSAGE_TYPE}">
<p>{$XE_VALIDATOR_MESSAGE}</p>
</div>
<div cond="$XE_VALIDATOR_MESSAGE && $XE_VALIDATOR_ID == 'modules/file/tpl/edit/2'" class="message {$XE_VALIDATOR_MESSAGE_TYPE}">
<p>{$XE_VALIDATOR_MESSAGE}</p>
</div>
<section class="section">
<h2>{$lang->file_name}</h2>
<form action="./" method="post" class="x_form-horizontal">
<input type="hidden" name="module" value="file" />
<input type="hidden" name="act" value="procFileAdminEditFileName" />
<input type="hidden" name="xe_validator_id" value="modules/file/tpl/edit/1" />
<input type="hidden" name="file_srl" value="{$file->file_srl}" />
<div class="x_control-group">
<label class="x_control-label" for="file_name">{$lang->file_name}</label>
<div class="x_controls">
<input type="text" name="file_name" id="file_name" value="{$file->source_filename}" />
</div>
</div>
<div class="x_clearfix btnArea">
<div class="x_pull-right">
<button type="submit" class="x_btn x_btn-primary">{$lang->cmd_save}</button>
</div>
</div>
</form>
</section>
<!--@if(preg_match('/\.(gif|jpe?g|png|bmp|webp|heic|avif)$/i', $file->source_filename, $matches))-->
<!--@if(!$file->width || !$file->height)-->
{@ list($file->width, $file->height) = getimagesize($file->uploaded_filename)}
<!--@endif-->
{@ $extension = strtolower($matches[1]); if ($extension === 'jpeg') $extension = 'jpg'; }
<section class="section">
<h2>{$lang->image_conversion}</h2>
<form action="./" method="post" class="x_form-horizontal">
<input type="hidden" name="module" value="file" />
<input type="hidden" name="act" value="procFileAdminEditImage" />
<input type="hidden" name="xe_validator_id" value="modules/file/tpl/edit/2" />
<input type="hidden" name="file_srl" value="{$file->file_srl}" />
<input type="hidden" name="original_width" value="{$file->width}" />
<input type="hidden" name="original_height" value="{$file->height}" />
<div class="x_control-group">
<label class="x_control-label">{$lang->image_size}</label>
<div class="x_controls">
<input type="number" name="new_width" id="new_width" value="{$file->width}" /> x
<input type="number" name="new_height" id="new_height" value="{$file->height}" />
<p class="x_help-block">
<button type="button" class="preset_size">2560</button>
<button type="button" class="preset_size">1920</button>
<button type="button" class="preset_size">1440</button>
<button type="button" class="preset_size">1280</button>
<button type="button" class="preset_size">1024</button>
</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label" for="format">{$lang->image_format}</label>
<div class="x_controls">
<select name="format" id="format">
<option value="jpg" selected="selected"|cond="$extension === 'jpg'">JPG</option>
<option value="png" selected="selected"|cond="$extension === 'png'">PNG</option>
<option value="webp" selected="selected"|cond="$extension === 'webp'">WebP</option>
</select>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label" for="quality">{$lang->image_quality}</label>
<div class="x_controls">
<input type="number" min="50" max="100" name="quality" id="quality" value="{$config->image_quality_adjustment ?? 75}" /> %
</div>
</div>
<div class="x_clearfix btnArea">
<div class="x_pull-right">
<button type="submit" class="x_btn x_btn-primary">{$lang->cmd_save}</button>
</div>
</div>
</form>
</section>
<!--@endif-->

View file

@ -50,6 +50,7 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
</th>
<th scope="col" class="nowr">{$lang->ipaddress}</th>
<th scope="col" class="nowr">{$lang->status}</th>
<th scope="col" class="nowr">{$lang->cmd_edit}</th>
<th scope="col"><input type="checkbox" data-name="cart" title="Check All" /></th>
</tr>
</thead>
@ -66,7 +67,7 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
<!--@end-->
{@ $cur_upload_target_srl = $val->upload_target_srl}
<tr>
<th colspan="8" scope="col">
<th colspan="9" scope="col">
<!--@if(!$val->upload_target_type)-->
<!--@if($val->isvalid=='Y')-->
{$lang->is_valid}
@ -112,6 +113,9 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
<td class="nowr">{zdate($val->regdate,"Y-m-d H:i")}</td>
<td class="nowr"><a href="{getUrl('search_target','ipaddress','search_keyword',$val->ipaddress)}">{$val->ipaddress}</a></td>
<td class="nowr"><!--@if($val->isvalid=='Y')-->{$lang->is_valid}<!--@else-->{$lang->is_stand_by}<!--@end--></td>
<td class="nowr">
<a href="{getUrl(['module' => 'admin', 'act' => 'dispFileAdminEdit', 'file_srl' => $val->file_srl])}">{$lang->cmd_edit}</a>
</td>
<td>
<input type="checkbox" name="cart" value="{$val->file_srl}" />
</td>

View file

@ -1,3 +1,4 @@
<load target="css/config.css" />
<load target="js/file_admin.js" />
<div class="x_page-header">

View file

@ -6,7 +6,7 @@ function getFileList() {
var fileListTable = jQuery('#fileListTable');
var cartList = [];
fileListTable.find(':checkbox[name=cart]').each(function(){
if(this.checked) cartList.push(this.value);
if(this.checked) cartList.push(this.value);
});
var params = new Array();
@ -58,3 +58,24 @@ function checkSearch(form)
}
*/
}
(function() {
$(function() {
$('.preset_size').on('click', function() {
const preset_size = parseInt($(this).text(), 10);
const width = parseInt($('input[name=original_width]').val(), 10);
const height = parseInt($('input[name=original_height]').val(), 10);
let new_width = 0;
let new_height = 0;
if (width > height) {
new_width = preset_size;
new_height = Math.round(preset_size * (height / width));
} else {
new_width = Math.round(preset_size * (width / height));
new_height = preset_size;
}
$('input[name=new_width]').val(new_width);
$('input[name=new_height]').val(new_height);
});
});
})(jQuery);