Merge branch 'rhymix:master' into master

This commit is contained in:
Lastorder 2025-03-03 10:47:47 +09:00 committed by GitHub
commit e5b729f8e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
152 changed files with 2348 additions and 792 deletions

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon version="0.2">
<title xml:lang="ko">어드민 메뉴 접근 로깅</title>
<title xml:lang="en">admin menu access logging</title>
<title xml:lang="en">Logging Access to the Administrator Menu</title>
<title xml:lang="zh-CN">后台访问日志</title>
<title xml:lang="zh-TW">管理選單訪問日誌</title>
<description xml:lang="ko">
@ -13,6 +13,9 @@
<description xml:lang="zh-TW">
管理選單訪問紀錄及登入日誌。
</description>
<description xml:lang="en">
This addon will record Rhymix administrators' access to the menu.
</description>
<version>RX_VERSION</version>
<date>RX_CORE</date>

View file

@ -6,7 +6,7 @@
본문 이미지를 하나의 갤러리 처럼 볼 수 있도록 하는 애드온입니다.
</description>
<description xml:lang="en">
Swipe your images of an document on your screens.
Swipe your images of a post on your screen.
</description>
<license>MIT License (codes from http://photoswipe.com/), GPLv2 (other codes by Rhymix contributors)</license>
<version>RX_VERSION</version>
@ -23,12 +23,16 @@
<extra_vars>
<var name="display_name" type="select">
<title xml:lang="ko">파일이름 출력 설정</title>
<title xml:lang="en">Display Filenames</title>
<description xml:lang="ko">넘겨보기 실행시 하단에 파일이름을 출력할 것인지 여부를 선택합니다. 기본값은 사용하도록 되어있습니다.</description>
<description xml:lang="en">Whether the PhotoSwipe displays the filenames or not. The default is to display.</description>
<options value="block">
<title xml:lang="ko">사용</title>
<title xml:lang="en">Display</title>
</options>
<options value="none">
<title xml:lang="ko">사용 안함</title>
<title xml:lang="en">Hide</title>
</options>
</var>
</extra_vars>

View file

@ -706,8 +706,8 @@ class Context
*/
public static function getFTPInfo()
{
$ftp_info = self::$_instance->db_info->ftp_info;
if (!$ftp_info->ftp_user || !$ftp_info->ftp_root_path)
$ftp_info = self::$_instance->db_info->ftp_info ?? null;
if (empty($ftp_info->ftp_user) || empty($ftp_info->ftp_root_path))
{
return null;
}
@ -2508,11 +2508,19 @@ class Context
* Add html code before </head>
*
* @param string $header add html code before </head>.
* @param bool $prepend
* @return void
*/
public static function addHtmlHeader($header)
public static function addHtmlHeader($header, bool $prepend = false)
{
self::$_instance->html_header .= (self::$_instance->html_header ? "\n" : '') . $header;
if ($prepend)
{
self::$_instance->html_header = $header . (self::$_instance->html_header ? "\n" : '') . self::$_instance->html_header;
}
else
{
self::$_instance->html_header .= (self::$_instance->html_header ? "\n" : '') . $header;
}
}
/**
@ -2594,10 +2602,19 @@ class Context
* Add html code after <body>
*
* @param string $header Add html code after <body>
* @param bool $prepend
* @return void
*/
public static function addBodyHeader($header)
public static function addBodyHeader($header, bool $prepend = false)
{
self::$_instance->body_header .= (self::$_instance->body_header ? "\n" : '') . $header;
if ($prepend)
{
self::$_instance->body_header = $header . (self::$_instance->body_header ? "\n" : '') . self::$_instance->body_header;
}
else
{
self::$_instance->body_header .= (self::$_instance->body_header ? "\n" : '') . $header;
}
}
/**
@ -2614,10 +2631,19 @@ class Context
* Add html code before </body>
*
* @param string $footer Add html code before </body>
* @param bool $prepend
* @return void
*/
public static function addHtmlFooter($footer)
public static function addHtmlFooter($footer, bool $prepend = false)
{
self::$_instance->html_footer .= (self::$_instance->html_footer ? "\n" : '') . $footer;
if ($prepend)
{
self::$_instance->html_footer = $footer . (self::$_instance->html_footer ? "\n" : '') . self::$_instance->html_footer;
}
else
{
self::$_instance->html_footer .= (self::$_instance->html_footer ? "\n" : '') . $footer;
}
}
/**

View file

@ -97,7 +97,7 @@ class DisplayHandler extends Handler
}
else
{
if($responseMethod == 'JSON' || $responseMethod == 'JS_CALLBACK' || isset($_SERVER['HTTP_X_AJAX_COMPAT']) || isset($_POST['_rx_ajax_compat']))
if($responseMethod == 'JSON' || isset($_SERVER['HTTP_X_AJAX_COMPAT']) || isset($_POST['_rx_ajax_compat']))
{
self::_printJSONHeader();
}

View file

@ -136,7 +136,6 @@ class HTMLDisplayHandler
// compile if connected to the layout
if($layout_srl > 0)
{
// handle separately if the layout is faceoff
if($layout_info && isset($layout_info->type) && $layout_info->type == 'faceoff')
{
@ -164,6 +163,16 @@ class HTMLDisplayHandler
$oTemplate = new Rhymix\Framework\Template;
$output = $oTemplate->compile($layout_path, $layout_file, $edited_layout_file);
// Add layout header script.
if ($layout_srl > 0)
{
$part_config = ModuleModel::getModulePartConfig('layout', $layout_srl);
if ($part_config && isset($part_config->header_script))
{
Context::addHtmlHeader($part_config->header_script, true);
}
}
// if popup_layout, remove admin bar.
$realLayoutPath = FileHandler::getRealPath($layout_path);
if(substr_compare($realLayoutPath, '/', -1) !== 0)

View file

@ -72,7 +72,7 @@ class FrontEndFileHandler extends Handler
* <pre>
* case js
* $args[0]: file name
* $args[1]: type (head | body)
* $args[1]: type (head | body | module)
* $args[2]: unused (previously targetIe)
* $args[3]: index
* case css
@ -245,6 +245,7 @@ class FrontEndFileHandler extends Handler
}
else if($file->fileExtension == 'js')
{
$file->jstype = $media === 'module' ? $media : '';
$file->key = sprintf('%s/%s', $file->filePath, $file->keyName);
}
@ -489,7 +490,7 @@ class FrontEndFileHandler extends Handler
$concat_filename = self::$assetdir . '/combined/' . sha1(serialize($concat_files)) . '.css';
if (!file_exists(\RX_BASEDIR . $concat_filename) || filemtime(\RX_BASEDIR . $concat_filename) < $concat_max_timestamp)
{
$concat_content = Rhymix\Framework\Formatter::concatCSS($concat_files, $concat_filename);
$concat_content = Rhymix\Framework\Formatter::concatCSS($concat_files, \RX_BASEDIR . $concat_filename);
$concat_content = '@charset "UTF-8";' . "\n\n" . preg_replace('/@charset\s*[\'"][a-z0-9-]+[\'"];\s*/i', '', $concat_content);
Rhymix\Framework\Storage::write(\RX_BASEDIR . $concat_filename, $concat_content);
}
@ -584,7 +585,8 @@ class FrontEndFileHandler extends Handler
$concat_filename = self::$assetdir . '/combined/' . sha1(serialize($concat_files)) . '.js';
if (!file_exists(\RX_BASEDIR . $concat_filename) || filemtime(\RX_BASEDIR . $concat_filename) < $concat_max_timestamp)
{
Rhymix\Framework\Storage::write(\RX_BASEDIR . $concat_filename, Rhymix\Framework\Formatter::concatJS($concat_files, $concat_filename));
$concat_content = Rhymix\Framework\Formatter::concatJS($concat_files, \RX_BASEDIR . $concat_filename);
Rhymix\Framework\Storage::write(\RX_BASEDIR . $concat_filename, $concat_content);
}
$concat_filename .= '?t=' . filemtime(\RX_BASEDIR . $concat_filename);
$result[] = array('file' => \RX_BASEURL . $concat_filename);
@ -602,7 +604,12 @@ class FrontEndFileHandler extends Handler
{
$url .= '?t=' . filemtime($file->fileFullPath);
}
$result[] = array('file' => $url);
$attrs = '';
if ($file->jstype)
{
$attrs = ' type="' . $file->jstype . '"';
}
$result[] = array('file' => $url, 'attrs' => $attrs);
}
}
}
@ -624,7 +631,7 @@ class FrontEndFileHandler extends Handler
{
foreach ($indexedMap as $file)
{
if ($file->isExternalURL || !is_readable($file->fileFullPath))
if ($file->isExternalURL || !is_readable($file->fileFullPath) || !empty($file->jstype))
{
$concat_key++;
$concat_list[$concat_key][] = $file;

View file

@ -268,13 +268,6 @@ class ModuleHandler extends Handler
// Reset layout_srl in module_info.
$module_info->{$targetSrl} = $layoutSrl;
// Add layout header script.
$part_config = ModuleModel::getModulePartConfig('layout', $layoutSrl);
if ($part_config && isset($part_config->header_script))
{
Context::addHtmlHeader($part_config->header_script);
}
}
else
{

View file

@ -133,6 +133,11 @@ require_once RX_BASEDIR . 'common/framework/DateTime.php';
require_once RX_BASEDIR . 'common/framework/Debug.php';
require_once RX_BASEDIR . 'common/framework/Lang.php';
/**
* Load system configuration.
*/
Rhymix\Framework\Config::init();
/**
* Load user configuration.
*/
@ -141,11 +146,6 @@ if(file_exists(RX_BASEDIR . 'config/config.user.inc.php'))
require_once RX_BASEDIR . 'config/config.user.inc.php';
}
/**
* Load system configuration.
*/
Rhymix\Framework\Config::init();
/**
* Install the debugger.
*/

View file

@ -3,7 +3,7 @@
/**
* RX_VERSION is the version number of the Rhymix CMS.
*/
define('RX_VERSION', '2.1.19');
define('RX_VERSION', '2.1.21');
/**
* RX_MICROTIME is the startup time of the current script, in microseconds since the Unix epoch.

View file

@ -6,7 +6,6 @@
* Copyright (c) Rhymix Developers and Contributors
*/
return array(
// YouTube
'www.youtube.com/',
'www.youtube-nocookie.com/',
@ -19,43 +18,17 @@ return array(
// Google Maps
'www.google.com/maps/embed',
'maps.google.com/',
'maps.google.co.kr/',
// Daum
'flvs.daum.net/',
'videofarm.daum.net/',
'api.v.daum.net/',
'videofarm.daum.net/',
// Giphy
'giphy.com/',
// Sooplive
'vod.sooplive.co.kr/',
// Kakao TV
'tv.kakao.com/',
'play-tv.kakao.com/',
// Naver
'serviceapi.rmcnmv.naver.com/',
'serviceapi.nmv.naver.com/',
'scrap.ad.naver.com/',
'static.campaign.naver.com/',
'musicplayer.naver.com/naverPlayer/posting/',
'player.music.naver.com/naverPlayer/posting/',
// Pandora TV
'www.pandora.tv/view/',
'flvr.pandora.tv/flv2pan/',
// Cyworld
'dbi.video.cyworld.com/v.sk/',
// Egloos
'v.egloos.com/v.sk/',
// Nate
'v.nate.com/v.sk/',
'w.blogdoc.nate.com/',
// SBS
'netv.sbs.co.kr/sbox/',
'news.sbs.co.kr/',
'wizard2.sbs.co.kr/',
'sbsplayer.sbs.co.kr/',
// Afreeca
'afree.ca/',
'chzzk.naver.com/',
);

View file

@ -487,9 +487,9 @@ class DB
}
// Collect various counts used in the page calculation.
list($is_expression, $list_count) = $query->navigation->list_count->getValue($args);
list($is_expression, $page_count) = $query->navigation->page_count->getValue($args);
list($is_expression, $page) = $query->navigation->page->getValue($args);
$list_count = $query->navigation->list_count->getValue($args)[0];
$page_count = $query->navigation->page_count->getValue($args)[0];
$page = $query->navigation->page->getValue($args)[0];
$total_count = intval($count);
$total_page = max(1, intval(ceil($total_count / $list_count)));
$last_index = $total_count - (($page - 1) * $list_count);
@ -531,6 +531,7 @@ class DB
$result = array();
$index = $last_index;
$step = $last_index !== 0 ? -1 : 1;
$count = 0;
$result_class = ($result_class && $result_class !== 'master') ? $result_class : 'stdClass';
if (!class_exists($result_class))
{
@ -540,6 +541,11 @@ class DB
{
$result[$index] = $row;
$index += $step;
$count++;
if ($count === 10000 && $this->_query_id !== '')
{
trigger_error('XML query ' . $this->_query_id . ' returned 10000 rows or more', E_USER_WARNING);
}
}
$stmt->closeCursor();

View file

@ -968,7 +968,7 @@ class Debug
case \E_USER_ERROR: return 'User Error';
case \E_USER_WARNING: return 'Warning';
case \E_USER_NOTICE: return 'Notice';
case \E_STRICT: return 'Strict Standards';
case 2048: return 'Strict Standards'; /* E_STRICT is deprecated */
case \E_PARSE: return 'Parse Error';
case \E_DEPRECATED: return 'Deprecated';
case \E_USER_DEPRECATED: return 'User Deprecated';

View file

@ -215,7 +215,10 @@ class HTTP
$proxy = parse_url($proxy);
$proxy_scheme = preg_match('/^(https|socks)/', $proxy['scheme'] ?? '') ? ($proxy['scheme'] . '://') : 'http://';
$proxy_auth = (!empty($proxy['user']) && !empty($proxy['pass'])) ? ($proxy['user'] . ':' . $proxy['pass'] . '@') : '';
$settings['proxy'] = sprintf('%s%s%s:%s', $proxy_scheme, $proxy_auth, $proxy['host'], $proxy['port']);
if (!empty($proxy['host']) && !empty($proxy['port']))
{
$settings['proxy'] = sprintf('%s%s%s:%s', $proxy_scheme, $proxy_auth, $proxy['host'], $proxy['port']);
}
}
self::$_client = new \GuzzleHttp\Client($settings);

View file

@ -25,7 +25,7 @@ class Queue
}
/**
* Get the default driver.
* Get a Queue driver instance.
*
* @param string $name
* @return ?Drivers\QueueInterface
@ -49,6 +49,16 @@ class Queue
}
}
/**
* Get the DB driver instance, for managing scheduled tasks.
*
* @return Drivers\Queue\DB
*/
public static function getDbDriver(): Drivers\Queue\DB
{
return self::getDriver('db');
}
/**
* Get the list of supported Queue drivers.
*
@ -86,7 +96,9 @@ class Queue
}
/**
* Add a task.
* Add a task to the queue.
*
* The queued task will be executed as soon as possible.
*
* The handler can be in one of the following formats:
* - Global function, e.g. myHandler
@ -128,29 +140,82 @@ class Queue
}
/**
* Get the first task to execute immediately.
* Add a task to be executed at a specific time.
*
* If no tasks are pending, this method will return null.
* Detailed scheduling of tasks will be handled by each driver.
* The queued task will be executed once at the configured time.
* The rest is identical to addTask().
*
* @param int $blocking
* @param int $time
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public static function addTaskAt(int $time, string $handler, ?object $args = null, ?object $options = null): int
{
if (!config('queue.enabled'))
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
// This feature always uses the DB driver.
$driver = self::getDbDriver();
return $driver->addTaskAt($time, $handler, $args, $options);
}
/**
* Add a task to be executed at an interval.
*
* The queued task will be executed repeatedly at the scheduled interval.
* The synax for specifying the interval is the same as crontab.
* The rest is identical to addTask().
*
* @param string $interval
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public static function addTaskAtInterval(string $interval, string $handler, ?object $args = null, ?object $options = null): int
{
if (!config('queue.enabled'))
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
// Validate the interval syntax.
if (!self::checkIntervalSyntax($interval))
{
throw new Exceptions\InvalidRequest('Invalid interval syntax: ' . $interval);
}
// This feature always uses the DB driver.
$driver = self::getDbDriver();
return $driver->addTaskAtInterval($interval, $handler, $args, $options);
}
/**
* Get information about a scheduled task if it exists.
*
* @param int $task_srl
* @return ?object
*/
public static function getTask(int $blocking = 0): ?object
public static function getScheduledTask(int $task_srl): ?object
{
$driver_name = config('queue.driver');
if (!$driver_name)
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
$driver = self::getDbDriver();
return $driver->getScheduledTask($task_srl);
}
$driver = self::getDriver($driver_name);
if (!$driver)
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
return $driver->getTask($blocking);
/**
* Cancel a scheduled task.
*
* @param int $task_srl
* @return bool
*/
public static function cancelScheduledTask(int $task_srl): bool
{
$driver = self::getDbDriver();
return $driver->cancelScheduledTask($task_srl);
}
/**
@ -165,7 +230,80 @@ class Queue
}
/**
* Process the queue.
* Check the interval syntax.
*
* This method returns true if the interval string is well-formed,
* and false otherwise. However, it does not check that all the numbers
* are in the correct range (e.g. 0-59 for minutes).
*
* @param string $interval
* @return bool
*/
public static function checkIntervalSyntax(string $interval): bool
{
$parts = preg_split('/\s+/', $interval);
if (!$parts || count($parts) !== 5)
{
return false;
}
foreach ($parts as $part)
{
if (!preg_match('!^(?:\\*(?:/\d+)?|\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*)$!', $part))
{
return false;
}
}
return true;
}
/**
* Parse an interval string check it against a timestamp.
*
* This method returns true if the interval covers the given timestamp,
* and false otherwise.
*
* @param string $interval
* @param ?int $time
* @return bool
*/
public static function parseInterval(string $interval, ?int $time): bool
{
$parts = preg_split('/\s+/', $interval);
if (!$parts || count($parts) !== 5)
{
return false;
}
$current_time = explode(' ', date('i G j n w', $time ?? time()));
foreach ($parts as $i => $part)
{
$subparts = explode(',', $part);
foreach ($subparts as $subpart)
{
if ($subpart === '*' || ltrim($subpart, '0') === ltrim($current_time[$i], '0'))
{
continue 2;
}
if ($subpart === '7' && $i === 4 && intval($current_time[$i], 10) === 0)
{
continue 2;
}
if (preg_match('!^\\*/(\d+)?$!', $subpart, $matches) && ($div = intval($matches[1], 10)) && (intval($current_time[$i], 10) % $div === 0))
{
continue 2;
}
if (preg_match('!^(\d+)-(\d+)$!', $subpart, $matches) && intval($current_time[$i], 10) >= intval($matches[1], 10) && intval($current_time[$i], 10) <= intval($matches[2], 10))
{
continue 2;
}
}
return false;
}
return true;
}
/**
* Process queued and scheduled tasks.
*
* This will usually be called by a separate script, run every minute
* through an external scheduler such as crontab or systemd.
@ -173,115 +311,54 @@ class Queue
* If you are on a shared hosting service, you may also call a URL
* using a "web cron" service provider.
*
* @param int $index
* @param int $count
* @param int $timeout
* @return void
*/
public static function process(int $timeout): void
public static function process(int $index, int $count, int $timeout): void
{
// This part will run in a loop until timeout.
$process_start_time = microtime(true);
// Get default driver instance.
$driver_name = config('queue.driver');
$driver = self::getDriver($driver_name);
if (!$driver_name || !$driver)
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
// Process scheduled tasks.
if ($index === 0)
{
$db_driver = self::getDbDriver();
$tasks = $db_driver->getScheduledTasks('once');
foreach ($tasks as $task)
{
self::_executeTask($task);
}
}
if ($index === 1 || $count < 2)
{
$db_driver = self::getDbDriver();
$tasks = $db_driver->getScheduledTasks('interval');
foreach ($tasks as $task)
{
$db_driver->updateLastRunTimestamp($task);
self::_executeTask($task);
}
}
// Process queued tasks.
while (true)
{
// Get a task from the driver.
// Get a task from the driver, with a 1 second delay at maximum.
$loop_start_time = microtime(true);
$task = self::getTask(1);
// Wait 1 second and loop back.
$task = $driver->getNextTask(1);
if ($task)
{
// Find the handler for the task.
$task->handler = trim($task->handler, '\\()');
$handler = null;
try
{
if (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$method_name = $matches[2];
if (class_exists($class_name) && method_exists($class_name, $method_name))
{
$handler = [$class_name, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
elseif (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)(?:\(\))?->(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$initializer_name = $matches[2];
$method_name = $matches[3];
if (class_exists($class_name) && method_exists($class_name, $initializer_name))
{
$obj = $class_name::$initializer_name();
if (method_exists($obj, $method_name))
{
$handler = [$obj, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
elseif (preg_match('/^new (?:\\\\)?([\\\\\\w]+)(?:\(\))?->(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$method_name = $matches[2];
if (class_exists($class_name) && method_exists($class_name, $method_name))
{
$obj = new $class_name();
$handler = [$obj, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
else
{
if (function_exists('\\' . $task->handler))
{
$handler = '\\' . $task->handler;
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
}
catch (\Throwable $th)
{
error_log(vsprintf('RxQueue: task handler %s could not be accessed: %s in %s:%d', [
$task->handler,
get_class($th),
$th->getFile(),
$th->getLine(),
]));
}
// Call the handler.
try
{
if ($handler)
{
call_user_func($handler, $task->args, $task->options);
}
}
catch (\Throwable $th)
{
error_log(vsprintf('RxQueue: task handler %s threw %s in %s:%d', [
$task->handler,
get_class($th),
$th->getFile(),
$th->getLine(),
]));
}
self::_executeTask($task);
}
// If the timeout is imminent, break the loop.
@ -299,4 +376,107 @@ class Queue
}
}
}
/**
* Execute a task.
*
* @param object $task
* @return void
*/
protected static function _executeTask(object $task): void
{
// Find the handler for the task.
$task->handler = trim($task->handler, '\\()');
$handler = null;
try
{
if (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$method_name = $matches[2];
if (class_exists($class_name) && method_exists($class_name, $method_name))
{
$handler = [$class_name, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
elseif (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)(?:\(\))?->(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$initializer_name = $matches[2];
$method_name = $matches[3];
if (class_exists($class_name) && method_exists($class_name, $initializer_name))
{
$obj = $class_name::$initializer_name();
if (method_exists($obj, $method_name))
{
$handler = [$obj, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
elseif (preg_match('/^new (?:\\\\)?([\\\\\\w]+)(?:\(\))?->(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$method_name = $matches[2];
if (class_exists($class_name) && method_exists($class_name, $method_name))
{
$obj = new $class_name();
$handler = [$obj, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
else
{
if (function_exists('\\' . $task->handler))
{
$handler = '\\' . $task->handler;
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
}
catch (\Throwable $th)
{
error_log(vsprintf('RxQueue: task handler %s could not be accessed: %s in %s:%d', [
$task->handler,
get_class($th),
$th->getFile(),
$th->getLine(),
]));
}
// Call the handler.
try
{
if ($handler)
{
call_user_func($handler, $task->args, $task->options);
}
}
catch (\Throwable $th)
{
error_log(vsprintf('RxQueue: task handler %s threw %s in %s:%d', [
$task->handler,
get_class($th),
$th->getFile(),
$th->getLine(),
]));
}
}
}

View file

@ -293,7 +293,7 @@ class Session
\Context::displayErrorPage('SSO Error', 'ERR_INVALID_SSO_REQUEST', 400);
exit;
}
if (!URL::isInternalUrl($sso_request) || !URL::isInternalURL($_SERVER['HTTP_REFERER']))
if (!URL::isInternalUrl($sso_request) || !URL::isInternalURL($_SERVER['HTTP_REFERER'] ?? ''))
{
\Context::displayErrorPage('SSO Error', 'ERR_INVALID_SSO_REQUEST', 400);
exit;

View file

@ -260,10 +260,10 @@ class Storage
* @param string $filename
* @param string|resource $content
* @param string $mode (optional)
* @param int $perms (optional)
* @param ?int $perms (optional)
* @return string|false
*/
public static function write(string $filename, $content, string $mode = 'w', int $perms = null)
public static function write(string $filename, $content, string $mode = 'w', ?int $perms = null)
{
$filename = rtrim($filename, '/\\');
$destination_dir = dirname($filename);

View file

@ -694,12 +694,12 @@ class Template
}
// If any of the variables seems to be an array or object, it's $vars.
if (!is_scalar($media_type))
if (!is_scalar($media_type ?? ''))
{
$vars = $media_type;
$media_type = null;
}
if (!is_scalar($index))
if (!is_scalar($index ?? ''))
{
$vars = $index;
$index = null;

View file

@ -62,10 +62,10 @@ interface QueueInterface
public function addTask(string $handler, ?object $args = null, ?object $options = null): int;
/**
* Get the first task.
* Get the next task from the queue.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object;
public function getNextTask(int $blocking = 0): ?object;
}

View file

@ -4,6 +4,7 @@ namespace Rhymix\Framework\Drivers\Queue;
use Rhymix\Framework\DB as RFDB;
use Rhymix\Framework\Drivers\QueueInterface;
use Rhymix\Framework\Queue;
/**
* The DB queue driver.
@ -99,12 +100,72 @@ class DB implements QueueInterface
}
/**
* Get the first task.
* Add a task to be executed at a specific time.
*
* @param int $time
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public function addTaskAt(int $time, string $handler, ?object $args = null, ?object $options = null): int
{
$oDB = RFDB::getInstance();
$task_srl = getNextSequence();
$stmt = $oDB->prepare(trim(<<<END
INSERT INTO task_schedule
(task_srl, task_type, first_run, handler, args, options, regdate)
VALUES (?, ?, ?, ?, ?, ?, ?)
END));
$result = $stmt->execute([
$task_srl,
'once',
date('Y-m-d H:i:s', $time),
$handler,
serialize($args),
serialize($options),
date('Y-m-d H:i:s'),
]);
return $result ? $task_srl : 0;
}
/**
* Add a task to be executed at an interval.
*
* @param string $interval
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public function addTaskAtInterval(string $interval, string $handler, ?object $args = null, ?object $options = null): int
{
$oDB = RFDB::getInstance();
$task_srl = getNextSequence();
$stmt = $oDB->prepare(trim(<<<END
INSERT INTO task_schedule
(task_srl, task_type, run_interval, handler, args, options, regdate)
VALUES (?, ?, ?, ?, ?, ?, ?)
END));
$result = $stmt->execute([
$task_srl,
'interval',
$interval,
$handler,
serialize($args),
serialize($options),
date('Y-m-d H:i:s'),
]);
return $result ? $task_srl : 0;
}
/**
* Get the next task from the queue.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object
public function getNextTask(int $blocking = 0): ?object
{
$oDB = RFDB::getInstance();
$oDB->beginTransaction();
@ -128,4 +189,123 @@ class DB implements QueueInterface
return null;
}
}
/**
* Get a scheduled task by its task_srl.
*
* @param int $task_srl
* @return ?object
*/
public function getScheduledTask(int $task_srl): ?object
{
$oDB = RFDB::getInstance();
$stmt = $oDB->query('SELECT * FROM task_schedule WHERE task_srl = ?', [$task_srl]);
$task = $stmt->fetchObject();
$stmt->closeCursor();
if ($task)
{
$task->args = unserialize($task->args);
$task->options = unserialize($task->options);
return $task;
}
else
{
return null;
}
}
/**
* Get scheduled tasks.
*
* @param string $type
* @return array
*/
public function getScheduledTasks(string $type): array
{
$oDB = RFDB::getInstance();
$tasks = [];
$task_srls = [];
// Get tasks to be executed once at the current time.
if ($type === 'once')
{
$oDB->beginTransaction();
$timestamp = date('Y-m-d H:i:s');
$stmt = $oDB->query("SELECT * FROM task_schedule WHERE task_type = 'once' AND first_run <= ? ORDER BY first_run FOR UPDATE", [$timestamp]);
while ($task = $stmt->fetchObject())
{
$task->args = unserialize($task->args);
$task->options = unserialize($task->options);
$tasks[] = $task;
$task_srls[] = $task->task_srl;
}
if (count($task_srls))
{
$stmt = $oDB->prepare('DELETE FROM task_schedule WHERE task_srl IN (' . implode(', ', array_fill(0, count($task_srls), '?')) . ')');
$stmt->execute($task_srls);
}
$oDB->commit();
}
// Get tasks to be executed at an interval.
if ($type === 'interval')
{
$stmt = $oDB->query("SELECT task_srl, run_interval FROM task_schedule WHERE task_type = 'interval' ORDER BY task_srl");
while ($task = $stmt->fetchObject())
{
if (Queue::parseInterval($task->run_interval, time()))
{
$task_srls[] = $task->task_srl;
}
}
if (count($task_srls))
{
$stmt = $oDB->prepare('SELECT * FROM task_schedule WHERE task_srl IN (' . implode(', ', array_fill(0, count($task_srls), '?')) . ')');
$stmt->execute($task_srls);
while ($task = $stmt->fetchObject())
{
$task->args = unserialize($task->args);
$task->options = unserialize($task->options);
$tasks[] = $task;
}
}
}
return $tasks;
}
/**
* Update the last executed timestamp of a scheduled task.
*
* @param object $task
* @return void
*/
public function updateLastRunTimestamp(object $task): void
{
$oDB = RFDB::getInstance();
if ($task->first_run)
{
$stmt = $oDB->prepare('UPDATE task_schedule SET last_run = ?, run_count = run_count + 1 WHERE task_srl = ?');
$stmt->execute([date('Y-m-d H:i:s'), $task->task_srl]);
}
else
{
$stmt = $oDB->prepare('UPDATE task_schedule SET first_run = ?, last_run = ?, run_count = run_count + 1 WHERE task_srl = ?');
$stmt->execute([date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), $task->task_srl]);
}
}
/**
* Cancel a scheduled task.
*
* @param int $task_srl
* @return bool
*/
public function cancelScheduledTask(int $task_srl): bool
{
$oDB = RFDB::getInstance();
$stmt = $oDB->query('DELETE FROM task_schedule WHERE task_srl = ?', [$task_srl]);
return ($stmt && $stmt->rowCount()) ? true : false;
}
}

View file

@ -105,12 +105,12 @@ class Dummy implements QueueInterface
}
/**
* Get the first task.
* Get the next task from the queue.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object
public function getNextTask(int $blocking = 0): ?object
{
$result = $this->_dummy_queue;
$this->_dummy_queue = null;

View file

@ -155,12 +155,12 @@ class Redis implements QueueInterface
}
/**
* Get the first task.
* Get the next task from the queue.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object
public function getNextTask(int $blocking = 0): ?object
{
if ($this->_conn)
{

View file

@ -101,8 +101,8 @@ class FilenameFilter
{
$images = 'gif|jpe?g|jfif|png|webp';
$audios = 'mp3|wav|ogg|flac|aac';
$videos = 'mp4|webm|ogv';
$legacy = 'avi|as[fx]|flv|m4[av]|midi?|mkv|moo?v|mpe?g|qt|r[am]m?|wm[av]';
$videos = 'mp4|webm';
$legacy = 'avi|as[fx]|flv|m4[av]|midi?|mkv|moo?v|mpe?g|qt|r[am]m?|wm[av]|ogv';
if ($include_multimedia)
{

View file

@ -135,7 +135,7 @@ class IpFilter
*/
public static function getCloudFlareRealIP()
{
if (!isset($_SERVER['HTTP_CF_CONNECTING_IP']))
if (!isset($_SERVER['HTTP_CF_CONNECTING_IP']) || isset($_SERVER['HTTP_CF_WORKER']))
{
return false;
}

View file

@ -198,6 +198,7 @@ class DBQueryParser extends BaseParser
$orderby->default = ($attribs['default'] ?? null) ?: null;
$orderby->order_var = ($attribs['order'] ?? null) ?: null;
$orderby->order_default = strtoupper($attribs['orderdefault'] ?? '') === 'DESC' ? 'DESC' : 'ASC';
$orderby->ifvar = trim($attribs['if'] ?? '') ?: null;
$query->navigation->orderby[] = $orderby;
}
foreach (['list_count', 'page_count', 'page', 'offset'] as $key)
@ -207,6 +208,7 @@ class DBQueryParser extends BaseParser
$query->navigation->{$key} = new DBQuery\VariableBase;
$query->navigation->{$key}->var = trim($tag['var'] ?? '') ?: null;
$query->navigation->{$key}->default = trim($tag['default'] ?? '') ?: null;
$query->navigation->{$key}->ifvar = trim($tag['if'] ?? '') ?: null;
}
}
}

View file

@ -11,4 +11,5 @@ class OrderBy extends VariableBase
public $default;
public $order_var;
public $order_default = 'ASC';
public $ifvar;
}

View file

@ -245,7 +245,11 @@ class Query extends VariableBase
// Compose the ORDER BY clause.
if ($this->navigation && count($this->navigation->orderby) && !$count_only)
{
$result .= ' ORDER BY ' . $this->_arrangeOrderBy($this->navigation);
$order_by = $this->_arrangeOrderBy($this->navigation);
if ($order_by !== '')
{
$result .= ' ORDER BY ' . $order_by;
}
}
// Compose the LIMIT/OFFSET clause.
@ -607,9 +611,15 @@ class Query extends VariableBase
// Process each column definition.
foreach ($navigation->orderby as $orderby)
{
// Skip
if ($orderby->ifvar && empty($this->_args[$orderby->ifvar]))
{
continue;
}
// Get the name of the column or expression to order by.
$column_name = '';
list($is_expression, $column_name) = $orderby->getValue($this->_args);
list($column_name, $is_expression, $is_default_value) = $orderby->getValue($this->_args);
if (!$column_name)
{
continue;
@ -618,6 +628,10 @@ class Query extends VariableBase
{
$column_name = self::quoteName($column_name);
}
elseif (!$is_default_value)
{
continue;
}
// Get the ordering (ASC or DESC).
if (preg_match('/^(ASC|DESC)$/i', $orderby->order_var ?: '', $matches))
@ -648,23 +662,34 @@ class Query extends VariableBase
*/
protected function _arrangeLimitOffset(Navigation $navigation): string
{
$list_count = 0;
$page = 0;
$offset = 0;
// Get the list count.
list($is_expression, $list_count) = $navigation->list_count->getValue($this->_args);
if (!$navigation->list_count->ifvar || !empty($this->_args[$navigation->list_count->ifvar]))
{
$list_count = $navigation->list_count->getValue($this->_args)[0];
}
if ($list_count <= 0)
{
return '';
}
$page = 0;
$offset = 0;
// Get the offset from the page or offset variable.
if ($navigation->page)
{
list($is_expression, $page) = $navigation->page->getValue($this->_args);
if (!$navigation->page->ifvar || !empty($this->_args[$navigation->page->ifvar]))
{
$page = $navigation->page->getValue($this->_args)[0];
}
}
if ($navigation->offset)
{
list($is_expression, $offset) = $navigation->offset->getValue($this->_args);
if (!$navigation->offset->ifvar || !empty($this->_args[$navigation->offset->ifvar]))
{
$offset = $navigation->offset->getValue($this->_args)[0];
}
}
// If page is available, set the offset and require pagination for this query.

View file

@ -281,11 +281,17 @@ class VariableBase
/**
* Get the current value, falling back to the default value if necessary.
*
* Format of return value: [value, is_expression, is_default_value]
*
* @param array $args
* @return array
*/
public function getValue(array $args): array
{
$value = null;
$is_expression = false;
$is_default_value = false;
if ($this->var && Query::isValidVariable($args[$this->var] ?? null, $this instanceof ColumnWrite))
{
if ($args[$this->var] === '')
@ -293,35 +299,32 @@ class VariableBase
if ($this instanceof ColumnWrite)
{
$value = $args[$this->var];
$is_expression = false;
}
else
{
list($is_expression, $value) = $this->getDefaultValue();
$is_default_value = true;
}
}
else
{
$is_expression = false;
$value = $args[$this->var];
}
}
elseif ($this->default !== null)
{
list($is_expression, $value) = $this->getDefaultValue();
}
else
{
$is_expression = null;
$value = null;
$is_default_value = true;
}
return [$is_expression, $value];
return [$value, $is_expression, $is_default_value];
}
/**
* Get the default value of this variable.
*
* Format of return value: [is_expression, value]
*
* @return array
*/
public function getDefaultValue(): array

View file

@ -692,7 +692,7 @@ function utf8_normalize_spaces($str, bool $multiline = false): string
*/
function utf8_trim($str): string
{
return preg_replace('/^[\s\pZ\pC]+|[\s\pZ\pC]+$/u', '', (string)$str);
return preg_replace('/^[\s\pZ\pC]+|[\s\pZ\pC]+$/u', '', (string)$str) ?? '';
}
/**

View file

@ -181,7 +181,7 @@
else if (filename.match(/\.(mp3|wav|ogg|flac|aac)$/i) && opt.autoinsertTypes.audio) {
temp_code = self.generateHtml($container, result);
}
else if (filename.match(/\.(mp4|webm|ogv)$/i) && opt.autoinsertTypes.video) {
else if (filename.match(/\.(mp4|webm)$/i) && opt.autoinsertTypes.video) {
temp_code = self.generateHtml($container, result);
}
@ -365,7 +365,7 @@
html = '<audio src="' + file.download_url + '" controls' +
' data-file-srl="' + file.file_srl + '" />';
}
else if (filename.match(/\.(mp4|webm|ogv)$/i)) {
else if (filename.match(/\.(mp4|webm)$/i)) {
if (file.original_type === 'image/gif') {
html = '<video src="' + file.download_url + '"'
+ ' autoplay loop muted playsinline data-file-srl="' + file.file_srl + '" />';
@ -519,7 +519,7 @@
if(file.thumbnail_filename) {
file.thumbnail_url = file.thumbnail_filename;
if(/\.(mp4|webm|ogv)$/i.test(file.source_filename)) {
if(/\.(mp4|webm)$/i.test(file.source_filename)) {
result_image.push(template_fileimte_video(file));
} else {
result_image.push(template_fileimte_image(file));

View file

@ -31,6 +31,8 @@ $lang->cmd_vote = 'Upvote';
$lang->cmd_vote_down = 'Downvote';
$lang->cmd_declare = 'Report';
$lang->cmd_cancel_declare = 'Cancel Report';
$lang->cmd_cancel_vote = 'Cancel Upvote';
$lang->cmd_cancel_vote_down = 'Cancel Downvote';
$lang->cmd_vote_config = 'Voting Configuration';
$lang->cmd_declare_config = 'Reporting Configuration';
$lang->cmd_declared_list = 'Reported List';
@ -150,6 +152,7 @@ $lang->colorset = 'Colorset';
$lang->mobile_colorset = 'Mobile Colorset';
$lang->extra_vars = 'Extra Input Fields';
$lang->domain = 'Domain Name';
$lang->deleted_domain = 'Deleted Domain';
$lang->url = 'URL';
$lang->document_url = 'Article URL';
$lang->trackback_url = 'Trackback URL';

View file

@ -152,6 +152,7 @@ $lang->colorset = '컬러셋';
$lang->mobile_colorset = '모바일 컬러셋';
$lang->extra_vars = '확장 변수';
$lang->domain = '도메인';
$lang->deleted_domain = '삭제된 도메인';
$lang->url = 'URL';
$lang->document_url = '게시글 주소';
$lang->trackback_url = '엮인글 주소';

View file

@ -495,7 +495,8 @@ function cut_str($string, $cut_size = 0, $tail = '...'): string
if(isset($GLOBALS['use_mb_strimwidth']) || function_exists('mb_strimwidth'))
{
$GLOBALS['use_mb_strimwidth'] = TRUE;
return mb_strimwidth($string, 0, $cut_size + 4, $tail, 'utf-8');
$string = html_entity_decode($string);
return escape(mb_strimwidth($string, 0, $cut_size + 4, $tail, 'utf-8'));
}
$chars = array(12, 4, 3, 5, 7, 7, 11, 8, 4, 5, 5, 6, 6, 4, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 8, 6, 8, 6, 10, 8, 8, 9, 8, 8, 7, 9, 8, 3, 6, 7, 7, 11, 8, 9, 8, 9, 8, 8, 7, 8, 8, 10, 8, 8, 8, 6, 11, 6, 6, 6, 4, 7, 7, 7, 7, 7, 3, 7, 7, 3, 3, 6, 3, 9, 7, 7, 7, 7, 4, 7, 3, 7, 6, 10, 6, 6, 7, 6, 6, 6, 9);

View file

@ -73,7 +73,7 @@ if (PHP_SAPI === 'cli' && $process_count > 1 && function_exists('pcntl_fork') &&
}
elseif ($pid == 0)
{
Rhymix\Framework\Queue::process($timeout);
Rhymix\Framework\Queue::process($i, $process_count, $timeout);
exit;
}
else
@ -96,7 +96,7 @@ if (PHP_SAPI === 'cli' && $process_count > 1 && function_exists('pcntl_fork') &&
}
else
{
Rhymix\Framework\Queue::process($timeout);
Rhymix\Framework\Queue::process(0, 1, $timeout);
}
// If called over the network, display a simple OK message to indicate success.

View file

@ -24,7 +24,7 @@
<!-- JS -->
@foreach (Context::getJsFile('head', true) as $js_file)
<script src="{!! $js_file['file'] !!}"></script>
<script src="{!! $js_file['file'] !!}"{!! $js_file['attrs'] !!}></script>
@endforeach
<!-- RSS -->

View file

@ -497,7 +497,7 @@
<span class="f_bar">|</span>
<a href="{getUrl('', 'mid', $mid ?? '', 'act', 'dispMemberSignUpForm')}">{$lang->cmd_signup}</a>
</div>
<a href="#" class="btn_ly_popup"><span class="blind">닫기</span></a>
<a href="#" class="btn_ly_popup"><span class="blind">{lang('cmd_close')}</span></a>
</div>
<script>
jQuery(function ($) {

View file

@ -11,3 +11,6 @@ $lang->about_installed_addon = 'Check PC and Mobile to switch on the addon.';
$lang->fixed = 'Fixed';
$lang->about_fixed = 'Check this, and the site administrator cannot change this setting.';
$lang->msg_not_exist_option = 'Configuration for this addon does not exist.';
$lang->run_method = 'Select opt-in or opt-out';
$lang->run_selected_module = 'Run this addon on the selected modules';
$lang->no_run_selected_module = 'Do NOT run this addon on the selected modules';

View file

@ -113,7 +113,7 @@ $lang->cmd_new_domain = 'Add New Domain';
$lang->cmd_edit_domain = 'Edit Domain';
$lang->cmd_is_default_domain = 'Default Domain';
$lang->cmd_multidomain_configuration = 'Multidomain Configuration';
$lang->cmd_unregistered_domain_action = 'Unregistered Domains';
$lang->cmd_unregistered_domain_action = 'Unconfigured Domains';
$lang->cmd_unregistered_domain_redirect_301 = '301 Redirect to Default Domain (Recommended)';
$lang->cmd_unregistered_domain_redirect_302 = '302 Redirect to Default Domain';
$lang->cmd_unregistered_domain_display = 'Display Main Screen as Usual';
@ -329,7 +329,7 @@ $lang->use_rewrite = 'Use Short URLs';
$lang->use_rewrite_0 = 'None';
$lang->use_rewrite_1 = 'XE-compatible URLs only';
$lang->use_rewrite_2 = 'All supported URLs';
$lang->about_use_rewrite = 'Your web server must support mod_rewrite in order for short URLs to work. Apache usually detects the .htaccess file automatically.<br />nginx users should configure rewrite rules according to <a href="https://github.com/rhymix/rhymix-docs/blob/master/ko/introduction/nginx.md> target="_blank">the manual</a>. Outdated versions of nginx rewrite rules only support XE-compatible short URLs.';
$lang->about_use_rewrite = 'Your web server must support mod_rewrite in order for short URLs to work. Apache usually detects the .htaccess file automatically.<br />nginx users should configure rewrite rules according to <a href="https://github.com/rhymix/rhymix-docs/blob/master/ko/introduction/nginx.md" target="_blank">the manual</a>. Outdated versions of nginx rewrite rules only support XE-compatible short URLs.';
$lang->timezone = 'Time Zone';
$lang->use_mobile_view = 'Enable Mobile View';
$lang->about_use_mobile_view = 'Show mobile page when visitors access with mobile devices.';
@ -371,7 +371,7 @@ $lang->site_default_color_scheme_options = array(
'dark' => 'Dark mode only',
);
$lang->use_sso = 'Use <abbr title="Single Sign On">SSO</abbr>?';
$lang->about_use_sso = 'Logging into one domain will automatically log the user into all domains.';
$lang->about_use_sso = 'Logging into one domain will automatically log the user into all domains.<br>Do not rely on this feature, as it will be removed in the future.';
$lang->about_arrange_session = 'Do you want to clean up old session data?';
$lang->cmd_clear_session = 'Session cleanup';
$lang->save = 'Save';

View file

@ -112,7 +112,7 @@ $lang->cmd_new_domain = '새 도메인 추가';
$lang->cmd_edit_domain = '도메인 정보 수정';
$lang->cmd_is_default_domain = '기본 도메인';
$lang->cmd_multidomain_configuration = '멀티도메인 기능 설정';
$lang->cmd_unregistered_domain_action = '등록되지 않은 도메인 처리';
$lang->cmd_unregistered_domain_action = '설정하지 않은 도메인 처리';
$lang->cmd_unregistered_domain_redirect_301 = '기본 도메인으로 301 Redirect (권장)';
$lang->cmd_unregistered_domain_redirect_302 = '기본 도메인으로 302 Redirect';
$lang->cmd_unregistered_domain_display = '메인 화면 표시';
@ -367,7 +367,7 @@ $lang->site_default_color_scheme_options = array(
'dark' => '어두운 색상 고정',
);
$lang->use_sso = '<abbr title="Single Sign On">SSO</abbr> 사용';
$lang->about_use_sso = '한 번만 로그인하면 모든 도메인에 로그인되도록 합니다.';
$lang->about_use_sso = '한 번만 로그인하면 모든 도메인에 로그인되도록 합니다.<br>이 기능은 폐기 예정이니 의존하지 마시기 바랍니다.';
$lang->about_arrange_session = '세션을 정리하시겠습니까?';
$lang->cmd_clear_session = '세션 정리';
$lang->save = '저장';

View file

@ -338,6 +338,9 @@ body>.x,
padding: 10px 15px;
border-bottom: 1px solid #aaa;
background-color: #78909C;
display: flex;
justify-content: space-between;
align-items: center;
}
.x .x_modal-header>h1,
.x .x_modal-header>h2,
@ -347,6 +350,13 @@ body>.x,
margin: 0;
color: #fff;
}
.x .x_modal-header>.close_window {
font-size: 20px;
font-weight: bold;
text-decoration: none;
color: #fff;
opacity: 0.5;
}
.x .x_modal-body {
overflow-y: visible;
max-height: none;

View file

@ -370,7 +370,7 @@ class autoinstallModel extends autoinstall
function getRemoveUrlByPackageSrl($packageSrl)
{
$ftp_info = Context::getFTPInfo();
if(!$ftp_info->ftp_root_path)
if(empty($ftp_info->ftp_root_path))
{
return;
}

View file

@ -6,7 +6,7 @@ $lang->order_newest = 'Newest';
$lang->order_popular = 'Popular';
$lang->order_download = 'Download';
$lang->success_installed = 'Successfully Installed';
$lang->description_ftp_note = 'If the %s is not set, the installation or update will not work. Pleas configure the FTP information.';
$lang->description_ftp_note = 'If the %s is not set, the installation or update will not work. Please configure the FTP information.';
$lang->ftp_setup = 'FTP configuration';
$lang->description_update = 'Click %s before using EasyInstall.';
$lang->status_update = 'update button';
@ -42,6 +42,8 @@ $lang->msg_sftp_not_supported = 'SFTP is not supported.';
$lang->msg_no_permission_to_install = 'Your web server does not have permission to update the installation path. Please check server permissions.';
$lang->msg_direct_install_not_supported = 'Cannot proceed due to write permission missing to the directories listed in the list below.';
$lang->msg_does_not_support_delete = 'Cannot delete this package (no moduleUninstall() in the module class).';
$lang->msg_update_core_title = 'Rhymix Core is updateing.';
$lang->msg_update_core = 'Prior to updating Rhymix Core, please check the compatibility of the installed packages (e.g., modules, widgets, layouts, skins, etc.).';
$lang->installed = 'Installed';
$lang->typename['core'] = 'Core';
$lang->typename['m.layout'] = 'Mobile layout';

View file

@ -9,44 +9,58 @@
class Board extends ModuleObject
{
var $search_option = array('title_content','title','content','comment','user_name','nick_name','user_id','tag'); ///< 검색 옵션
var $order_target = array('list_order', 'update_order', 'regdate', 'voted_count', 'blamed_count', 'readed_count', 'comment_count', 'title', 'nick_name', 'user_name', 'user_id'); // 정렬 옵션
var $skin = "default"; ///< skin name
var $list_count = 20; ///< the number of documents displayed in a page
var $page_count = 10; ///< page number
var $category_list = NULL; ///< category list
/**
* constructor
*
* @return void
* Default search columns
*/
function __construct()
{
parent::__construct();
}
public $search_option = [
'title_content',
'title',
'content',
'comment',
'user_name',
'nick_name',
'user_id',
'tag',
];
/**
* @brief install the module
**/
* Default sort columns
*/
public $order_target = [
'list_order',
'update_order',
'regdate',
'voted_count',
'blamed_count',
'readed_count',
'comment_count',
'title',
'nick_name',
'user_name',
'user_id',
];
/**
* Default values
*/
public $skin = 'default';
public $list_count = 20;
public $page_count = 10;
public $category_list;
/**
* Callback functions for autoinstall
*/
function moduleInstall()
{
}
/**
* @brief chgeck module method
**/
function checkUpdate()
{
}
/**
* @brief update module
**/
function moduleUpdate()
{
@ -54,17 +68,6 @@ class Board extends ModuleObject
function moduleUninstall()
{
$output = executeQueryArray("board.getAllBoard");
if(!$output->data) return new BaseObject();
@set_time_limit(0);
$oModuleController = getController('module');
foreach($output->data as $board)
{
$oModuleController->deleteModule($board->module_srl);
}
return new BaseObject();
}
}

View file

@ -5,21 +5,14 @@
* @class boardController
* @author NAVER (developers@xpressengine.com)
* @brief board module Controller class
**/
*/
class BoardController extends Board
{
/**
* @brief initialization
**/
function init()
{
}
/**
* @brief insert document
**/
function procBoardInsertDocument()
*/
public function procBoardInsertDocument()
{
// check grant
if(!$this->grant->write_document)
@ -152,7 +145,6 @@ class BoardController extends Board
$manual = true;
$anonymous_name = $this->module_info->anonymous_name ?: 'anonymous';
$anonymous_name = $this->createAnonymousName($anonymous_name, $logged_info->member_srl, $obj->document_srl);
$this->module_info->admin_mail = '';
$obj->notify_message = 'N';
$obj->email_address = $obj->homepage = $obj->user_id = '';
@ -295,7 +287,7 @@ class BoardController extends Board
$this->setMessage($msg_code);
}
function procBoardRevertDocument()
public function procBoardRevertDocument()
{
$update_id = Context::get('update_id');
$logged_info = Context::get('logged_info');
@ -338,8 +330,8 @@ class BoardController extends Board
/**
* @brief delete the document
**/
function procBoardDeleteDocument()
*/
public function procBoardDeleteDocument()
{
// get the document_srl
$document_srl = Context::get('document_srl');
@ -416,8 +408,8 @@ class BoardController extends Board
/**
* @brief vote
**/
function procBoardVoteDocument()
*/
public function procBoardVoteDocument()
{
// Check document_srl
$document_srl = intval(Context::get('document_srl'));
@ -434,8 +426,8 @@ class BoardController extends Board
/**
* @brief insert comments
**/
function procBoardInsertComment()
*/
public function procBoardInsertComment()
{
// check grant
if(!$this->grant->write_comment)
@ -513,7 +505,6 @@ class BoardController extends Board
// For anonymous use, remove writer's information and notifying information
if($this->module_info->use_anonymous == 'Y' && (!$this->grant->manager || ($this->module_info->anonymous_except_admin ?? 'N') !== 'Y'))
{
$this->module_info->admin_mail = '';
$obj->notify_message = 'N';
$obj->member_srl = -1*$logged_info->member_srl;
$obj->email_address = $obj->homepage = $obj->user_id = '';
@ -625,8 +616,8 @@ class BoardController extends Board
/**
* @brief delete the comment
**/
function procBoardDeleteComment()
*/
public function procBoardDeleteComment()
{
// get the comment_srl
$comment_srl = Context::get('comment_srl');
@ -741,8 +732,8 @@ class BoardController extends Board
/**
* @brief delete the tracjback
**/
function procBoardDeleteTrackback()
*/
public function procBoardDeleteTrackback()
{
$trackback_srl = Context::get('trackback_srl');
@ -765,8 +756,8 @@ class BoardController extends Board
/**
* @brief check the password for document and comment
**/
function procBoardVerificationPassword()
*/
public function procBoardVerificationPassword()
{
// get the id number of the document and the comment
$password = Context::get('password');
@ -790,7 +781,9 @@ class BoardController extends Board
}
$oComment->setGrantForSession();
} else {
}
else
{
// get the document information
$oDocument = DocumentModel::getDocument($document_srl);
if(!$oDocument->isExists())
@ -810,8 +803,8 @@ class BoardController extends Board
/**
* @brief the trigger for displaying 'view document' link when click the user ID
**/
function triggerMemberMenu($obj)
*/
public function triggerMemberMenu($obj)
{
if(!$mid = Context::get('cur_mid'))
{

View file

@ -3,7 +3,7 @@
class BoardMobile extends BoardView
{
function getBoardCommentPage()
public function getBoardCommentPage()
{
$this->dispBoardCommentPage();
$oTemplate = TemplateHandler::getInstance();

View file

@ -5,22 +5,16 @@
* @class boardModel
* @author NAVER (developers@xpressengine.com)
* @brief board module Model class
**/
*/
class BoardModel extends Board
{
/**
* @brief initialization
**/
function init()
{
}
/**
* @brief get the list configuration
**/
*/
public static function getListConfig($module_srl)
{
// get the list config value, if it is not exitsted then setup the default value
$module_srl = (int)$module_srl;
$list_config = ModuleModel::getModulePartConfig('board', $module_srl);
if(!is_array($list_config) || count($list_config) <= 0)
{
@ -53,7 +47,7 @@ class BoardModel extends Board
/**
* @brief return the default list configration value
**/
*/
public static function getDefaultListConfig($module_srl)
{
$extra_vars = [];
@ -84,7 +78,7 @@ class BoardModel extends Board
/**
* @brief return module name in sitemap
**/
*/
public function triggerModuleListInSitemap(&$obj)
{
array_push($obj, 'board');

View file

@ -5,24 +5,26 @@
* @class boardView
* @author NAVER (developers@xpressengine.com)
* @brief board module View class
**/
*/
class BoardView extends Board
{
var $listConfig;
var $columnList;
/**
* Default values
*/
public $listConfig = [];
public $columnList = [];
/**
* @brief initialization
* board module can be used in either normal mode or admin mode.\n
**/
function init()
*/
public function init()
{
$oSecurity = new Security();
$oSecurity->encodeHTML('document_srl', 'comment_srl', 'vid', 'mid', 'page', 'category', 'search_target', 'search_keyword', 'sort_index', 'order_type', 'trackback_srl');
/**
* setup the module general information
**/
*/
$m = Context::get('m');
$this->list_count = $m ? ($this->module_info->mobile_list_count ?? 20) : ($this->module_info->list_count ?? 20);
$this->search_list_count = $m ? ($this->module_info->mobile_search_list_count ?? 20) : ($this->module_info->search_list_count ?? 20);
@ -68,7 +70,7 @@ class BoardView extends Board
/**
* check the consultation function, if the user is admin then swich off consultation function
* if the user is not logged, then disppear write document/write comment./ view document
**/
*/
if($this->module_info->consultation == 'Y' && !$this->grant->manager && !$this->grant->consultation_read)
{
$this->consultation = TRUE;
@ -87,13 +89,13 @@ class BoardView extends Board
/**
* use context::set to setup extra variables
**/
*/
$extra_keys = DocumentModel::getExtraKeys($this->module_info->module_srl);
Context::set('extra_keys', $extra_keys);
/**
* add extra variables to order(sorting) target
**/
*/
if (is_array($extra_keys))
{
foreach($extra_keys as $val)
@ -103,7 +105,7 @@ class BoardView extends Board
}
/**
* load javascript, JS filters
**/
*/
Context::addJsFilter($this->module_path.'tpl/filter', 'input_password.xml');
Context::loadFile([$this->module_path.'tpl/js/board.js', 'head']);
if (config('url.rewrite') > 1)
@ -127,12 +129,12 @@ class BoardView extends Board
/**
* @brief display board contents
**/
function dispBoardContent()
*/
public function dispBoardContent()
{
/**
* check the access grant (all the grant has been set by the module object)
**/
*/
if(!$this->grant->access || !$this->grant->list)
{
$this->dispBoardMessage($this->user->isMember() ? 'msg_not_permitted' : 'msg_not_logged');
@ -140,13 +142,13 @@ class BoardView extends Board
/**
* display the category list, and then setup the category list on context
**/
*/
$this->dispBoardCategoryList();
/**
* display the search options on the screen
* add extra vaiables to the search options
**/
*/
// use search options on the template (the search options key has been declared, based on the language selected)
foreach($this->search_option as $opt) $search_option[$opt] = lang($opt);
$extra_keys = Context::get('extra_keys');
@ -208,7 +210,7 @@ class BoardView extends Board
/**
* add javascript filters
**/
*/
Context::addJsFilter($this->module_path.'tpl/filter', 'search.xml');
$oSecurity = new Security();
@ -220,10 +222,11 @@ class BoardView extends Board
/**
* @brief display the category list
**/
function dispBoardCategoryList(){
*/
public function dispBoardCategoryList()
{
// check if the use_category option is enabled
if($this->module_info->use_category=='Y')
if ($this->module_info->use_category === 'Y' || !empty($this->include_modules))
{
// check the grant
if(!$this->grant->list)
@ -239,14 +242,31 @@ class BoardView extends Board
}
else
{
$category_list = DocumentModel::getCategoryList($this->module_srl);
if ($this->module_info->use_category === 'Y')
{
$category_list = DocumentModel::getCategoryList($this->module_srl);
}
else
{
$category_list = [];
}
foreach ($this->include_modules as $module_srl)
{
if ($module_srl != $this->module_srl)
{
$category_list += DocumentModel::getCategoryList($module_srl);
if ((ModuleModel::getModuleExtraVars($module_srl)->hide_category ?? 'N') !== 'Y')
{
$category_list += DocumentModel::getCategoryList($module_srl);
}
}
}
if ($category_list)
{
$this->module_info->hide_category = 'N';
$this->module_info->use_category = 'Y';
}
}
Context::set('category_list', $category_list);
@ -257,15 +277,16 @@ class BoardView extends Board
/**
* @brief display the board conent view
**/
function dispBoardContentView(){
*/
public function dispBoardContentView()
{
// get the variable value
$document_srl = Context::get('document_srl');
$page = Context::get('page');
/**
* if the document exists, then get the document information
**/
*/
if($document_srl)
{
$oDocument = DocumentModel::getDocument($document_srl, false, true);
@ -318,16 +339,17 @@ class BoardView extends Board
/**
* if the document is not existed, get an empty document
**/
*/
}
else
{
$oDocument = DocumentModel::getDocument(0);
$oDocument->add('module_srl', $this->module_srl);
}
/**
*check the document view grant
**/
*/
if($oDocument->isExists())
{
if(!$this->grant->view && !$oDocument->isGranted())
@ -373,17 +395,18 @@ class BoardView extends Board
/**
* add javascript filters
**/
*/
Context::addJsFilter($this->module_path.'tpl/filter', 'insert_comment.xml');
}
/**
* @brief display the document file list (can be used by API)
**/
function dispBoardContentFileList(){
*/
public function dispBoardContentFileList()
{
/**
* check the access grant (all the grant has been set by the module object)
**/
*/
if(!$this->grant->access)
{
return $this->dispBoardMessage('msg_not_permitted');
@ -449,8 +472,9 @@ class BoardView extends Board
/**
* @brief display the document comment list (can be used by API)
**/
function dispBoardContentCommentList(){
*/
public function dispBoardContentCommentList()
{
// check document view grant
$this->dispBoardContentView();
@ -475,8 +499,9 @@ class BoardView extends Board
/**
* @brief display notice list (can be used by API)
**/
function dispBoardNoticeList(){
*/
public function dispBoardNoticeList()
{
// check the grant
if(!$this->grant->list || (Context::get('document_srl') && $this->module_info->use_bottom_list === 'N'))
{
@ -502,8 +527,9 @@ class BoardView extends Board
/**
* @brief display board content list
**/
function dispBoardContentList(){
*/
public function dispBoardContentList()
{
// check the grant
if(!$this->grant->list || (Context::get('document_srl') && $this->module_info->use_bottom_list === 'N'))
{
@ -670,7 +696,7 @@ class BoardView extends Board
}
}
function _makeListColumnList()
public function _makeListColumnList()
{
// List of all available columns
$allColumnList = array(
@ -727,8 +753,8 @@ class BoardView extends Board
/**
* @brief display tag list
**/
function dispBoardTagList()
*/
public function dispBoardTagList()
{
// check if there is not grant fot view list, then alert an warning message
if(!$this->grant->list)
@ -774,7 +800,7 @@ class BoardView extends Board
/**
* @brief display category list
*/
function dispBoardCategory()
public function dispBoardCategory()
{
$this->dispBoardCategoryList();
$this->setTemplateFile('category.html');
@ -783,7 +809,7 @@ class BoardView extends Board
/**
* @brief display comment page
*/
function dispBoardCommentPage()
public function dispBoardCommentPage()
{
$document_srl = Context::get('document_srl');
if(!$document_srl)
@ -810,8 +836,8 @@ class BoardView extends Board
/**
* @brief display document write form
**/
function dispBoardWrite()
*/
public function dispBoardWrite()
{
// check grant
if(!$this->grant->write_document)
@ -821,7 +847,7 @@ class BoardView extends Board
/**
* check if the category option is enabled not not
**/
*/
if($this->module_info->use_category=='Y')
{
// get the user group information
@ -969,7 +995,7 @@ class BoardView extends Board
/**
* add JS filters
**/
*/
if($this->grant->manager || $this->module_info->allow_no_category == 'Y')
{
Context::addJsFilter($this->module_path.'tpl/filter', 'insert_admin.xml');
@ -985,7 +1011,7 @@ class BoardView extends Board
$this->setTemplateFile('write_form');
}
function _getStatusNameList()
public function _getStatusNameList()
{
$resultList = array();
if(!empty($this->module_info->use_status))
@ -1006,8 +1032,8 @@ class BoardView extends Board
/**
* @brief display board module deletion form
**/
function dispBoardDelete()
*/
public function dispBoardDelete()
{
// check grant
if(!$this->grant->write_document)
@ -1078,7 +1104,7 @@ class BoardView extends Board
/**
* add JS filters
**/
*/
Context::addJsFilter($this->module_path.'tpl/filter', 'delete_document.xml');
$this->setTemplateFile('delete_form');
@ -1086,8 +1112,8 @@ class BoardView extends Board
/**
* @brief display comment wirte form
**/
function dispBoardWriteComment()
*/
public function dispBoardWriteComment()
{
$document_srl = Context::get('document_srl');
@ -1122,7 +1148,7 @@ class BoardView extends Board
/**
* add JS filter
**/
*/
Context::addJsFilter($this->module_path.'tpl/filter', 'insert_comment.xml');
$this->setTemplateFile('comment_form');
@ -1130,8 +1156,8 @@ class BoardView extends Board
/**
* @brief display comment replies page
**/
function dispBoardReplyComment()
*/
public function dispBoardReplyComment()
{
// check grant
if(!$this->grant->write_comment)
@ -1191,7 +1217,7 @@ class BoardView extends Board
/**
* add JS filters
**/
*/
Context::addJsFilter($this->module_path.'tpl/filter', 'insert_comment.xml');
$this->setTemplateFile('comment_form');
@ -1199,8 +1225,8 @@ class BoardView extends Board
/**
* @brief display the comment modification from
**/
function dispBoardModifyComment()
*/
public function dispBoardModifyComment()
{
// check grant
if(!$this->grant->write_comment)
@ -1275,7 +1301,7 @@ class BoardView extends Board
/**
* add JS fitlers
**/
*/
Context::addJsFilter($this->module_path.'tpl/filter', 'insert_comment.xml');
$this->setTemplateFile('comment_form');
@ -1283,8 +1309,8 @@ class BoardView extends Board
/**
* @brief display the delete comment form
**/
function dispBoardDeleteComment()
*/
public function dispBoardDeleteComment()
{
// check grant
if(!$this->grant->write_comment)
@ -1358,7 +1384,7 @@ class BoardView extends Board
/**
* add JS filters
**/
*/
Context::addJsFilter($this->module_path.'tpl/filter', 'delete_comment.xml');
$this->setTemplateFile('delete_comment_form');
@ -1366,8 +1392,8 @@ class BoardView extends Board
/**
* @brief display the delete trackback form
**/
function dispBoardDeleteTrackback()
*/
public function dispBoardDeleteTrackback()
{
$oTrackbackModel = getModel('trackback');
if(!$oTrackbackModel)
@ -1393,13 +1419,13 @@ class BoardView extends Board
/**
* add JS filters
**/
*/
Context::addJsFilter($this->module_path.'tpl/filter', 'delete_trackback.xml');
$this->setTemplateFile('delete_trackback_form');
}
function dispBoardUpdateLog()
public function dispBoardUpdateLog()
{
if($this->grant->update_view !== true)
{
@ -1427,7 +1453,7 @@ class BoardView extends Board
$this->setTemplateFile('update_list');
}
function dispBoardUpdateLogView()
public function dispBoardUpdateLogView()
{
if($this->grant->update_view !== true)
{
@ -1464,7 +1490,7 @@ class BoardView extends Board
$this->setTemplateFile('update_view');
}
function dispBoardVoteLog()
public function dispBoardVoteLog()
{
iF($this->grant->vote_log_view !== true)
{
@ -1528,7 +1554,7 @@ class BoardView extends Board
/**
* Default 404 Handler.
*/
function dispBoardNotFound()
public function dispBoardNotFound()
{
$this->dispBoardMessage('msg_not_founded', 404);
}
@ -1540,7 +1566,7 @@ class BoardView extends Board
* @param int $http_code
* @return void
*/
function dispBoardMessage($msg_code, $http_code = 403)
public function dispBoardMessage($msg_code, $http_code = 403)
{
//Context::set('message', lang($msg_code));
//$this->setTemplateFile('message');
@ -1561,11 +1587,10 @@ class BoardView extends Board
* @param int $http_code
* @return void
*/
function alertMessage($msg_code, $http_code = 403)
public function alertMessage($msg_code, $http_code = 403)
{
$script = sprintf('<script> jQuery(function(){ alert(%s); } );</script>', json_encode(lang($msg_code)));
Context::addHtmlFooter($script);
$this->setHttpStatusCode($http_code);
}
}

View file

@ -11,44 +11,45 @@ $lang->list_target_item = 'Target Item';
$lang->list_display_item = 'Display Item';
$lang->summary = 'Summary';
$lang->thumbnail = 'Thumbnail';
$lang->last_post = 'Last post';
$lang->last_post = 'Last Post';
$lang->board_management = 'Board Management';
$lang->search_result = 'Search Result';
$lang->consultation = 'Consultation';
$lang->use_consultation = 'Use as Consultation Board';
$lang->secret = 'Secret';
$lang->thisissecret = 'This is a secret article.';
$lang->thisissecret = 'This is a secret post.';
$lang->admin_mail = 'Administrator\'s Mail';
$lang->update_log = 'Update Log';
$lang->last_updater = 'Latest Update by';
$lang->cmd_board_list = 'Boards List';
$lang->cmd_module_config = 'Common Board Setting';
$lang->cmd_module_config = 'Common Board Settings';
$lang->cmd_board_info = 'Board Info';
$lang->cmd_list_setting = 'List Setting';
$lang->cmd_list_setting = 'List Settings';
$lang->cmd_list_items = 'Displayed Items and Order';
$lang->cmd_create_board = 'Create a new board';
$lang->cmd_create_board = 'Create a New Board';
$lang->cmd_manage_selected_board = 'Manage Selected Board';
$lang->about_layout_setup = 'You can manually modify board layout code. Insert or manage the widget code anywhere you want';
$lang->about_board_category = 'You can make board categories. When board category is broken, try rebuilding the cache file manually.';
$lang->about_except_notice = 'Notices will not be displayed in the normal list.<br />Caution: using this option can increase DB load if you have many visitors and lots of articles.';
$lang->about_use_bottom_list = 'Display the list at the bottom when viewing an article.';
$lang->about_customize_bottom_list = 'Calculating the bottom list consumes a lot of server resources.<br />You may be able to reduce DB load by not calculating it exactly when not needed.<br />This should have no effect on SEO.';
$lang->about_layout_setup = 'You can manually modify the board layout code. Insert or manage the widget code anywhere you want.';
$lang->about_board_category = 'You can make board categories. When a board category is broken, try rebuilding the cache file manually.';
$lang->about_except_notice = 'Notices will not be displayed in the normal list.<br />Caution: Using this option may increase DB load if you have many visitors and lots of posts.';
$lang->about_use_bottom_list = 'Display the list at the bottom when viewing a post.';
$lang->about_customize_bottom_list = 'Exact calculations of the lists consume a lot of server resources.<br />You can save server resources by calculating it roughly.<br />This should have no effect on SEO.';
$lang->about_use_anonymous_part1 = 'Hide the author\'s nickname to turn this board into an anonymous board.';
$lang->about_use_anonymous_part2 = 'It is more useful if you also hide the nickname in the skin.<br>Please also turn off document history, or the author\'s information may be revealed by the history.';
$lang->about_use_anonymous_part2 = 'It is more useful if you hide the nickname in the skin, as well.<br>Please concurrently turn off post history, which may unveil the author\'s information.';
$lang->about_anonymous_except_admin = 'The administrator\'s nickname will not be hidden.';
$lang->about_anonymous_name = 'You can customize the anonymous name that is displayed instead of the author\'s nickname.<br><b>$NUM</b> will be replaced with a random number that is unique to each member. (e.g. anon_$NUM → anon_12345678)<br><b>$DAILYNUM</b> will be replaced with a random number that is unique to each member but changes every day.<br><b>$DOCNUM</b> will be replaced with a random number that is unique to each member and changes from document to document.<br><b>$DOCDAILYNUM</b> will be replaced with a random number that is unique to each member and changes every day from document to document.<br>You can append a number to each variable, like <strong>$DAILYNUM:5</strong> to control the number of digits from 1 to 8.<br>To use hexadecimal digits that include some alphabets, use <strong>STR</strong> instead of <strong>NUM</strong>.';
$lang->about_anonymous_name = 'You can customize the anonymous name that will be displayed instead of the author\'s nickname.<br><b>$NUM</b> will be replaced with a random number that is unique to each member. (e.g. anon_$NUM → anon_12345678)<br><b>$DAILYNUM</b> will be replaced with a random number that is unique to each member but changes every day.<br><b>$DOCNUM</b> will be replaced with a random number that is unique to each member and changes from post to post.<br><b>$DOCDAILYNUM</b> will be replaced with a random number that is unique to each member and changes every day from post to post.<br>You can append a number to each variable, like <strong>$DAILYNUM:5</strong> to control the number of digits from 1 to 8.<br>To use hexadecimal digits that include some alphabets, use <strong>STR</strong> instead of <strong>NUM</strong>.';
$lang->about_board = 'This module is for creating and managing boards.';
$lang->about_consultation = 'Members who are not maangers will only see their own articles.<br>When this feature is enabled, non-members cannot read or write any articles on this board.';
$lang->about_secret = 'Users will be able to write secret articles or comments.';
$lang->about_admin_mail = 'A mail will be sent when an article or comment is submitted. Mails can be sent to mutiple mail addresses if connecting addresses with commas(,).';
$lang->about_consultation = 'Members who are not managers will only see their own posts.<br>When this feature is enabled, non-members cannot read or write any posts on this board.';
$lang->about_secret = 'Users will be able to write secret posts or comments.';
$lang->about_admin_mail = 'A mail will be sent when a post or comment is submitted. Mails can be sent to mutiple mail addresses if connecting addresses with commas(,).';
$lang->about_list_config = 'If using list-style skin, you may arrange items to display. However, this feature might not be availble for non-official skins. If you double-click target items and display items, then you can add / remove them';
$lang->about_use_status = 'Please select status that can be selected when you write article.';
$lang->about_use_status = 'Please select status that can be selected when you write a post.';
$lang->about_protect_comment = 'Prevent updating or deleting a comment if it has children.';
$lang->about_update_log = 'Store a log of every version of a document every time it is updated.';
$lang->skip_bottom_list_for_olddoc = 'Do not calculate the bottom list exactly when viewing an old article.';
$lang->about_update_log = 'Store a log of every version of a post every time it is updated.';
$lang->skip_bottom_list_for_olddoc = 'Do not calculate the bottom list exactly when viewing an old post.';
$lang->skip_bottom_list_for_robot = 'Do not calculate the bottom list exactly when a robot is visiting.';
$lang->msg_not_enough_point = 'Your point is not enough to write an article in this board.';
$lang->msg_not_enough_point = 'Your point is not enough to write a post in this board.';
$lang->write_comment = 'Write a comment';
$lang->msg_not_allow_comment = 'This article is not allowed to write comment.';
$lang->msg_not_allow_comment = 'This post is not allowed to write comment.';
$lang->no_board_instance = 'There is no board created.';
$lang->choose_board_instance = 'Please choose one or more board instance.';
$lang->comment_status = 'Comments Allowed';
@ -62,33 +63,41 @@ $lang->protect_comment = 'Protect Comment';
$lang->protect_admin_content = 'Protect Admin Content';
$lang->protect_regdate = 'Update/Delete Time Limit';
$lang->filter_specialchars = 'Block Abuse of Unicode Symbols';
$lang->document_length_limit = 'Limit Document Size';
$lang->comment_length_limit = 'Limit Comment Size';
$lang->document_length_limit = 'Limit Post Length';
$lang->comment_length_limit = 'Limit Comment Length';
$lang->inline_data_url_limit = 'Limit Data URLs';
$lang->about_document_length_limit = 'Restrict documents that are too large. This limit may be triggered by pasting content that contains a lot of unnecessary markup.<br>This setting has no effect on the administrator and board managers.';
$lang->about_document_length_limit = 'Restrict posts that are too large. This limit may be triggered by pasting content that contains a lot of unnecessary markup.<br>This setting has no effect on the administrator and board managers.';
$lang->about_comment_length_limit = 'Restrict comments that are too large.<br>This setting has no effect on the administrator and board managers.';
$lang->about_inline_data_url_limit = 'Restrict data: URLs that can be used to evade file size limits or cause processing issues.<br>This setting also applies to the administrator and board managers.';
$lang->update_order_on_comment = 'Update Document on New Comment';
$lang->about_update_order_on_comment = 'When a new comment is posted, update the update timestamp of the parent document. This is needed for forums.';
$lang->update_order_on_comment = 'Update Post on New Comment';
$lang->about_update_order_on_comment = 'When a new comment is posted, update the update timestamp of the parent post. This is needed for forums.';
$lang->about_filter_specialchars = 'Prevent use of excessive Unicode accents, RLO characters, and other symbols that hinder readability.';
$lang->document_force_to_move = 'Delete to Trash';
$lang->about_document_force_to_move = 'When a document is deleted, move to Trash instead of deleting it permamently.';
$lang->about_protect_regdate = 'Prevent updating or deleting a document or comment after a certain amount of time has passed. (Unit: day)';
$lang->about_protect_content = 'Prevent updating a document if there are comments on it.';
$lang->about_protect_admin_content = 'Prevent updating or deleting a document or comment written by the administrator, even by a user who is permitted to manage the board.';
$lang->msg_protect_delete_content = 'You cannot delete a document with comments on it.';
$lang->msg_protect_update_content = 'You cannot update a document with comments on it.';
$lang->msg_admin_document_no_modify = 'You cannot edit the administrator\'s document.';
$lang->about_protect_regdate = 'Prevent updating or deleting a post or comment after a certain amount of time has passed. (Unit: day)';
$lang->about_protect_content = 'Prevent updating a post if there are comments on it.';
$lang->about_protect_admin_content = 'Prevent updating or deleting a post or comment written by the administrator, even by a user who is permitted to manage the board.';
$lang->msg_protect_delete_content = 'You cannot delete a post with comments on it.';
$lang->msg_protect_update_content = 'You cannot update a post with comments on it.';
$lang->msg_admin_document_no_modify = 'You cannot edit the administrator\'s post.';
$lang->msg_admin_comment_no_modify = 'You cannot edit the administrator\'s comment.';
$lang->msg_board_delete_protect_comment = 'You cannot delete a comment when there are replies.';
$lang->msg_board_update_protect_comment = 'You cannot update a comment when there are replies.';
$lang->msg_protect_regdate_document = 'You cannot update or delete a document after %d days.';
$lang->msg_protect_regdate_document = 'You cannot update or delete a post after %d days.';
$lang->msg_protect_regdate_comment = 'You cannot update or delete a comment after %d days.';
$lang->msg_dont_have_update_log = 'This document has no update log.';
$lang->msg_dont_have_update_log = 'This post has no update log.';
$lang->msg_content_too_long = 'The content is too long.';
$lang->msg_data_url_restricted = 'The content has been restricted due to excessively large data URLs (such as inline images).';
$lang->original_letter = 'Original';
$lang->msg_warning_update_log = '<span class="x_label x_label-important">Warning!</span> This can massively increase the size of your database.';
$lang->reason_update = 'Reason for the update';
$lang->msg_no_update_id = 'The update ID field is mandatory.';
$lang->msg_no_update_log = 'There is no log for updates.';
$lang->cmd_modify_by_update_log = 'Modify this post with this log';
$lang->msg_admin_update_log = 'This post has been edited by the administrator. Please refer to the administrator.';
$lang->msg_update_log_revert = 'Are you sure to revert the post to this version?';
$lang->write_admin = 'Written by the administrator';
$lang->revert_reason_update = 'Revert to this version';
$lang->document_force_to_move = 'Delete to Recycle Bin';
$lang->about_document_force_to_move = 'When a post is deleted, depositing it in Recycle Bin instead of deleting it permamently.';
$lang->comment_delete_message = 'Leave Placeholder for Deleted Comment';
$lang->about_comment_delete_message = 'When a comment is deleted, leave a placeholder saying that it has been deleted.';
$lang->cmd_only_p_comment = 'Only if there are replies';
@ -96,10 +105,13 @@ $lang->cmd_all_comment_message = 'Always';
$lang->cmd_do_not_message = 'Never';
$lang->delete_placeholder = 'Delete Placeholder';
$lang->msg_document_notify_mail = '[%s] The new post : %s';
$lang->cmd_document_vote_user = 'Upvoted by';
$lang->cmd_comment_vote_user = 'Upvoted by';
$lang->msg_not_target = 'You can only see the referrers\' lists for posts and comments.';
$lang->cmd_board_combined_board = 'Combined Board';
$lang->about_board_combined_board = 'You can use this board to view documents from other boards. Press the Ctrl key and click to select multiple boards.<br /><span style="color:red">Warning: permissions for the current board will apply to all affected documents and comments.</span>';
$lang->about_board_combined_board = 'You can use this board to view posts from other boards. Press the Ctrl key and click to select multiple boards.<br /><span style="color:red">Warning: permissions for the current board will apply to all affected posts and comments.</span>';
$lang->cmd_board_include_modules = 'Include Boards';
$lang->cmd_board_include_modules_none = '(None)';
$lang->cmd_board_include_days = 'Include Duration';
$lang->about_board_include_days = 'Only combine recent documents. If this value is set to 0, all documents from selected boards will be combined.<br />Durations shorter than 1 day can be set as fractions of a day, e.g. 0.25 days = 6 hours.';
$lang->about_board_include_days = 'Only combine recent posts. If this value is set to 0, all posts from selected boards will be combined.<br />Durations shorter than 1 day can be set as fractions of a day, e.g. 0.25 days = 6 hours.';
$lang->cmd_board_include_notice = 'Include Notices';

View file

@ -22,8 +22,10 @@
{@ $_extra_vars = $oDocument->getExtraVars(); }
<dl class="xv">
<!--@foreach($_extra_vars as $key => $val)-->
<dt>{$val->name}</dt>
<dd>{$val->getValueHTML()}</dd>
<!--@if($val->hasValue())-->
<dt>{$val->name}</dt>
<dd>{$val->getValueHTML()}</dd>
<!--@end-->
<!--@end-->
</dl>
<!--@end-->

View file

@ -22,7 +22,7 @@
<!-- Extra Output -->
<div class="exOut" cond="$oDocument->isExtraVarsExists() && $oDocument->isAccessible()">
<table border="1" cellspacing="0" summary="Extra Form Output">
<tr loop="$oDocument->getExtraVars() => $key,$val">
<tr loop="$oDocument->getExtraVars() => $key,$val" cond="$val->hasValue()">
<th scope="row">{$val->name}</th>
<td>{$val->getValueHTML()}&nbsp;</td>
</tr>

View file

@ -26,7 +26,7 @@
<!-- Extra Output -->
<div class="exOut" cond="$oDocument->isExtraVarsExists() && $oDocument->isAccessible()">
<table border="1" cellspacing="0" summary="Extra Form Output">
<tr loop="$oDocument->getExtraVars() => $key,$val">
<tr loop="$oDocument->getExtraVars() => $key,$val" cond="$val->hasValue()">
<th scope="row">{$val->name}</th>
<td>{$val->getValueHTML()}&nbsp;</td>
</tr>

View file

@ -19,8 +19,13 @@
<div class="x_controls">
<select name="domain_srl" id="domain_srl">
<option value="-1" selected="selected"|cond="!isset($module_info->domain_srl) || $module_info->domain_srl == -1">{$lang->cmd_any_domain}</option>
{@ $domain_srl_list = []}
<!--@foreach(ModuleModel::getAllDomains(100)->data as $domain)-->
<option value="{$domain->domain_srl}" selected="selected"|cond="$domain->domain_srl == $module_info->domain_srl">{$domain->domain}</option>
{@ $domain_srl_list[] = $domain->domain_srl}
<!--@endif-->
<!--@if(isset($module_info->domain_srl) && $module_info->domain_srl > -1 && !in_array($module_info->domain_srl, $domain_srl_list))-->
<option value="-1" selected="selected">{$lang->deleted_domain}</option>
<!--@endif-->
</select>
<span class="baseurl">{\RX_BASEURL}<!--@if(!config('url.rewrite'))-->index.php?mid=<!--@endif--></span>

View file

@ -30,7 +30,14 @@
</block>
<block cond="$val->module_category_srl">{$module_category[$val->module_category_srl]->title}</block>
</td>
<td class="domain_prefix"><span class="domain">{$val->domain ?? ''}</span>{\RX_BASEURL}</td>
<td class="domain_prefix">
{@
if (isset($val->domain_srl) && $val->domain_srl > -1 && !isset($val->domain)):
$val->domain = lang('deleted_domain');
endif;
}
<span class="domain">{$val->domain ?? ''}</span>{\RX_BASEURL}
</td>
<td>{$val->mid}</td>
<td><a href="{getSiteUrl($val->domain,'','mid',$val->mid)}" target="_blank">{$val->browser_title}</a></td>
<td>{zdate($val->regdate,"Y-m-d")}</td>

View file

@ -839,7 +839,10 @@ class CommentController extends Comment
}
}
$this->sendEmailToAdminAfterInsertComment($obj);
if (config('mail.default_from'))
{
$this->sendEmailToAdminAfterInsertComment($obj);
}
$output->add('comment_srl', $obj->comment_srl);
@ -890,10 +893,13 @@ class CommentController extends Comment
Author: " . $member_info->nick_name . "
<br />Author e-mail: " . $member_info->email_address . "
<br />From : <a href=\"" . $url_comment . "\">" . $url_comment . "</a>
<br />
<br />Comment:
<br />\"" . $obj->content . "\"
<br />
<br />Document:
<br />\"" . $oDocument->getContentText(). "\"
<br />\"" . $oDocument->getTitleText(). "\"
<br />\"" . $oDocument->getContentPlainText(). "\"
<br />
<br />
Approve it: <a href=\"" . $url_approve . "\">" . $url_approve . "</a>
@ -908,10 +914,13 @@ class CommentController extends Comment
Author: " . $member_info->nick_name . "
<br />Author e-mail: " . $member_info->email_address . "
<br />From : <a href=\"" . $url_comment . "\">" . $url_comment . "</a>
<br />
<br />Comment:
<br />\"" . $obj->content . "\"
<br />
<br />Document:
<br />\"" . $oDocument->getContentText(). "\"
<br />\"" . $oDocument->getTitleText(). "\"
<br />\"" . $oDocument->getContentPlainText(). "\"
";
}
@ -919,17 +928,11 @@ class CommentController extends Comment
$oMail = new \Rhymix\Framework\Mail();
$oMail->setSubject($mail_title);
$oMail->setBody($mail_content);
$oMail->setFrom(config('mail.default_from') ?: $member_info->email_address, $member_info->nick_name);
if($member_info->email_address)
{
$oMail->setReplyTo($member_info->email_address);
}
foreach (array_map('trim', explode(',', $module_info->admin_mail)) as $email_address)
{
$oMail->addTo($email_address);
}
$oMail->send();
// send email to all admins - STOP
}
$comment_srl_list = array(0 => $obj->comment_srl);
@ -1711,6 +1714,12 @@ class CommentController extends Comment
$output->add('blamed_count', $trigger_obj->after_point);
}
// Prevent session data getting too large
if (count($_SESSION['voted_comment']) > 200)
{
$_SESSION['voted_comment'] = array_slice($_SESSION['voted_comment'], 100, null, true);
}
return $output;
}
@ -1870,6 +1879,12 @@ class CommentController extends Comment
// leave into the session information
$_SESSION['declared_comment'][$comment_srl] = TRUE;
// Prevent session data getting too large
if (count($_SESSION['declared_comment']) > 200)
{
$_SESSION['declared_comment'] = array_slice($_SESSION['declared_comment'], 100, null, true);
}
$this->setMessage('success_declared');
}

View file

@ -241,6 +241,10 @@ class CommentItem extends BaseObject
{
$_SESSION['accessible'][$this->comment_srl] = $this->get('last_update');
}
if(is_array($_SESSION['accessible']) && count($_SESSION['accessible']) > 200)
{
$_SESSION['accessible'] = array_slice($_SESSION['accessible'], 100, null, true);
}
}
function isEditable()
@ -673,7 +677,7 @@ class CommentItem extends BaseObject
return;
}
$file_list = FileModel::getFiles($this->comment_srl, array(), 'file_srl', TRUE);
$file_list = FileModel::getFiles($this->comment_srl, array(), 'file_srl', true, 'com', true);
return $file_list;
}

View file

@ -737,7 +737,7 @@ class CommentModel extends Comment
// generate a list
$list[$comment_srl] = $source_list[$i];
if($parent_srl)
if($parent_srl && isset($list[$parent_srl]))
{
$list[$parent_srl]->child[] = &$list[$comment_srl];
}

View file

@ -2,14 +2,14 @@
$lang->cmd_comment_do = 'I want to';
$lang->comment_list = 'Comments List';
$lang->cmd_toggle_checked_comment = 'Invert Selection';
$lang->cmd_delete_checked_comment = 'Delete selected item';
$lang->cmd_delete_checked_comment = 'Delete Selected Item';
$lang->trash = 'Recycle Bin';
$lang->cmd_trash = 'Move to Recycle Bin';
$lang->comment_count = 'Number of Comments per Page';
$lang->comment_page_count = 'Number of Pages';
$lang->comment_default_page = 'Default Page';
$lang->comment_default_page_first = 'First page';
$lang->comment_default_page_last = 'Last page';
$lang->comment_default_page_first = 'First Page';
$lang->comment_default_page_last = 'Last Page';
$lang->about_comment_count = 'Set the number of comments to show on each page.';
$lang->about_comment_page_count = 'Set the number of pagination links to show at the bottom.';
$lang->max_thread_depth = 'Maximum Thread Depth';
@ -29,14 +29,14 @@ $lang->search_target_list['last_update'] = 'Last update';
$lang->search_target_list['ipaddress'] = 'IP Address';
$lang->search_target_list['is_secret'] = 'Status';
$lang->no_text_comment = 'No text in this comment.';
$lang->no_text_document = 'No text in this document.';
$lang->no_text_document = 'No text in this post.';
$lang->secret_name_list['Y'] = 'Secret';
$lang->secret_name_list['N'] = 'Public';
$lang->published_name_list[0] = 'Waiting';
$lang->published_name_list[1] = 'Published';
$lang->published_name_list[2] = 'Secret';
$lang->published_name_list[3] = 'Embargo';
$lang->published_name_list[4] = 'Trash';
$lang->published_name_list[4] = 'Recycle Bin';
$lang->published_name_list[5] = 'Blocked';
$lang->published_name_list[6] = 'Blocked';
$lang->published_name_list[7] = 'Deleted';
@ -47,13 +47,15 @@ $lang->published_name_list['N'] = 'Unpublished';
$lang->comment_manager = 'Manage Selected Comment';
$lang->selected_comment = 'Selected Comment';
$lang->cmd_comment_validation = 'Use comment validation';
$lang->about_comment_validation = 'If you want to use comment validation before displaying on your module frontend select USE, otherwise select NOT USE.';
$lang->published = 'Publish status';
$lang->about_comment_validation = 'If you want to use comment validation before displaying on your module frontend, select USE; otherwise, select NOT USE.';
$lang->published = 'Publish Status';
$lang->cmd_publish = 'Publish';
$lang->cmd_unpublish = 'Unpublish';
$lang->select_module = 'Select Module';
$lang->page = 'Page';
$lang->msg_not_selected_comment = 'There are no selected comment.';
$lang->msg_not_selected_comment = 'There are no selected comments.';
$lang->msg_admin_comment_no_delete = 'You cannot delete the superadmin\'s comments.';
$lang->msg_admin_c_comment_no_delete = 'You cannot delete this comment due to the supperadmin reply.';
$lang->improper_comment_declare = 'Report an improper comment';
$lang->declaring_user = 'Reporter';
$lang->improper_comment_declare_reason = 'Reason';
@ -71,4 +73,6 @@ $lang->msg_admin_censored_comment = 'This comment has been hidden by an administ
$lang->msg_deleted_comment = 'This comment has been deleted.';
$lang->msg_admin_deleted_comment = 'This comment has been deleted by an administrator.';
$lang->msg_no_text_comment = 'This comment contains no text.';
$lang->msg_comment_notify_mail = "[%s] A new comment was posted on document: \" %s \"";
$lang->msg_comment_notify_mail = "[%s] A new comment on the post: \" %s \"";
$lang->msg_admin_comment_no_move_to_trash = 'You have no permission to move the superadmin\'s comments to the trash bin.';
$lang->msg_module_srl_not_exists = 'Module serial number not found.';

View file

@ -38,7 +38,7 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
<tr>
<th scope="col">{$lang->comment}</th>
<th scope="col" class="nowr">{$lang->writer}</th>
<th scope="col" class="nowr rx_detail_marks">{$lang->cmd_vote}(+/-)</th>
<th scope="col" class="nowr rx_detail_marks">{$lang->cmd_vote} / {$lang->cmd_vote_down}</th>
<th scope="col" class="nowr">{$lang->date}</th>
<th scope="col" class="nowr rx_detail_marks">{$lang->ipaddress}</th>
<th scope="col" class="nowr rx_detail_marks">{$lang->status}</th>

View file

@ -290,8 +290,9 @@ class CommunicationView extends communication
$option->editor_skin = $this->config->editor_skin;
$option->sel_editor_colorset = $this->config->editor_colorset;
$option->editor_focus = Context::get('source_message') ? 'Y' : 'N';
if(Context::get('m'))
if(Context::get('m') || stripos($_SERVER['HTTP_USER_AGENT'] ?? '', 'mobile') !== false)
{
$option->editor_toolbar = 'simple';
$option->editor_toolbar_hide = 'Y';
}
if ($option->allow_fileupload)

View file

@ -50,3 +50,7 @@ $lang->enable_communication_friend = 'Friend Enable';
$lang->enable_communication_message = 'Message Enable';
$lang->enable_attachment = 'Allow Attachment';
$lang->attachment_size_limit = 'Attachment Size Limit';
$lang->warning = 'Caution!';
$lang->msg_allow_message_friend = 'You can only receive direct messages from your friends.';
$lang->msg_allow_meesage_Block = 'Your direct message box is turned off.';
$lang->msg_allow_message_please = 'You should allow direct messages from everyone in order to receive the replies for this message.';

View file

@ -49,6 +49,7 @@
<action name="procDocumentAdminInsertExtraVar" type="controller" permission="manager:config:*" check_var="module_srl" ruleset="insertExtraVar" />
<action name="procDocumentAdminDeleteExtraVar" type="controller" permission="manager:config:*" check_var="module_srl" />
<action name="procDocumentAdminMoveExtraVar" type="controller" permission="manager:config:*" check_var="module_srl" />
<action name="procDocumentAdminRecalculateCategoryCounts" type="controller" />
</actions>
<eventHandlers>
<eventHandler after="module.deleteModule" class="controller" method="triggerDeleteModuleDocuments" />

View file

@ -731,6 +731,38 @@ class DocumentAdminController extends Document
$output = $oDocumentController->deleteDocument($oDocument->get('document_srl'), true, true, $oDocument);
return $output;
}
/**
* Recalculate category document counts
*/
public function procDocumentAdminRecalculateCategoryCounts()
{
// Get the document count for each category.
$output = executeQueryArray('document.getCategoryDocumentCounts', []);
$module_srl_list = [];
// Update the document count of each category.
$oDB = DB::getInstance();
$oDB->beginTransaction();
$oDB->query('UPDATE document_categories SET document_count = 0');
$stmt = $oDB->prepare('UPDATE document_categories SET document_count = ? WHERE category_srl = ?');
foreach ($output->data ?: [] as $row)
{
$module_srl_list[$row->module_srl] = true;
$stmt->execute([$row->count, $row->category_srl]);
}
$oDB->commit();
// Refresh category cache files for affected modules.
$oDocumentController = DocumentController::getInstance();
foreach ($module_srl_list as $module_srl => $unused)
{
$oDocumentController->makeCategoryFile($module_srl);
}
$this->setError(-1);
$this->setMessage('success_updated');
}
}
/* End of file document.admin.controller.php */
/* Location: ./modules/document/document.admin.controller.php */

View file

@ -699,7 +699,7 @@ class DocumentController extends Document
unset($obj->user_id);
}
$obj->uploaded_count = FileModel::getFilesCount($obj->document_srl);
$obj->uploaded_count = FileModel::getFilesCount($obj->document_srl, 'doc');
// Call a trigger (before)
$output = ModuleHandler::triggerCall('document.insertDocument', 'before', $obj);
@ -996,7 +996,7 @@ class DocumentController extends Document
if(($obj->title_color ?? 'N') === 'N') $obj->title_color = 'N';
if(($obj->notify_message ?? 'N') !== 'Y') $obj->notify_message = 'N';
if(($obj->allow_trackback ?? 'N') !== 'Y') $obj->allow_trackback = 'N';
$obj->uploaded_count = FileModel::getFilesCount($obj->document_srl);
$obj->uploaded_count = FileModel::getFilesCount($obj->document_srl, 'doc');
// Call a trigger (before)
$output = ModuleHandler::triggerCall('document.updateDocument', 'before', $obj);
@ -1274,7 +1274,7 @@ class DocumentController extends Document
{
// Check if deletion is allowed
$ev_output = $extra_item->validate(null);
if (!$ev_output->toBool())
if ($ev_output && !$ev_output->toBool())
{
$oDB->rollback();
return $ev_output;
@ -1724,6 +1724,12 @@ class DocumentController extends Document
$_SESSION['readed_document'][$document_srl] = true;
}
// Prevent session data getting too large
if (is_array($_SESSION['readed_document']) && count($_SESSION['readed_document']) > 1000)
{
$_SESSION['readed_document'] = array_slice($_SESSION['readed_document'], 500, null, true);
}
return TRUE;
}
@ -2111,6 +2117,12 @@ class DocumentController extends Document
$output->add('blamed_count', $trigger_obj->after_point);
}
// Prevent session data getting too large
if (count($_SESSION['voted_document']) > 200)
{
$_SESSION['voted_document'] = array_slice($_SESSION['voted_document'], 100, null, true);
}
return $output;
}
@ -2270,6 +2282,12 @@ class DocumentController extends Document
// Leave in the session information
$_SESSION['declared_document'][$document_srl] = true;
// Prevent session data getting too large
if (count($_SESSION['declared_document']) > 200)
{
$_SESSION['declared_document'] = array_slice($_SESSION['declared_document'], 100, null, true);
}
$this->setMessage('success_declared');
}
@ -3757,7 +3775,7 @@ Content;
$args = new stdClass;
$args->document_srl = $document_srl;
$args->uploaded_count = FileModel::getFilesCount($document_srl);
$args->uploaded_count = FileModel::getFilesCount($document_srl, 'doc');
executeQuery('document.updateUploadedCount', $args);
}
}

View file

@ -285,6 +285,10 @@ class DocumentItem extends BaseObject
{
$_SESSION['accessible'][$this->document_srl] = $this->get('last_update');
}
if(is_array($_SESSION['accessible']) && count($_SESSION['accessible']) > 200)
{
$_SESSION['accessible'] = array_slice($_SESSION['accessible'], 100, null, true);
}
}
function allowComment()
@ -1456,7 +1460,7 @@ class DocumentItem extends BaseObject
if(!isset($this->uploadedFiles[$sortIndex]))
{
$this->uploadedFiles[$sortIndex] = FileModel::getFiles($this->document_srl, array(), $sortIndex, true);
$this->uploadedFiles[$sortIndex] = FileModel::getFiles($this->document_srl, array(), $sortIndex, true, 'doc', true);
}
return $this->uploadedFiles[$sortIndex];

View file

@ -1438,7 +1438,7 @@ class DocumentModel extends Document
}
// add subcategories
if(isset($args->category_srl) && $args->category_srl)
if(isset($args->category_srl) && $args->category_srl && !is_array($args->category_srl))
{
$category_list = self::getCategoryList($args->module_srl);
if(isset($category_list[$args->category_srl]))

View file

@ -105,6 +105,7 @@ $lang->select_category = 'Select a category.';
$lang->category_description = 'Category Description';
$lang->no_title_document = 'No title in this document.';
$lang->send_default_message = 'Send the default message';
$lang->msg_admin_document_no_move_to_trash = 'You are not allowed to move the superadmin\'s article to the trash bin.';
$lang->default_message_format = '%1$s the document by manager.';
$lang->default_message_verbs['move'] = 'moved';
$lang->default_message_verbs['copy'] = 'copied';
@ -134,3 +135,6 @@ $lang->allow_declare_from_same_ip = 'Allow reporting from same IP';
$lang->allow_declare_cancel = 'Allow report cancellation';
$lang->cmd_search_division = 'Search division';
$lang->about_search_division = 'Divide search results into sections in order to reduce time and server load. Setting this value to 0 will disable divisions entirely.<br />Disabling divisions or using large values may cause serious problems such as timeouts or server overload. Values between 5000 and 10000 are recommended.';
$lang->cmd_recalculate_category_counts = 'Recalculate per-category document counts';
$lang->about_recalculate_category_counts = 'If per-category document counts are displayed incorrectly after batch move or deletion, click the button above to refresh the statistics.<br />This may take a long time if you have a large number of posts.';
$lang->prevent_redeclare = 'Prevent the same user from reporting this post again.';

View file

@ -126,4 +126,6 @@ $lang->allow_declare_from_same_ip = '동일 IP 신고 허용';
$lang->allow_declare_cancel = '신고 취소 허용';
$lang->cmd_search_division = '검색 결과 분할';
$lang->about_search_division = '검색 소요시간과 서버 부하를 줄이기 위해 일정 갯수만큼 끊어서 검색합니다. 0으로 설정할 경우 분할하지 않습니다.<br />분할하지 않거나 지나치게 많은 게시물을 한 번에 검색하려고 하면 타임아웃, 서버 다운 등 심각한 부작용이 발생할 수 있으므로 5000~10000 내외를 권장합니다.';
$lang->cmd_recalculate_category_counts = '분류별 문서 수 다시 계산';
$lang->about_recalculate_category_counts = '게시물 일괄 이동 등으로 각 분류별 문서 수가 정확하게 표시되지 않는 경우, 위의 버튼을 클릭하여 갱신할 수 있습니다.<br />문서가 많은 사이트라면 오랜 시간이 걸릴 수 있으니 주의하시기 바랍니다.';
$lang->prevent_redeclare = '같은 사용자가 이 게시물을 다시 신고하는 것을 방지합니다.';

View file

@ -0,0 +1,17 @@
<query id="getCategoryDocumentCounts" action="select">
<tables>
<table name="documents" />
</tables>
<columns>
<column name="module_srl" />
<column name="category_srl" />
<column name="count(*)" alias="count" />
</columns>
<conditions>
<condition operation="notequal" column="category_srl" default="0" />
</conditions>
<groups>
<group column="module_srl" />
<group column="category_srl" />
</groups>
</query>

View file

@ -6,6 +6,7 @@
<input type="hidden" name="type" value="" />
<div class="x_modal-header">
<h1>{$lang->cmd_manage_document}</h1>
<a class="close_window" href="javascript:window.close()">&times;</a>
</div>
<div class="x_modal-body x_form-horizontal" style="max-height:none">
<!--@if(count($document_list)==0)-->
@ -75,4 +76,4 @@ jQuery(function($){
doGetCategoryFromModule({$module_srl});
<!--@end-->
});
</script>
</script>

View file

@ -34,7 +34,7 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
<th scope="col" class="title">{$lang->title}</th>
<th scope="col" class="nowr">{$lang->writer}</th>
<th scope="col" class="nowr">{$lang->readed_count}</th>
<th scope="col" class="nowr">{$lang->cmd_vote}(+/-)</th>
<th scope="col" class="nowr">{$lang->cmd_vote} / {$lang->cmd_vote_down}</th>
<th scope="col" class="nowr"><a href="{getUrl('sort_index', 'declared_count')}">{$lang->declared_count} <block cond="$sort_index == 'declared_count'"></block></a></th>
<th scope="col" class="nowr"><a href="{getUrl('sort_index', 'regdate')}">{$lang->original_date} <block cond="$sort_index == 'regdate'"></block></a></th>
<th scope="col" class="nowr"><a href="{getUrl('sort_index', 'declared_latest')}">{$lang->latest_declared_date} <block cond="$sort_index == 'declared_latest'"></block></a></th>

View file

@ -48,6 +48,13 @@
<p class="x_help-block">{$lang->about_search_division}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->cmd_recalculate_category_counts}</label>
<div class="x_controls">
<p><button type="button" class="recalculate_category_counts">{$lang->cmd_recalculate_category_counts}</button></p>
<p class="x_help-block">{$lang->about_recalculate_category_counts}</p>
</div>
</div>
<div class="btnArea x_clearfix">
<span class="x_pull-right" style="margin-left:10px;"><input class="btn" type="button" value="{$lang->cmd_delete_all_thumbnail}" onclick="doDeleteAllThumbnail()"/></span>
<span class="x_pull-right"><input class="x_btn x_btn-primary" type="submit" value="{$lang->cmd_save}" /></span>

View file

@ -1,4 +1,5 @@
<load target="js/document_admin.js" />
<load target="js/document_extra_keys.js" />
<!--%import("filter/insert_extra_var.xml")-->
<!--%import("filter/delete_extra_var.xml")-->
@ -49,7 +50,7 @@
<label class="x_inline" for="is_required_n"><input type="radio" name="is_required" id="is_required_n" value="N" checked="checked"|cond="$selected_var->is_required != 'Y'" /> {$lang->not}</label>
</div>
</div>
<div class="x_control-group">
<div class="x_control-group" data-visible-types="select,radio,checkbox">
<label class="x_control-label">{$lang->extra_vars_is_strict}</label>
<div class="x_controls">
<label class="x_inline" for="is_strict_y"><input type="radio" name="is_strict" id="is_strict_y" value="Y" checked="checked"|cond="$selected_var->is_strict == 'Y'" /> {$lang->yes}</label>
@ -57,14 +58,14 @@
<p class="x_help-block">{$lang->about_extra_vars_is_strict}</p>
</div>
</div>
<div class="x_control-group">
<div class="x_control-group" data-invisible-types="file">
<label class="x_control-label" for="default">{$lang->default_value}</label>
<div class="x_controls">
<input type="text" name="default" id="default" value="{$selected_var->default}" />
<p class="x_help-block">{$lang->about_extra_vars_default_value}</p>
</div>
</div>
<div class="x_control-group">
<div class="x_control-group" data-visible-types="select,radio,checkbox">
<label class="x_control-label" for="default">{$lang->extra_vars_options}</label>
<div class="x_controls">
<textarea type="text" name="options" id="options">{$selected_var ? implode("\n", $selected_var->getOptions()) : ''}</textarea>

View file

@ -21,7 +21,7 @@ function doManageDocument(type) {
/* 선택된 글의 삭제 또는 이동 후 */
function completeManageDocument(ret_obj) {
if(opener) {
if(opener) {
opener.window.location.href = opener.window.current_url.setQuery('document_srl', '');
}
alert(ret_obj['message']);
@ -106,7 +106,7 @@ function getDocumentList() {
var documentListTable = jQuery('#documentListTable');
var cartList = [];
documentListTable.find(':checkbox[name=cart]').each(function(){
if(this.checked) cartList.push(this.value);
if(this.checked) cartList.push(this.value);
});
var params = new Array();
@ -174,12 +174,16 @@ function completeGetModuleList(ret_obj, response_tags)
}
jQuery(document).ready(function($){
$('#module_list').bind('change', function(e){
$('#module_list').on('change', function(e){
makeMidList($('#module_list').val());
});
$('#mid_list').bind('change', function(e){
$('#mid_list').on('change', function(e){
doGetCategoryFromModule($('#mid_list').val());
});
$('.recalculate_category_counts').on('click', function(e) {
exec_json('document.procDocumentAdminRecalculateCategoryCounts', { });
e.preventDefault();
});
});
function makeMidList(moduleName)

View file

@ -156,7 +156,7 @@ function clearValue(){
// clear value
$w.find('input[type="text"], textarea').val('');
$w.find('.x_inline.checked').removeClass('checked');
$w.find('input[type="checkbox"]').removeAttr('checked');
$w.find('input[type="checkbox"]').prop('checked', false);
$w.find('.lang_code').trigger('reload-multilingual');
$w.find('.color-indicator').trigger('keyup');
$w.find('.rx-spectrum').trigger('keyup');
@ -204,14 +204,14 @@ function modifyNode(node,e){
$w.find('textarea[name="category_description"]').val(data.category_info.description).trigger('reload-multilingual');
for(var i in data.category_info.group_srls){
var group_srl = data.category_info.group_srls[i];
$w.find('input[name="group_srls[]"][value="' + group_srl + '"]').attr('checked', 'checked')
$w.find('input[name="group_srls[]"][value="' + group_srl + '"]').prop('checked', true)
.parent().addClass('checked');
}
if(data.category_info.expand == 'Y'){
$w.find('input[name="expand"]').attr('checked', 'checked');
$w.find('input[name="expand"]').prop('checked', true).parent().addClass('checked');
}
if(data.category_info.is_default == 'Y'){
$w.find('input[name="is_default"]').attr('checked', 'checked');
$w.find('input[name="is_default"]').prop('checked', true).parent().addClass('checked');
}
});

View file

@ -0,0 +1,25 @@
(function($) {
$(function() {
$('select#type').on('change', function() {
const selected_type = $(this).val();
$(this).parents('form').find('.x_control-group').each(function() {
const visible_types = $(this).data('visibleTypes');
if (visible_types) {
if (visible_types.split(',').indexOf(selected_type) >= 0) {
$(this).show();
} else {
$(this).hide();
}
}
const invisible_types = $(this).data('invisibleTypes');
if (invisible_types) {
if (invisible_types.split(',').indexOf(selected_type) >= 0) {
$(this).hide();
} else {
$(this).show();
}
}
});
}).triggerHandler('change');
});
})(jQuery);

View file

@ -111,6 +111,10 @@ class EditorAdminView extends Editor
// Get a group list to set a group
$group_list = MemberModel::getGroups(0);
foreach ($group_list ?: [] as $group)
{
$group->title = Context::replaceUserLang($group->title, true);
}
Context::set('group_list', $group_list);
// Get a mid list

View file

@ -165,6 +165,10 @@ class EditorView extends Editor
// Get a group list
$group_list = MemberModel::getGroups();
Context::set('group_list', $group_list);
foreach ($group_list ?: [] as $group)
{
$group->title = Context::replaceUserLang($group->title, true);
}
//Security
$security = new Security();

View file

@ -1,4 +1,5 @@
<?php
$lang->editor_now = 'Editor Preview with the Current Settings';
$lang->editor_component = 'Editor Component';
$lang->main_editor = 'Main Editor';
$lang->comment_editor = 'Comment Editor';
@ -58,8 +59,8 @@ $lang->component_extra_vars = 'Option Variable';
$lang->component_grant = 'Permission Setting';
$lang->content_font = 'Content Font';
$lang->content_font_size = 'Content Font Size';
$lang->about_component = 'About component';
$lang->about_component_mid = 'Editor components can select targets.(All targets will be selected when nothing is selected.)';
$lang->about_component = 'About Component';
$lang->about_component_mid = 'Editor components can select targets. (All targets will be selected when nothing is selected.)';
$lang->msg_component_is_not_founded = 'Cannot find editor component %s.';
$lang->msg_component_is_disabled = 'Editor component %s is disabled.';
$lang->msg_component_is_inserted = 'Selected component is already inserted.';
@ -78,7 +79,7 @@ $lang->enable_extra_component_grant = 'Permission to use extra components';
$lang->enable_html_grant = 'Permission to edit HTML';
$lang->enable_autosave = 'Enable Auto-Save';
$lang->editor_auto_dark_mode = 'Automatic dark mode';
$lang->allow_html = 'allow HTML';
$lang->allow_html = 'Allow HTML';
$lang->height_resizable = 'Height Resizable';
$lang->editor_height = 'Height of Editor';
$lang->about_default_editor_settings = 'Follow the default settings from the Editor module.';

View file

@ -121,6 +121,24 @@ class Value
$this->value = $value;
}
/**
* Check if this extra variable has a value.
*
* @return bool
*/
public function hasValue(): bool
{
$value = self::_getTypeValue($this->type, $this->value);
if ($value === null || $value === '' || (is_array($value) && !count($value)))
{
return false;
}
else
{
return true;
}
}
/**
* Get the raw value.
*
@ -233,9 +251,9 @@ class Value
*
* @param mixed $value
* @param mixed $old_value
* @return ?BaseObject
* @return BaseObject
*/
public function validate($value, $old_value = null): ?BaseObject
public function validate($value, $old_value = null): BaseObject
{
// Take legacy encoding into consideration.
if (is_array($value))
@ -295,7 +313,7 @@ class Value
}
}
return null;
return new BaseObject;
}
/**

View file

@ -9,6 +9,7 @@
@required(toBool($definition->is_required))
@disabled(toBool($definition->is_disabled))
@readonly(toBool($definition->is_readonly))>
<option value="">@lang('cmd_select')</option>
@foreach ($definition->getOptions() as $v)
<option value="{{ $v }}" @selected($has_value ? in_array($v, $value) : ($v === $default_value))>{{ $v }}</option>
@endforeach

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

@ -72,16 +72,22 @@ class FileAdminController extends File
$config->allowed_filetypes = Context::get('allowed_filetypes');
// Image settings
$config->image_autoconv['bmp2jpg'] = Context::get('image_autoconv_bmp2jpg') === 'Y' ? true : false;
$config->image_autoconv['png2jpg'] = Context::get('image_autoconv_png2jpg') === 'Y' ? true : false;
$config->image_autoconv['webp2jpg'] = Context::get('image_autoconv_webp2jpg') === 'Y' ? true : false;
$config->image_autoconv['avif2jpg'] = Context::get('image_autoconv_avif2jpg') === 'Y' ? true : false;
$config->image_autoconv['heic2jpg'] = Context::get('image_autoconv_heic2jpg') === 'Y' ? true : false;
$config->image_autoconv['gif2mp4'] = Context::get('image_autoconv_gif2mp4') === 'Y' ? true : false;
$config->image_autoconv = [];
foreach (Context::get('image_autoconv') ?: [] as $source_type => $target_type)
{
if (in_array($target_type, ['Y', 'N']))
{
$config->image_autoconv[$source_type] = tobool($target_type);
}
elseif (in_array($target_type, ['', 'jpg', 'png', 'webp']))
{
$config->image_autoconv[$source_type] = $target_type;
}
}
$config->max_image_width = intval(Context::get('max_image_width')) ?: '';
$config->max_image_height = intval(Context::get('max_image_height')) ?: '';
$config->max_image_size_action = Context::get('max_image_size_action') ?: '';
$config->max_image_size_same_format = Context::get('max_image_size_same_format') === 'Y' ? 'Y' : 'N';
$config->max_image_size_same_format = strval(Context::get('max_image_size_same_format'));
$config->max_image_size_admin = Context::get('max_image_size_admin') === 'Y' ? 'Y' : 'N';
$config->image_quality_adjustment = max(50, min(100, intval(Context::get('image_quality_adjustment'))));
$config->image_autorotate = Context::get('image_autorotate') === 'Y' ? true : false;
@ -296,6 +302,167 @@ 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(lang('file.msg_image_conversion_failed'));
}
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;
}
if (isset($config->save_changelog) && $config->save_changelog === 'Y')
{
$changelog1 = executeQuery('file.insertFileChangelog', [
'change_type' => 'D',
'file_srl' => $file->file_srl,
'file_size' => $file->file_size,
'uploaded_filename' => $file->uploaded_filename,
]);
if (!$changelog1->toBool())
{
return $changelog1;
}
$changelog2 = executeQuery('file.insertFileChangelog', [
'change_type' => 'I',
'file_srl' => $file->file_srl,
'file_size' => $filesize,
'uploaded_filename' => $relative_path,
]);
if (!$changelog2->toBool())
{
return $changelog2;
}
}
}
else
{
throw new Rhymix\Framework\Exception(lang('file.msg_image_conversion_failed'));
}
$this->setMessage(sprintf(lang('file.msg_image_converted'), FileHandler::filesize($file->file_size), FileHandler::filesize($filesize)));
$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

@ -29,7 +29,10 @@ class FileController extends File
$file_info = Context::get('Filedata');
// An error appears if not a normally uploaded file
if(!$file_info || !is_uploaded_file($file_info['tmp_name'])) exit();
if (!$file_info || !is_uploaded_file($file_info['tmp_name']))
{
throw new Rhymix\Framework\Exceptions\InvalidRequest();
}
// Validate editor_sequence and module_srl.
$editor_sequence = Context::get('editor_sequence');
@ -156,12 +159,17 @@ class FileController extends File
// Create the response
Context::setResponseMethod('JSON');
$this->add('file_srl', $output->get('file_srl'));
$this->add('file_size', $output->get('file_size'));
$this->add('direct_download', $output->get('direct_download'));
$this->add('source_filename', $output->get('source_filename'));
$this->add('upload_target_srl', $output->get('upload_target_srl'));
$this->add('source_filename', $output->get('source_filename'));
$this->add('thumbnail_filename', $output->get('thumbnail_filename'));
$this->add('file_size', $output->get('file_size'));
$this->add('disp_file_size', FileHandler::filesize($output->get('file_size')));
$this->add('mime_type', $output->get('mime_type'));
$this->add('original_type', $output->get('original_type'));
$this->add('width', $output->get('width'));
$this->add('height', $output->get('height'));
$this->add('duration', $output->get('duration'));
$this->add('direct_download', $output->get('direct_download'));
if ($output->get('direct_download') === 'Y')
{
$this->add('download_url', FileModel::getDirectFileUrl($output->get('uploaded_filename')));
@ -772,6 +780,10 @@ class FileController extends File
{
$_SESSION['upload_info'] = array();
}
if(count($_SESSION['upload_info']) > 200)
{
$_SESSION['upload_info'] = array_slice($_SESSION['upload_info'], 100, null, true);
}
if(!isset($_SESSION['upload_info'][$editor_sequence]))
{
$_SESSION['upload_info'][$editor_sequence] = new stdClass();
@ -821,6 +833,21 @@ class FileController extends File
return $output;
}
/**
* Update upload target type
*
* @param int|array $file_srl
* @param string $upload_target_type
* @return BaseObject
*/
public function updateTargetType($file_srl, $upload_target_type)
{
$args = new stdClass;
$args->file_srl = $file_srl;
$args->upload_target_type = $upload_target_type;
return executeQuery('file.updateFileTargetType', $args);
}
/**
* Add an attachement
*
@ -1097,13 +1124,17 @@ class FileController extends File
$output->add('file_srl', $args->file_srl);
$output->add('file_size', $args->file_size);
$output->add('sid', $args->sid);
$output->add('upload_target_srl', $upload_target_srl);
$output->add('direct_download', $args->direct_download);
$output->add('source_filename', $args->source_filename);
$output->add('upload_target_srl', $upload_target_srl);
$output->add('uploaded_filename', $args->uploaded_filename);
$output->add('thumbnail_filename', $args->thumbnail_filename);
$output->add('mime_type', $args->mime_type);
$output->add('original_type', $args->original_type);
$output->add('width', $args->width);
$output->add('height', $args->height);
$output->add('duration', $args->duration);
$output->add('sid', $args->sid);
return $output;
}
@ -1163,23 +1194,11 @@ class FileController extends File
{
$adjusted['type'] = 'mp4';
}
elseif ($config->image_autoconv['png2jpg'] && $image_info['type'] === 'png' && function_exists('imagepng'))
elseif (!empty($config->image_autoconv[$image_info['type']]))
{
$adjusted['type'] = 'jpg';
$adjusted['type'] = $config->image_autoconv[$image_info['type']];
}
elseif ($config->image_autoconv['webp2jpg'] && $image_info['type'] === 'webp' && function_exists('imagewebp'))
{
$adjusted['type'] = 'jpg';
}
elseif ($config->image_autoconv['bmp2jpg'] && $image_info['type'] === 'bmp' && function_exists('imagebmp'))
{
$adjusted['type'] = 'jpg';
}
elseif ($config->image_autoconv['avif2jpg'] && $image_info['type'] === 'avif')
{
$adjusted['type'] = 'jpg';
}
elseif ($config->image_autoconv['heic2jpg'] && $image_info['type'] === 'heic')
elseif (!empty($config->image_autoconv[$image_info['type'] . '2jpg']))
{
$adjusted['type'] = 'jpg';
}
@ -1246,7 +1265,7 @@ class FileController extends File
$adjusted['height'] = (int)$resize_height;
if (!$is_animated && $adjusted['type'] === $image_info['type'] && $config->max_image_size_same_format !== 'Y')
{
$adjusted['type'] = 'jpg';
$adjusted['type'] = $config->max_image_size_same_format ?: 'jpg';
}
}
}

View file

@ -82,7 +82,7 @@ class FileModel extends File
// Set file list
$filter_type = $_SESSION['upload_info'][$editor_sequence]->upload_target_type ?? null;
$files = self::getFiles($upload_target_srl, [], 'file_srl', false, $filter_type);
$files = self::getFiles($upload_target_srl, [], 'file_srl', false, $filter_type, true);
foreach ($files as $file_info)
{
$obj = new stdClass;
@ -93,6 +93,9 @@ class FileModel extends File
$obj->disp_file_size = FileHandler::filesize($file_info->file_size);
$obj->mime_type = $file_info->mime_type;
$obj->original_type = $file_info->original_type;
$obj->width = $file_info->width;
$obj->height = $file_info->height;
$obj->duration = $file_info->duration;
$obj->direct_download = $file_info->direct_download;
$obj->cover_image = ($file_info->cover_image === 'Y') ? true : false;
if($obj->direct_download === 'Y' && self::isDownloadable($file_info))
@ -291,15 +294,20 @@ class FileModel extends File
*
* @param int $upload_target_srl The sequence to get a number of files
* @param ?string $upload_target_type
* @param bool $include_null_target_type
* @return int Returns a number of files
*/
public static function getFilesCount($upload_target_srl, $upload_target_type = null)
public static function getFilesCount($upload_target_srl, $upload_target_type = null, $include_null_target_type = false)
{
$args = new stdClass();
$args->upload_target_srl = $upload_target_srl;
if ($upload_target_type)
{
$args->upload_target_type = $upload_target_type;
if ($include_null_target_type)
{
$args->include_null_target_type = true;
}
}
$output = executeQuery('file.getFilesCount', $args);
return (int)$output->data->count;
@ -435,9 +443,12 @@ class FileModel extends File
* @param int $upload_target_srl The sequence of target to get file list
* @param array $columnList The list of columns to get from DB
* @param string $sortIndex The column that used as sort index
* @param bool $valid_files_only
* @param ?string $upload_target_type
* @param bool $include_null_target_type
* @return array Returns array of object that contains file information. If no result returns null.
*/
public static function getFiles($upload_target_srl, $columnList = array(), $sortIndex = 'file_srl', $valid_files_only = false, $upload_target_type = null)
public static function getFiles($upload_target_srl, $columnList = array(), $sortIndex = 'file_srl', $valid_files_only = false, $upload_target_type = null, $include_null_target_type = false)
{
$args = new stdClass();
$args->upload_target_srl = $upload_target_srl;
@ -449,6 +460,10 @@ class FileModel extends File
if ($upload_target_type)
{
$args->upload_target_type = $upload_target_type;
if ($include_null_target_type)
{
$args->include_null_target_type = true;
}
}
$output = executeQueryArray('file.getFiles', $args, $columnList);
@ -458,12 +473,23 @@ class FileModel extends File
}
$fileList = array();
$nullList = array();
foreach ($output->data as $file)
{
$file->source_filename = escape($file->source_filename, false);
$file->download_url = self::getDownloadUrl($file->file_srl, $file->sid, 0, $file->source_filename);
$fileList[] = $file;
if ($file->upload_target_type === null)
{
$nullList[] = $file->file_srl;
}
}
if (count($nullList) && $upload_target_type)
{
FileController::getInstance()->updateTargetType($nullList, $upload_target_type);
}
return $fileList;
}

View file

@ -54,6 +54,7 @@ $lang->msg_not_permitted_download = 'You do not have a permission to download.';
$lang->msg_file_cart_is_null = 'Please select a file(s) to delete.';
$lang->msg_checked_file_is_deleted = '%d attachment(s) was(were) deleted.';
$lang->msg_exceeds_limit_size = 'This file exceeds the attachment limit.';
$lang->msg_not_allowed_filetype = 'This file format is not allowed to be uploaded.';
$lang->msg_exceeds_max_image_size = 'This image is too large. Images must be no larger than %dx%dpx.';
$lang->msg_exceeds_max_image_width = 'This image is too large. The maximum permitted width is %dpx.';
$lang->msg_exceeds_max_image_height = 'This image is too large. The maximum permitted height is %dpx.';
@ -90,11 +91,6 @@ $lang->use_video_default_file_config = 'Use Default Settings Of Video File';
$lang->about_use_video_default_file_config = 'Follow the video settings of image file from the File module.';
$lang->image_autoconv = 'Convert Type';
$lang->about_image_autoconv = 'Automatically convert uploaded images. This may help you handle image formats that are not widely supported or waste disk space.';
$lang->image_autoconv_bmp2jpg = 'BMP → JPG';
$lang->image_autoconv_png2jpg = 'PNG → JPG';
$lang->image_autoconv_webp2jpg = 'WebP → JPG';
$lang->image_autoconv_avif2jpg = 'AVIF → JPG';
$lang->image_autoconv_heic2jpg = 'HEIC → JPG';
$lang->max_image_size = 'Limit Image Size';
$lang->about_max_image_size = 'Limit the dimensions of uploaded images. Note that this is only indirectly related to file size.';
$lang->max_image_size_action_nothing = 'If exceeded, do nothing';
@ -102,7 +98,8 @@ $lang->max_image_size_action_block = 'If exceeded, block upload';
$lang->max_image_size_action_resize = 'If exceeded, resize automatically';
$lang->max_image_size_action_cut = 'If exceeded, cut automatically';
$lang->max_image_size_same_format_Y = 'Maintain file format';
$lang->max_image_size_same_format_N = 'Convert to JPG';
$lang->max_image_size_same_format_to_jpg = 'Convert to JPG';
$lang->max_image_size_same_format_to_webp = 'Convert to WebP';
$lang->max_image_size_admin = 'Also apply to administrator';
$lang->image_quality_adjustment = 'Image Quality';
$lang->about_image_quality_adjustment = 'adjust the quality of images that will is converted by other settings.<br />If set to more than 75% (Standard), the file size may be larger than the original.';
@ -114,6 +111,8 @@ $lang->image_autoconv_gif2mp4 = 'Convert GIF to MP4';
$lang->about_image_autoconv_gif2mp4 = 'convert animated GIF images into MP4 videos to save storage and bandwidth.<br />This requires ffmpeg settings below. Videos may not play properly in older browsers.';
$lang->max_video_size = 'Limit Video Size';
$lang->about_max_video_size = 'Limit the dimensions of uploaded videos. Note that this is only indirectly related to file size.';
$lang->max_video_duration = 'Limit Video Duration';
$lang->about_max_video_duration = 'Restrict the maximum length of videos. This is independent of the limit of filesize (e.g., MB, GB) or two-dimensional size for videos.';
$lang->video_autoconv_any2mp4 = 'Convert to MP4';
$lang->about_video_autoconv_any2mp4 = 'Convert all other types of videos to MP4 format that can be played on the web.<br />Supported original formats vary by ffmpeg version and system environment, but usually include AVI and MOV.';
$lang->video_always_reencode = 'Always Reencode';
@ -132,3 +131,8 @@ $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';
$lang->msg_image_converted = 'The image has been converted. (%s → %s)';
$lang->msg_image_conversion_failed = 'The image conversion has failed.';

View file

@ -38,7 +38,7 @@ $lang->about_allow_indexing_format = '구글 등 검색엔진에 의한 색인
$lang->about_allow_outlink = '다른 사이트에서 파일 다운로드 링크에 직접 접근하는 것을 허용합니다.<br />본문에 바로 삽입할 수 있는 이미지 파일은 라이믹스에서 접근을 통제할 수 없으며, 이를 차단하려면 웹서버 설정이 필요합니다.';
$lang->about_allow_outlink_format = '파일 외부 링크 설정에 상관없이 허용하는 파일 확장자입니다.<br />여러 개 입력시 쉼표(,)을 이용해서 구분해 주세요. 예) doc, zip, pdf';
$lang->about_allow_outlink_site = '파일 외부 링크 설정에 상관없이 허용하는 사이트 주소입니다.<br />여러 개 입력시 줄을 바꿔서 구분해 주세요. 예) https://www.rhymix.org/';
$lang->about_allow_multimedia_direct_download = '오디오, 동영상 등의 멀티미디어 파일 링크를 본문에 삽입할 때 직접 접근이 가능한 링크를 사용합니다.<br>다운로드시 PHP를 거치지 않게 되므로 서버 부하가 줄어들지만, 권한이 없는 사람에게 링크가 노출되지 않도록 주의해야 합니다.<br>&quot;아니오&quot;를 선택하면 본문에 삽입된 동영상을 재생할 때마다 첨부파일의 다운로드 횟수가 증가됩니다.';
$lang->about_allow_multimedia_direct_download = '오디오, 동영상 등의 멀티미디어 파일 링크를 본문에 삽입할 때 직접 접근이 가능한 링크를 사용합니다.<br>다운로드시 PHP를 거치지 않게 되므로 서버 부하가 줄어들지만, 권한이 없는 사람에게 링크가 노출되지 않도록 주의해야 합니다.<br>&quot;아니오&quot;를 선택하면 본문에 삽입된 멀티미디어 파일이 자동 재생되지 않을 수 있습니다.';
$lang->about_allowed_filesize = '각 파일의 용량을 제한할 수 있습니다.<br />관리자에게는 이 게시판의 제한과 <a href="%s" target="_blank">파일 모듈</a>의 제한 중 높은 쪽이 적용됩니다.';
$lang->about_allowed_attach_size = '하나의 문서에 첨부할 수 있는 최대 용량을 제한할 수 있습니다.<br />관리자에게는 이 게시판의 제한과 <a href="%s" target="_blank">파일 모듈</a>의 제한 중 높은 쪽이 적용됩니다.';
$lang->about_allowed_filesize_global = '관리자를 포함하여 사이트 전체에 적용되는 파일 용량 제한입니다.';
@ -91,11 +91,6 @@ $lang->use_video_default_file_config = '동영상 파일 기본 설정 사용';
$lang->about_use_video_default_file_config = '파일 모듈의 동영상 파일 기본 설정을 따릅니다.';
$lang->image_autoconv = '이미지 자동 변환';
$lang->about_image_autoconv = '업로드된 이미지의 타입을 변환합니다. 다양한 환경에서 호환되지 않거나, 용량을 낭비하는 이미지를 처리할 수 있습니다.';
$lang->image_autoconv_bmp2jpg = 'BMP → JPG';
$lang->image_autoconv_png2jpg = 'PNG → JPG';
$lang->image_autoconv_webp2jpg = 'WebP → JPG';
$lang->image_autoconv_avif2jpg = 'AVIF → JPG';
$lang->image_autoconv_heic2jpg = 'HEIC → JPG';
$lang->max_image_size = '이미지 크기 제한';
$lang->about_max_image_size = '업로드된 이미지의 크기를 제한하거나 조정합니다. 파일 용량과는 직접적인 관계가 없으니 참고하세요.';
$lang->max_image_size_action_nothing = '초과시 아무 것도 하지 않음';
@ -103,7 +98,8 @@ $lang->max_image_size_action_block = '초과시 업로드 금지';
$lang->max_image_size_action_resize = '초과시 자동 크기 조정';
$lang->max_image_size_action_cut = '초과시 자르기';
$lang->max_image_size_same_format_Y = '동일한 포맷 유지';
$lang->max_image_size_same_format_N = 'JPG로 변환';
$lang->max_image_size_same_format_to_jpg = 'JPG로 변환';
$lang->max_image_size_same_format_to_webp = 'WebP로 변환';
$lang->max_image_size_admin = '관리자에게도 적용';
$lang->image_quality_adjustment = '이미지 화질';
$lang->about_image_quality_adjustment = '다른 설정에 의해 이미지가 변환될 경우 화질을 조정합니다.<br />75% (표준) 이상으로 설정시 오히려 원본보다 용량이 늘어날 수 있습니다.';
@ -135,3 +131,8 @@ $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 = '이미지 포맷';
$lang->msg_image_converted = '이미지가 변환되었습니다. (%s → %s)';
$lang->msg_image_conversion_failed = '이미지 변환에 실패했습니다.';

View file

@ -4,7 +4,10 @@
</tables>
<conditions>
<condition operation="equal" column="upload_target_srl" var="upload_target_srl" filter="number" notnull="notnull" />
<condition operation="equal" column="upload_target_type" var="upload_target_type" pipe="and" />
<group pipe="and">
<condition operation="equal" column="upload_target_type" var="upload_target_type" />
<condition operation="null" column="upload_target_type" if="include_null_target_type" pipe="or" />
</group>
<condition operation="equal" column="isvalid" var="isvalid" pipe="and" />
</conditions>
<navigation>

View file

@ -7,7 +7,10 @@
</columns>
<conditions>
<condition operation="equal" column="upload_target_srl" var="upload_target_srl" filter="number" />
<condition operation="equal" column="upload_target_type" var="upload_target_type" pipe="and" />
<group pipe="and">
<condition operation="equal" column="upload_target_type" var="upload_target_type" />
<condition operation="null" column="upload_target_type" if="include_null_target_type" pipe="or" />
</group>
<condition operation="like_prefix" column="regdate" var="regDate" pipe="and" />
</conditions>
</query>

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

@ -6,6 +6,6 @@
<column name="upload_target_type" var="upload_target_type" />
</columns>
<conditions>
<condition operation="equal" column="file_srl" var="file_srl" filter="number" notnull="notnull" />
<condition operation="in" column="file_srl" var="file_srl" filter="number" notnull="notnull" />
</conditions>
</query>
</query>

View file

@ -2,4 +2,23 @@
margin-top: -10px;
border-bottom: 1px solid #ccc;
background: #f5f5f5;
}
}
.image_autoconv_types {
margin-bottom: 5px;
}
.image_autoconv_types:last-of-type {
margin-bottom: 10px;
}
.image_autoconv_types .x_inline {
display: inline-block;
min-width: 40px;
}
.image_autoconv_types select {
min-width: 0 !important;
margin-left: 16px;
}
.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

@ -31,6 +31,9 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
</block>
</a>
</th>
<th scope="col" class="nowr">
{$lang->image_size}
</th>
<th scope="col" class="nowr">
<a href="{getUrl('', 'module', 'admin', 'act', 'dispFileAdminList', 'sort_index', 'download_count', 'order_type', ($order_type == 'desc' && $sort_index == 'download_count') ? 'asc' : 'desc', 'isvalid', $isvalid ?? null)}">
{$lang->cmd_download}
@ -50,6 +53,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 +70,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="10" scope="col">
<!--@if(!$val->upload_target_type)-->
<!--@if($val->isvalid=='Y')-->
{$lang->is_valid}
@ -99,6 +103,14 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
<!--@end-->
<a href="{$val->download_url}">{escape($val->source_filename, false)}</a></td>
<td class="nowr">{FileHandler::filesize($val->file_size)}</td>
<td class="nowr">
<!--@if($val->width && $val->height)-->
{$val->width}x{$val->height}
<!--@endif-->
<!--@if($val->duration)-->
<br>({$val->duration}{$lang->unit_sec})
<!--@endif-->
</td>
<td class="nowr">{$val->download_count}</td>
<td class="nowr">
<!--@if($val->upload_target_type == 'doc' && $document_list[$document_srl])-->
@ -112,6 +124,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);

View file

@ -35,26 +35,20 @@
<div class="x_control-group">
<label class="x_control-label">{$lang->image_autoconv}</label>
<div class="x_controls">
<label for="image_autoconv_bmp2jpg">
<input type="checkbox" name="image_autoconv_bmp2jpg" id="image_autoconv_bmp2jpg" value="Y" checked="checked"|cond="$config->image_autoconv['bmp2jpg']" disabled="disabled"|cond="!function_exists('imagebmp')" />
{$lang->image_autoconv_bmp2jpg}
</label>
<label for="image_autoconv_png2jpg">
<input type="checkbox" name="image_autoconv_png2jpg" id="image_autoconv_png2jpg" value="Y" checked="checked"|cond="$config->image_autoconv['png2jpg']" disabled="disabled"|cond="!function_exists('imagepng')" />
{$lang->image_autoconv_png2jpg}
</label>
<label for="image_autoconv_webp2jpg">
<input type="checkbox" name="image_autoconv_webp2jpg" id="image_autoconv_webp2jpg" value="Y" checked="checked"|cond="$config->image_autoconv['webp2jpg']" disabled="disabled"|cond="!function_exists('imagewebp')" />
{$lang->image_autoconv_webp2jpg}
</label>
<label for="image_autoconv_avif2jpg">
<input type="checkbox" name="image_autoconv_avif2jpg" id="image_autoconv_avif2jpg" value="Y" checked="checked"|cond="$config->image_autoconv['avif2jpg']" disabled="disabled"|cond="!$is_magick" />
{$lang->image_autoconv_avif2jpg}
</label>
<label for="image_autoconv_heic2jpg">
<input type="checkbox" name="image_autoconv_heic2jpg" id="image_autoconv_heic2jpg" value="Y" checked="checked"|cond="$config->image_autoconv['heic2jpg']" disabled="disabled"|cond="!$is_magick" />
{$lang->image_autoconv_heic2jpg}
</label>
{@ $source_types = ['bmp', 'jpg', 'png', 'webp', 'avif', 'heic']}
<!--@foreach($source_types as $source_type)-->
<div class="image_autoconv_types">
<label for="image_autoconv_{$source_type}" class="x_inline">
{strtoupper($source_type)}
</label>
<select name="image_autoconv[{$source_type}]" id="image_autoconv_{$source_type}" disabled="disabled"|cond="!$is_magick && in_array($source_type, ['avif', 'heic'])">
<option value=""></option>
<option value="jpg" selected="selected"|cond="($config->image_autoconv[$source_type] ?? '') === 'jpg' || !empty($config->image_autoconv[$source_type . '2jpg'])">JPG</option>
<option value="png" selected="selected"|cond="($config->image_autoconv[$source_type] ?? '') === 'png'">PNG</option>
<option value="webp" selected="selected"|cond="($config->image_autoconv[$source_type] ?? '') === 'webp'">WebP</option>
</select>
</div>
<!--@endforeach-->
<p class="x_help-block">
{$lang->about_image_autoconv}<br />
{$lang->msg_need_magick}
@ -76,12 +70,16 @@
</p>
<p class="x_help-block">
<label class="x_inline" for="max_image_size_same_format_Y">
<input type="radio" name="max_image_size_same_format" id="max_image_size_same_format_Y" value="Y" checked="checked"|cond="$config->max_image_size_same_format === 'Y'" />
<input type="radio" name="max_image_size_same_format" id="max_image_size_same_format_Y" value="Y" checked="checked"|cond="!isset($config->max_image_size_same_format) || $config->max_image_size_same_format === 'Y'" />
{$lang->max_image_size_same_format_Y}
</label>
<label class="x_inline" for="max_image_size_same_format_N">
<input type="radio" name="max_image_size_same_format" id="max_image_size_same_format_N" value="N" checked="checked"|cond="$config->max_image_size_same_format !== 'Y'" />
{$lang->max_image_size_same_format_N}
<label class="x_inline" for="max_image_size_same_format_to_jpg">
<input type="radio" name="max_image_size_same_format" id="max_image_size_same_format_to_jpg" value="jpg" checked="checked"|cond="$config->max_image_size_same_format === 'jpg' || $config->max_image_size_same_format === 'N'" />
{$lang->max_image_size_same_format_to_jpg}
</label>
<label class="x_inline" for="max_image_size_same_format_to_webp">
<input type="radio" name="max_image_size_same_format" id="max_image_size_same_format_to_webp" value="webp" checked="checked"|cond="$config->max_image_size_same_format === 'webp'" />
{$lang->max_image_size_same_format_to_webp}
</label>
<label for="max_image_size_admin">
<input type="checkbox" name="max_image_size_admin" id="max_image_size_admin" value="Y" checked="checked"|cond="$config->max_image_size_admin === 'Y'" />
@ -135,11 +133,11 @@
<label class="x_control-label">{$lang->image_autoconv_gif2mp4}</label>
<div class="x_controls">
<label for="image_autoconv_gif2mp4_Y" class="x_inline">
<input type="radio" name="image_autoconv_gif2mp4" id="image_autoconv_gif2mp4_Y" value="Y" checked="checked"|cond="$config->image_autoconv['gif2mp4'] === true" disabled="disabled"|cond="!$is_ffmpeg" />
<input type="radio" name="image_autoconv[gif2mp4]" id="image_autoconv_gif2mp4_Y" value="Y" checked="checked"|cond="$config->image_autoconv['gif2mp4'] === true" disabled="disabled"|cond="!$is_ffmpeg" />
{$lang->cmd_yes}
</label>
<label for="image_autoconv_gif2mp4_N" class="x_inline">
<input type="radio" name="image_autoconv_gif2mp4" id="image_autoconv_gif2mp4_N" value="N" checked="checked"|cond="$config->image_autoconv['gif2mp4'] !== true" disabled="disabled"|cond="!$is_ffmpeg" />
<input type="radio" name="image_autoconv[gif2mp4]" id="image_autoconv_gif2mp4_N" value="N" checked="checked"|cond="$config->image_autoconv['gif2mp4'] !== true" disabled="disabled"|cond="!$is_ffmpeg" />
{$lang->cmd_no}
</label>
<p class="x_help-block">{$lang->about_image_autoconv_gif2mp4}</p>

View file

@ -337,7 +337,7 @@ class installController extends install
}
// Check curl
if(function_exists('curl_init'))
if(function_exists('curl_init') && function_exists('curl_exec'))
{
$checklist['curl'] = true;
}
@ -347,7 +347,7 @@ class installController extends install
}
// Check GD
if(function_exists('imagecreatefromgif'))
if(function_exists('imagecreatefromjpeg') && function_exists('imagecreatefrompng'))
{
$checklist['gd'] = true;
}
@ -367,7 +367,7 @@ class installController extends install
}
// Check json
if(function_exists('json_encode'))
if(function_exists('json_encode') && function_exists('json_decode'))
{
$checklist['json'] = true;
}

View file

@ -1,11 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="0.2">
<title xml:lang="ko">한국 우편번호</title>
<title xml:lang="en">Korean Postal Code</title>
<description xml:lang="ko">공개 API를 이용해 우편번호 검색 서비스를 이용합니다.</description>
<description xml:lang="en">Module to utilize public postal code searching APIs in South Korea.</description>
<version>RX_VERSION</version>
<date>RX_CORE</date>
<author email_address="developers@xpressengine.com" link="http://www.xpressengine.com/">
<name xml:lang="ko">NAVER</name>
<name xml:lang="en">NAVER</name>
</author>
</module>

20
modules/krzip/lang/en.php Normal file
View file

@ -0,0 +1,20 @@
<?php
$lang->krzip = 'Korean Postal Code';
$lang->cmd_krzip_api_type = 'Select API Provider';
$lang->cmd_krzip_daumapi = 'Daum-Kakao API';
$lang->cmd_krzip_epostapi = 'Korea Post API';
$lang->cmd_krzip_postcodify = 'Postcodify';
$lang->cmd_krzip_regkey = 'Registration Key';
$lang->cmd_krzip_postcode = 'Postal Code';
$lang->cmd_krzip_address = 'Mailing Address';
$lang->cmd_krzip_detail_address = 'Detailed Address';
$lang->about_krzip = 'Postal code searching using public APIs for postal codes in South Korea.';
$lang->about_krzip_api_handler = 'Determining which API will be used to find the postal codes.';
$lang->about_krzip_epostapi_regkey = 'Entering the registration key from the Korea Post for using their API.';
$lang->msg_krzip_road_address_expectation = 'Road-Name Based Address: %s';
$lang->msg_krzip_jibun_address_expectation = 'Land-Lot Based Address: %s';
$lang->msg_krzip_no_query = 'Please provide a search keyword.';
$lang->msg_krzip_is_maintenance = 'Under maintenance.';
$lang->msg_krzip_wrong_regkey = 'This is an invalid registration key.';
$lang->msg_krzip_no_result = 'There is no found address.';
$lang->msg_krzip_riddling_wrong = 'Unknown error in the postal code module.';

Some files were not shown because too many files have changed in this diff Show more