mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-04 17:21:39 +09:00
Merge branch 'rhymix:develop' into develop
This commit is contained in:
commit
88b5281094
111 changed files with 2542 additions and 405 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -2,7 +2,7 @@ name: PHP Lint & Codeception
|
|||
on: [ push, pull_request ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
|
||||
- [이슈 및 PR 작성 방법](https://rhymix.org/manual/contrib/github)
|
||||
- [코딩 규칙](https://rhymix.org/manual/contrib/coding-standards)
|
||||
- [GPL: 개발자, 디자이너, 사용자 등의 권리와 의무](https://rhymix.org/manual/contrib/license)
|
||||
|
|
|
|||
|
|
@ -773,9 +773,10 @@ class Context
|
|||
}
|
||||
if (count($vars))
|
||||
{
|
||||
$title = trim(trim(preg_replace_callback('/\\$(\w+)/', function($matches) use($vars) {
|
||||
$title = trim(preg_replace_callback('/\\$(\w+)/', function($matches) use($vars) {
|
||||
return isset($vars[strtolower($matches[1])]) ? $vars[strtolower($matches[1])] : $matches[0];
|
||||
}, $title), ' -'));
|
||||
}, $title));
|
||||
$title = preg_replace('/(-\s+)+-/', '-', trim($title, ' -'));
|
||||
}
|
||||
self::$_instance->browser_title = $title;
|
||||
}
|
||||
|
|
@ -1368,7 +1369,9 @@ class Context
|
|||
$file['tmp_name'] = $val['tmp_name'][$i];
|
||||
$file['error'] = $val['error'][$i];
|
||||
$file['size'] = $val['size'][$i];
|
||||
$files[] = $file;
|
||||
|
||||
$subkey = (is_int($i) || ctype_digit($i)) ? intval($i) : preg_replace('/[^a-z0-9:+=_-]/i', '', (string)$i);
|
||||
$files[$subkey] = $file;
|
||||
}
|
||||
if(count($files))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -573,19 +573,32 @@ class HTMLDisplayHandler
|
|||
|
||||
foreach ($document_files as $file)
|
||||
{
|
||||
if ($file->isvalid !== 'Y' || !preg_match('/\.(?:bmp|gif|jpe?g|png|webp)$/i', $file->uploaded_filename))
|
||||
if ($file->isvalid !== 'Y' || !preg_match('/\.(?:bmp|gif|jpe?g|png|webp|mp4)$/i', $file->uploaded_filename))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list($width, $height) = @getimagesize($file->uploaded_filename);
|
||||
if ($width < 100 && $height < 100)
|
||||
if (str_starts_with($file->mime_type, 'video/'))
|
||||
{
|
||||
continue;
|
||||
if ($file->thumbnail_filename)
|
||||
{
|
||||
list($width, $height) = @getimagesize($file->thumbnail_filename);
|
||||
if ($width >= 100 || $height >= 100)
|
||||
{
|
||||
$document_images[] = array('filepath' => $file->thumbnail_filename, 'width' => $width, 'height' => $height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
list($width, $height) = @getimagesize($file->uploaded_filename);
|
||||
if ($width >= 100 || $height >= 100)
|
||||
{
|
||||
$document_images[] = array('filepath' => $file->uploaded_filename, 'width' => $width, 'height' => $height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$document_images[] = array('filepath' => $file->uploaded_filename, 'width' => $width, 'height' => $height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Rhymix\Framework\Cache::set("seo:document_images:$document_srl", $document_images);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,16 @@ class JSONDisplayHandler
|
|||
$variables['message'] = $oModule->getMessage();
|
||||
|
||||
self::_convertCompat($variables, Context::getRequestMethod());
|
||||
return json_encode($variables) . "\n";
|
||||
$result = json_encode($variables) . "\n";
|
||||
if (json_last_error() != \JSON_ERROR_NONE)
|
||||
{
|
||||
trigger_error('JSON encoding error: ' . json_last_error_msg(), E_USER_WARNING);
|
||||
return json_encode([
|
||||
'error' => -1,
|
||||
'message' => 'JSON encoding error',
|
||||
]) . "\n";
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -703,7 +703,7 @@ class ModuleHandler extends Handler
|
|||
$procResult = $oModule->proc();
|
||||
|
||||
$methodList = array('XMLRPC' => 1, 'JSON' => 1, 'JS_CALLBACK' => 1);
|
||||
if(!$oModule->stop_proc && !isset($methodList[Context::getRequestMethod()]) && !isset($_SERVER['HTTP_X_AJAX_TARGET']) && !isset($_POST['_rx_ajax_form']))
|
||||
if(!$oModule->stop_proc && !isset($methodList[Context::getRequestMethod()]))
|
||||
{
|
||||
$error = $oModule->getError();
|
||||
$message = $oModule->getMessage();
|
||||
|
|
@ -1014,28 +1014,6 @@ class ModuleHandler extends Handler
|
|||
$methodList = array('XMLRPC' => 1, 'JSON' => 1, 'JS_CALLBACK' => 1);
|
||||
if(!isset($methodList[Context::getRequestMethod()]))
|
||||
{
|
||||
// Handle iframe form submissions.
|
||||
$ajax_form_target = strval($_SERVER['HTTP_X_AJAX_TARGET'] ?? ($_POST['_rx_ajax_form'] ?? ''));
|
||||
if($ajax_form_target !== '' && starts_with('_rx_temp_iframe_', $ajax_form_target))
|
||||
{
|
||||
$data = [];
|
||||
if ($this->error)
|
||||
{
|
||||
$data['error'] = -1;
|
||||
$data['message'] = lang($this->error);
|
||||
}
|
||||
else
|
||||
{
|
||||
$data['error'] = $oModule->error;
|
||||
$data['message'] = lang($oModule->message);
|
||||
}
|
||||
$data = array_merge($data, $oModule->getVariables());
|
||||
|
||||
ob_end_clean();
|
||||
echo sprintf('<html><head></head><body><script>parent.XE.handleIframeResponse(%s, %s);</script></body></html>', json_encode($ajax_form_target), json_encode($data));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle redirects.
|
||||
if($oModule->getRedirectUrl())
|
||||
{
|
||||
|
|
@ -1122,7 +1100,7 @@ class ModuleHandler extends Handler
|
|||
if($layout_info)
|
||||
{
|
||||
// Input extra_vars into $layout_info
|
||||
if($layout_info->extra_var_count)
|
||||
if(isset($layout_info->extra_var_count) && $layout_info->extra_var_count)
|
||||
{
|
||||
|
||||
foreach($layout_info->extra_var as $var_id => $val)
|
||||
|
|
@ -1138,7 +1116,7 @@ class ModuleHandler extends Handler
|
|||
}
|
||||
}
|
||||
// Set menus into context
|
||||
if($layout_info->menu_count)
|
||||
if(isset($layout_info->menu_count) && $layout_info->menu_count)
|
||||
{
|
||||
$oMenuAdminController = getAdminController('menu');
|
||||
$homeMenuCacheFile = null;
|
||||
|
|
|
|||
|
|
@ -210,8 +210,7 @@ class BaseObject
|
|||
*/
|
||||
public function add($key, $val)
|
||||
{
|
||||
$this->variables[$key] = $val;
|
||||
return $this;
|
||||
return $this->set($key, $val);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"ezyang/htmlpurifier": "4.16.*",
|
||||
"google/auth": "1.26.*",
|
||||
"guzzlehttp/guzzle": "7.8.*",
|
||||
"guzzlehttp/psr7": "2.6.*",
|
||||
"jbbcode/jbbcode": "1.4.*",
|
||||
"leafo/lessphp": "dev-master",
|
||||
"league/html-to-markdown": "5.1.*",
|
||||
|
|
|
|||
|
|
@ -463,7 +463,9 @@ class DB
|
|||
|
||||
if ($this->isError())
|
||||
{
|
||||
return $this->getError();
|
||||
$output = $this->getError();
|
||||
$output->add('_count', $query_string);
|
||||
return $output;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -474,11 +476,13 @@ class DB
|
|||
catch (Exceptions\DBError $e)
|
||||
{
|
||||
$output = $this->setError(-1, $e->getMessage());
|
||||
$output->add('_count', $query_string);
|
||||
return $output;
|
||||
}
|
||||
catch (\PDOException $e)
|
||||
{
|
||||
$output = $this->setError(-1, $e->getMessage());
|
||||
$output->add('_count', $query_string);
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -560,6 +560,13 @@ class Mail
|
|||
*/
|
||||
public function send(): bool
|
||||
{
|
||||
// If queue is enabled, send asynchronously.
|
||||
if (config('queue.enabled') && !defined('RXQUEUE_CRON'))
|
||||
{
|
||||
Queue::addTask(self::class . '::' . 'sendAsync', $this);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get caller information.
|
||||
$backtrace = debug_backtrace(0);
|
||||
if(count($backtrace) && isset($backtrace[0]['file']))
|
||||
|
|
@ -600,6 +607,17 @@ class Mail
|
|||
return $this->sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email asynchronously (for Queue integration).
|
||||
*
|
||||
* @param self $mail
|
||||
* @return void
|
||||
*/
|
||||
public static function sendAsync(self $mail): void
|
||||
{
|
||||
$mail->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the message was sent.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -402,6 +402,13 @@ class Push
|
|||
*/
|
||||
public function send(): bool
|
||||
{
|
||||
// If queue is enabled, send asynchronously.
|
||||
if (config('queue.enabled') && !defined('RXQUEUE_CRON'))
|
||||
{
|
||||
Queue::addTask(self::class . '::' . 'sendAsync', $this);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get caller information.
|
||||
$backtrace = debug_backtrace(0);
|
||||
if(count($backtrace) && isset($backtrace[0]['file']))
|
||||
|
|
@ -464,6 +471,17 @@ class Push
|
|||
return $this->sent > 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send asynchronously (for Queue integration).
|
||||
*
|
||||
* @param self $sms
|
||||
* @return void
|
||||
*/
|
||||
public static function sendAsync(self $push): void
|
||||
{
|
||||
$push->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the device token
|
||||
*
|
||||
|
|
|
|||
302
common/framework/Queue.php
Normal file
302
common/framework/Queue.php
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework;
|
||||
|
||||
/**
|
||||
* The Queue class.
|
||||
*/
|
||||
class Queue
|
||||
{
|
||||
/**
|
||||
* Static properties.
|
||||
*/
|
||||
protected static $_drivers = [];
|
||||
|
||||
/**
|
||||
* Add a custom Queue driver.
|
||||
*
|
||||
* @param string $name
|
||||
* @param object $driver
|
||||
* @return void
|
||||
*/
|
||||
public static function addDriver(string $name, Drivers\QueueInterface $driver): void
|
||||
{
|
||||
self::$_drivers[$name] = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default driver.
|
||||
*
|
||||
* @param string $name
|
||||
* @return ?Drivers\QueueInterface
|
||||
*/
|
||||
public static function getDriver(string $name): ?Drivers\QueueInterface
|
||||
{
|
||||
if (isset(self::$_drivers[$name]))
|
||||
{
|
||||
return self::$_drivers[$name];
|
||||
}
|
||||
|
||||
$driver_class = '\Rhymix\Framework\Drivers\Queue\\' . $name;
|
||||
if (class_exists($driver_class))
|
||||
{
|
||||
$driver_config = config('queue.' . $name) ?: [];
|
||||
return self::$_drivers[$name] = $driver_class::getInstance($driver_config);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of supported Queue drivers.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getSupportedDrivers(): array
|
||||
{
|
||||
$result = [];
|
||||
foreach (Storage::readDirectory(__DIR__ . '/drivers/queue', false) as $filename)
|
||||
{
|
||||
$driver_name = substr($filename, 0, -4);
|
||||
$class_name = '\Rhymix\Framework\Drivers\Queue\\' . $driver_name;
|
||||
if ($class_name::isSupported())
|
||||
{
|
||||
$result[$driver_name] = [
|
||||
'name' => $class_name::getName(),
|
||||
'required' => $class_name::getRequiredConfig(),
|
||||
'optional' => $class_name::getOptionalConfig(),
|
||||
];
|
||||
}
|
||||
}
|
||||
foreach (self::$_drivers as $driver_name => $driver)
|
||||
{
|
||||
if ($driver->isSupported())
|
||||
{
|
||||
$result[$driver_name] = [
|
||||
'name' => $driver->getName(),
|
||||
'required' => $driver->getRequiredConfig(),
|
||||
'optional' => $driver->getOptionalConfig(),
|
||||
];
|
||||
}
|
||||
}
|
||||
ksort($result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task.
|
||||
*
|
||||
* The handler can be in one of the following formats:
|
||||
* - Global function, e.g. myHandler
|
||||
* - ClassName::staticMethodName
|
||||
* - ClassName::getInstance()->methodName
|
||||
* - new ClassName()->methodName
|
||||
*
|
||||
* Once identified and/or instantiated, the handler will be passed $args
|
||||
* and $options, in that order. Each of them must be a single object.
|
||||
*
|
||||
* It is strongly recommended that you write a dedicated method to handle
|
||||
* queued tasks, rather than reusing an existing method with a potentially
|
||||
* incompatible structure. If you must to call an existing method,
|
||||
* you should consider writing a wrapper.
|
||||
*
|
||||
* Any value returned by the handler will be discarded. If you throw an
|
||||
* exception, it may be logged, but it will not cause a fatal error.
|
||||
*
|
||||
* @param string $handler
|
||||
* @param ?object $args
|
||||
* @param ?object $options
|
||||
* @return int
|
||||
*/
|
||||
public static function addTask(string $handler, ?object $args = null, ?object $options = null): int
|
||||
{
|
||||
$driver_name = config('queue.driver');
|
||||
if (!$driver_name)
|
||||
{
|
||||
throw new Exceptions\FeatureDisabled('Queue not configured');
|
||||
}
|
||||
|
||||
$driver = self::getDriver($driver_name);
|
||||
if (!$driver)
|
||||
{
|
||||
throw new Exceptions\FeatureDisabled('Queue not configured');
|
||||
}
|
||||
|
||||
return $driver->addTask($handler, $args, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first task to execute immediately.
|
||||
*
|
||||
* If no tasks are pending, this method will return null.
|
||||
* Detailed scheduling of tasks will be handled by each driver.
|
||||
*
|
||||
* @param int $blocking
|
||||
* @return ?object
|
||||
*/
|
||||
public static function getTask(int $blocking = 0): ?object
|
||||
{
|
||||
$driver_name = config('queue.driver');
|
||||
if (!$driver_name)
|
||||
{
|
||||
throw new Exceptions\FeatureDisabled('Queue not configured');
|
||||
}
|
||||
|
||||
$driver = self::getDriver($driver_name);
|
||||
if (!$driver)
|
||||
{
|
||||
throw new Exceptions\FeatureDisabled('Queue not configured');
|
||||
}
|
||||
|
||||
return $driver->getTask($blocking);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the process key.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkKey(string $key): bool
|
||||
{
|
||||
return config('queue.key') === $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the queue.
|
||||
*
|
||||
* This will usually be called by a separate script, run every minute
|
||||
* through an external scheduler such as crontab or systemd.
|
||||
*
|
||||
* If you are on a shared hosting service, you may also call a URL
|
||||
* using a "web cron" service provider.
|
||||
*
|
||||
* @param int $timeout
|
||||
* @return void
|
||||
*/
|
||||
public static function process(int $timeout): void
|
||||
{
|
||||
// This part will run in a loop until timeout.
|
||||
$process_start_time = microtime(true);
|
||||
while (true)
|
||||
{
|
||||
// Get a task from the driver.
|
||||
$loop_start_time = microtime(true);
|
||||
$task = self::getTask(1);
|
||||
|
||||
// Wait 1 second and loop back.
|
||||
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(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
// If the timeout is imminent, break the loop.
|
||||
$process_elapsed_time = microtime(true) - $process_start_time;
|
||||
if ($process_elapsed_time > $timeout - 2)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// If there was no task, wait 1 second to make sure that the loop isn't too tight.
|
||||
$loop_elapsed_time = microtime(true) - $loop_start_time;
|
||||
if (!$task && $loop_elapsed_time < 1)
|
||||
{
|
||||
usleep(intval((1 - $loop_elapsed_time) * 1000000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -511,6 +511,13 @@ class SMS
|
|||
*/
|
||||
public function send(): bool
|
||||
{
|
||||
// If queue is enabled, send asynchronously.
|
||||
if (config('queue.enabled') && !defined('RXQUEUE_CRON'))
|
||||
{
|
||||
Queue::addTask(self::class . '::' . 'sendAsync', $this);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get caller information.
|
||||
$backtrace = debug_backtrace(0);
|
||||
if(count($backtrace) && isset($backtrace[0]['file']))
|
||||
|
|
@ -566,6 +573,17 @@ class SMS
|
|||
return $this->sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an SMS asynchronously (for Queue integration).
|
||||
*
|
||||
* @param self $sms
|
||||
* @return void
|
||||
*/
|
||||
public static function sendAsync(self $sms): void
|
||||
{
|
||||
$sms->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the message was sent.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ class UA
|
|||
}
|
||||
|
||||
// Look for common search engine names and the 'bot' keyword.
|
||||
if (preg_match('/bot|spider|crawler|archiver|wget|curl|php|slurp|wordpress|facebook|teoma|yeti|daum|apachebench|mediapartners-google|[(<+]https?:|@/i', $ua))
|
||||
if (preg_match('/bot|spider|crawler|archiver|wget|curl|php|slurp|wordpress|facebook|external(agent|fetcher)|teoma|yeti|daum|apachebench|googleother|mediapartners-google|[(<+]https?:|@/i', $ua))
|
||||
{
|
||||
return self::$_robot_cache[$ua] = true;
|
||||
}
|
||||
|
|
|
|||
71
common/framework/drivers/QueueInterface.php
Normal file
71
common/framework/drivers/QueueInterface.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework\Drivers;
|
||||
|
||||
/**
|
||||
* The Queue driver interface.
|
||||
*/
|
||||
interface QueueInterface
|
||||
{
|
||||
/**
|
||||
* Create a new instance of the current Queue driver, using the given settings.
|
||||
*
|
||||
* @param array $config
|
||||
* @return QueueInterface
|
||||
*/
|
||||
public static function getInstance(array $config): QueueInterface;
|
||||
|
||||
/**
|
||||
* Get the human-readable name of this Queue driver.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getName(): string;
|
||||
|
||||
/**
|
||||
* Get the list of configuration fields required by this Queue driver.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getRequiredConfig(): array;
|
||||
|
||||
/**
|
||||
* Get the list of configuration fields optionally used by this Queue driver.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getOptionalConfig(): array;
|
||||
|
||||
/**
|
||||
* Check if this driver is supported on this server.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupported(): bool;
|
||||
|
||||
/**
|
||||
* Validate driver configuration.
|
||||
*
|
||||
* @param mixed $config
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateConfig($config): bool;
|
||||
|
||||
/**
|
||||
* Add a task.
|
||||
*
|
||||
* @param string $handler
|
||||
* @param ?object $args
|
||||
* @param ?object $options
|
||||
* @return int
|
||||
*/
|
||||
public function addTask(string $handler, ?object $args = null, ?object $options = null): int;
|
||||
|
||||
/**
|
||||
* Get the first task.
|
||||
*
|
||||
* @param int $blocking
|
||||
* @return ?object
|
||||
*/
|
||||
public function getTask(int $blocking = 0): ?object;
|
||||
}
|
||||
131
common/framework/drivers/queue/db.php
Normal file
131
common/framework/drivers/queue/db.php
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework\Drivers\Queue;
|
||||
|
||||
use Rhymix\Framework\DB as RFDB;
|
||||
use Rhymix\Framework\Drivers\QueueInterface;
|
||||
|
||||
/**
|
||||
* The DB queue driver.
|
||||
*/
|
||||
class DB implements QueueInterface
|
||||
{
|
||||
/**
|
||||
* Create a new instance of the current Queue driver, using the given settings.
|
||||
*
|
||||
* @param array $config
|
||||
* @return QueueInterface
|
||||
*/
|
||||
public static function getInstance(array $config): QueueInterface
|
||||
{
|
||||
return new self($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human-readable name of this Queue driver.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'DB';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of configuration fields required by this Queue driver.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getRequiredConfig(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of configuration fields optionally used by this Queue driver.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getOptionalConfig(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this driver is supported on this server.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupported(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate driver configuration.
|
||||
*
|
||||
* @param mixed $config
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateConfig($config): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task.
|
||||
*
|
||||
* @param string $handler
|
||||
* @param ?object $args
|
||||
* @param ?object $options
|
||||
* @return int
|
||||
*/
|
||||
public function addTask(string $handler, ?object $args = null, ?object $options = null): int
|
||||
{
|
||||
$oDB = RFDB::getInstance();
|
||||
$stmt = $oDB->prepare('INSERT INTO task_queue (handler, args, options) VALUES (?, ?, ?)');
|
||||
$result = $stmt->execute([$handler, serialize($args), serialize($options)]);
|
||||
return $result ? $oDB->getInsertID() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first task.
|
||||
*
|
||||
* @param int $blocking
|
||||
* @return ?object
|
||||
*/
|
||||
public function getTask(int $blocking = 0): ?object
|
||||
{
|
||||
$oDB = RFDB::getInstance();
|
||||
$oDB->beginTransaction();
|
||||
$stmt = $oDB->query('SELECT * FROM task_queue ORDER BY id LIMIT 1 FOR UPDATE');
|
||||
$result = $stmt->fetchObject();
|
||||
$stmt->closeCursor();
|
||||
|
||||
if ($result)
|
||||
{
|
||||
$stmt = $oDB->prepare('DELETE FROM task_queue WHERE id = ?');
|
||||
$stmt->execute([$result->id]);
|
||||
$oDB->commit();
|
||||
|
||||
$result->args = unserialize($result->args);
|
||||
$result->options = unserialize($result->options);
|
||||
return $result;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oDB->commit();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
119
common/framework/drivers/queue/dummy.php
Normal file
119
common/framework/drivers/queue/dummy.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework\Drivers\Queue;
|
||||
|
||||
use Rhymix\Framework\Drivers\QueueInterface;
|
||||
|
||||
/**
|
||||
* The Dummy queue driver.
|
||||
*/
|
||||
class Dummy implements QueueInterface
|
||||
{
|
||||
/**
|
||||
* Dummy queue for testing.
|
||||
*/
|
||||
protected $_dummy_queue;
|
||||
|
||||
/**
|
||||
* Create a new instance of the current Queue driver, using the given settings.
|
||||
*
|
||||
* @param array $config
|
||||
* @return QueueInterface
|
||||
*/
|
||||
public static function getInstance(array $config): QueueInterface
|
||||
{
|
||||
return new self($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human-readable name of this Queue driver.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'Dummy';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of configuration fields required by this Queue driver.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getRequiredConfig(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of configuration fields optionally used by this Queue driver.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getOptionalConfig(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this driver is supported on this server.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupported(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate driver configuration.
|
||||
*
|
||||
* @param mixed $config
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateConfig($config): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task.
|
||||
*
|
||||
* @param string $handler
|
||||
* @param ?object $args
|
||||
* @param ?object $options
|
||||
* @return int
|
||||
*/
|
||||
public function addTask(string $handler, ?object $args = null, ?object $options = null): int
|
||||
{
|
||||
$this->_dummy_queue = (object)[
|
||||
'handler' => $handler,
|
||||
'args' => $args,
|
||||
'options' => $options,
|
||||
];
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first task.
|
||||
*
|
||||
* @param int $blocking
|
||||
* @return ?object
|
||||
*/
|
||||
public function getTask(int $blocking = 0): ?object
|
||||
{
|
||||
$result = $this->_dummy_queue;
|
||||
$this->_dummy_queue = null;
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
197
common/framework/drivers/queue/redis.php
Normal file
197
common/framework/drivers/queue/redis.php
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework\Drivers\Queue;
|
||||
|
||||
use Rhymix\Framework\Drivers\QueueInterface;
|
||||
|
||||
/**
|
||||
* The Redis queue driver.
|
||||
*/
|
||||
class Redis implements QueueInterface
|
||||
{
|
||||
/**
|
||||
* The Redis connection is stored here.
|
||||
*/
|
||||
protected $_conn;
|
||||
protected $_key;
|
||||
|
||||
/**
|
||||
* Create a new instance of the current Queue driver, using the given settings.
|
||||
*
|
||||
* @param array $config
|
||||
* @return QueueInterface
|
||||
*/
|
||||
public static function getInstance(array $config): QueueInterface
|
||||
{
|
||||
return new self($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human-readable name of this Queue driver.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'Redis';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of configuration fields required by this Queue driver.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getRequiredConfig(): array
|
||||
{
|
||||
return ['host', 'port'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of configuration fields optionally used by this Queue driver.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getOptionalConfig(): array
|
||||
{
|
||||
return ['dbnum', 'user', 'pass'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this driver is supported on this server.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupported(): bool
|
||||
{
|
||||
return class_exists('\\Redis');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate driver configuration.
|
||||
*
|
||||
* @param mixed $config
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateConfig($config): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$test = new \Redis;
|
||||
$test->connect($config['host'], $config['port'] ?? 6379);
|
||||
if (isset($config['user']) || isset($config['pass']))
|
||||
{
|
||||
$auth = [];
|
||||
if (isset($config['user']) && $config['user']) $auth[] = $config['user'];
|
||||
if (isset($config['pass']) && $config['pass']) $auth[] = $config['pass'];
|
||||
$test->auth(count($auth) > 1 ? $auth : $auth[0]);
|
||||
}
|
||||
if (isset($config['dbnum']))
|
||||
{
|
||||
$test->select(intval($config['dbnum']));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (\Throwable $th)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->_conn = new \Redis;
|
||||
$this->_conn->connect($config['host'], $config['port'] ?? 6379);
|
||||
if (isset($config['user']) || isset($config['pass']))
|
||||
{
|
||||
$auth = [];
|
||||
if (isset($config['user']) && $config['user']) $auth[] = $config['user'];
|
||||
if (isset($config['pass']) && $config['pass']) $auth[] = $config['pass'];
|
||||
$this->_conn->auth(count($auth) > 1 ? $auth : $auth[0]);
|
||||
}
|
||||
if (isset($config['dbnum']))
|
||||
{
|
||||
$this->_conn->select(intval($config['dbnum']));
|
||||
}
|
||||
$this->_key = 'rxQueue_' . substr(hash_hmac('sha1', 'rxQueue_', config('crypto.authentication_key')), 0, 24);
|
||||
}
|
||||
catch (\RedisException $e)
|
||||
{
|
||||
$this->_conn = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task.
|
||||
*
|
||||
* @param string $handler
|
||||
* @param ?object $args
|
||||
* @param ?object $options
|
||||
* @return int
|
||||
*/
|
||||
public function addTask(string $handler, ?object $args = null, ?object $options = null): int
|
||||
{
|
||||
$value = serialize((object)[
|
||||
'handler' => $handler,
|
||||
'args' => $args,
|
||||
'options' => $options,
|
||||
]);
|
||||
|
||||
if ($this->_conn)
|
||||
{
|
||||
$result = $this->_conn->rPush($this->_key, $value);
|
||||
return intval($result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first task.
|
||||
*
|
||||
* @param int $blocking
|
||||
* @return ?object
|
||||
*/
|
||||
public function getTask(int $blocking = 0): ?object
|
||||
{
|
||||
if ($this->_conn)
|
||||
{
|
||||
if ($blocking > 0)
|
||||
{
|
||||
$result = $this->_conn->blpop($this->_key, $blocking);
|
||||
if (is_array($result) && isset($result[1]))
|
||||
{
|
||||
return unserialize($result[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = $this->_conn->lpop($this->_key);
|
||||
if ($result)
|
||||
{
|
||||
return unserialize($result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -132,6 +132,11 @@ class SolAPI extends Base implements \Rhymix\Framework\Drivers\SMSInterface
|
|||
}
|
||||
}
|
||||
|
||||
foreach ($original->getExtraVars() as $key => $value)
|
||||
{
|
||||
$options->$key = $value;
|
||||
}
|
||||
|
||||
$data['messages'][] = $options;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ class FilenameFilter
|
|||
*/
|
||||
public static function clean(string $filename): string
|
||||
{
|
||||
// Clean up unnecessary encodings.
|
||||
$filename = strtr($filename, ['&' => '&', ''' => "'"]);
|
||||
|
||||
// Replace dangerous characters with safe alternatives, maintaining meaning as much as possible.
|
||||
$illegal = array('\\', '/', '<', '>', '{', '}', ':', ';', '|', '"', '~', '`', '$', '%', '^', '*', '?');
|
||||
$replace = array('', '', '(', ')', '(', ')', '_', ',', '_', '', '_', '\'', '_', '_', '_', '', '');
|
||||
|
|
@ -31,9 +34,6 @@ class FilenameFilter
|
|||
$filename = preg_replace('/__+/', '_', $filename);
|
||||
$filename = preg_replace('/\.\.+/', '.', $filename);
|
||||
|
||||
// Clean up unnecessary encodings.
|
||||
$filename = strtr($filename, array('&' => '&'));
|
||||
|
||||
// Change .php files to .phps to make them non-executable.
|
||||
if (strtolower(substr($filename, strlen($filename) - 4)) === '.php')
|
||||
{
|
||||
|
|
|
|||
|
|
@ -353,12 +353,12 @@
|
|||
unselectNonImageFiles: function() {},
|
||||
|
||||
generateHtml: function($container, file) {
|
||||
var filename = String(file.source_filename);
|
||||
var filename = String(file.source_filename).escape();
|
||||
var html = '';
|
||||
var data = $container.data();
|
||||
|
||||
if (filename.match(/\.(gif|jpe?g|png|webp)$/i)) {
|
||||
html = '<img src="' + file.download_url + '" alt="' + file.source_filename + '"'
|
||||
html = '<img src="' + file.download_url + '" alt="' + filename + '"'
|
||||
+ ' editor_component="image_link" data-file-srl="' + file.file_srl + '" />';
|
||||
}
|
||||
else if (filename.match(/\.(mp3|wav|ogg|flac|aac)$/i)) {
|
||||
|
|
@ -388,7 +388,7 @@
|
|||
}
|
||||
|
||||
if (html === '') {
|
||||
html += '<a href="' + file.download_url + '" data-file-srl="' + file.file_srl + '">' + file.source_filename + '</a>\n';
|
||||
html += '<a href="' + file.download_url + '" data-file-srl="' + file.file_srl + '">' + filename + '</a>\n';
|
||||
}
|
||||
|
||||
return html;
|
||||
|
|
@ -405,7 +405,6 @@
|
|||
try {
|
||||
var textarea = _getCkeContainer(data.editorSequence).find('.cke_source');
|
||||
var editor = _getCkeInstance(data.editorSequence);
|
||||
console.log(textarea, editor.mode);
|
||||
if (textarea.length && editor.mode == 'source') {
|
||||
var caretPosition = textarea[0].selectionStart;
|
||||
var currentText = textarea[0].value;
|
||||
|
|
@ -516,7 +515,7 @@
|
|||
$container.data(data);
|
||||
|
||||
file.thumbnail_url = file.download_url;
|
||||
file.source_filename = file.source_filename.replace("&", "&");
|
||||
file.source_filename = file.source_filename.replace("&", "&").replace("'", "'");
|
||||
|
||||
if(file.thumbnail_filename) {
|
||||
file.thumbnail_url = file.thumbnail_filename;
|
||||
|
|
|
|||
|
|
@ -132,7 +132,6 @@ $.fn.simpleTree = function(opt){
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
TREE.setTreeNodes = function(obj, useParent){
|
||||
|
|
@ -226,10 +225,7 @@ $.fn.simpleTree = function(opt){
|
|||
trigger.click(function(event){
|
||||
TREE.nodeToggle(node);
|
||||
});
|
||||
if(!$.browser.msie)
|
||||
{
|
||||
trigger.css('float','left');
|
||||
}
|
||||
trigger.css('float','left');
|
||||
};
|
||||
TREE.dragStart = function(event){
|
||||
var LI = $(event.data.LI);
|
||||
|
|
|
|||
|
|
@ -214,11 +214,13 @@
|
|||
var url = XE.URI(request_uri).pathname();
|
||||
var action_parts = action.split('.');
|
||||
var request_info;
|
||||
if (action === 'raw') {
|
||||
|
||||
if (params instanceof FormData) {
|
||||
request_info = (params.get('module') || params.get('mid')) + '.' + params.get('act');
|
||||
} else if (action === 'raw') {
|
||||
request_info = 'RAW FORM SUBMISSION';
|
||||
} else {
|
||||
params = params ? ($.isArray(params) ? arr2obj(params) : params) : {};
|
||||
//if (action_parts.length != 2) return;
|
||||
params.module = action_parts[0];
|
||||
params.act = action_parts[1];
|
||||
request_info = params.module + "." + params.act;
|
||||
|
|
@ -342,7 +344,7 @@
|
|||
|
||||
// Send the AJAX request.
|
||||
try {
|
||||
$.ajax({
|
||||
var args = {
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url,
|
||||
|
|
@ -351,7 +353,11 @@
|
|||
headers : headers,
|
||||
success : successHandler,
|
||||
error : errorHandler
|
||||
});
|
||||
};
|
||||
if (params instanceof FormData) {
|
||||
args.contentType = false;
|
||||
}
|
||||
$.ajax(args);
|
||||
} catch(e) {
|
||||
alert(e);
|
||||
return;
|
||||
|
|
@ -376,7 +382,6 @@
|
|||
* Function for AJAX submission of arbitrary forms.
|
||||
*/
|
||||
XE.ajaxForm = function(form, callback_success, callback_error) {
|
||||
// Abort if the form already has a 'target' attribute.
|
||||
form = $(form);
|
||||
// Get success and error callback functions.
|
||||
if (typeof callback_success === 'undefined') {
|
||||
|
|
@ -402,43 +407,10 @@
|
|||
callback_error = null;
|
||||
}
|
||||
}
|
||||
// Set _rx_ajax_form flag
|
||||
if (!form.find('input[name=_rx_ajax_form]').size()) {
|
||||
form.append('<input type="hidden" name="_rx_ajax_form" value="json" />');
|
||||
setTimeout(function() {
|
||||
form.find('input[name=_rx_ajax_form]').remove();
|
||||
}, 1000);
|
||||
}
|
||||
// If the form has file uploads, use a hidden iframe to submit. Otherwise use exec_json.
|
||||
var has_files = form.find('input[type=file][name!=Filedata]').size();
|
||||
if (has_files) {
|
||||
var iframe_id = '_rx_temp_iframe_' + (new Date()).getTime();
|
||||
$('<iframe id="' + iframe_id + '" name="' + iframe_id + '" style="display:none"></iframe>').appendTo($(document.body));
|
||||
form.attr('method', 'POST').attr('enctype', 'multipart/form-data').attr('target', iframe_id);
|
||||
form.find('input[name=_rx_ajax_form]').val(iframe_id);
|
||||
window.XE.handleIframeResponse = function(iframe_id, data) {
|
||||
if (data.error) {
|
||||
if (callback_error) {
|
||||
callback_error(data);
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
} else {
|
||||
callback_success(data);
|
||||
}
|
||||
if (iframe_id.match(/^_rx_temp_iframe_[0-9]+$/)) {
|
||||
$('iframe#' + iframe_id).remove();
|
||||
}
|
||||
};
|
||||
setTimeout(function() {
|
||||
form.removeAttr('target');
|
||||
}, 1000);
|
||||
form.submit();
|
||||
} else {
|
||||
window.exec_json('raw', form.serialize(), callback_success, callback_error);
|
||||
}
|
||||
window.exec_json('raw', new FormData(form[0]), callback_success, callback_error);
|
||||
};
|
||||
$(document).on('submit', 'form.rx_ajax', function(event) {
|
||||
// Abort if the form already has a 'target' attribute.
|
||||
if (!$(this).attr('target')) {
|
||||
event.preventDefault();
|
||||
XE.ajaxForm(this);
|
||||
|
|
|
|||
|
|
@ -415,6 +415,7 @@ function legacy_filter(filter_name, form, module, act, callback, responses, conf
|
|||
|
||||
args[0] = filter_name;
|
||||
args[1] = function(f) {
|
||||
var hasFile = false;
|
||||
var params = {}, res = [], elms = f.elements, data = $(f).serializeArray();
|
||||
$.each(data, function(i, field) {
|
||||
var v = $.trim(field.value), n = field.name;
|
||||
|
|
@ -435,11 +436,20 @@ function legacy_filter(filter_name, form, module, act, callback, responses, conf
|
|||
}
|
||||
});
|
||||
|
||||
$(f).find('input[type=file][name^=extra_vars]').each(function() {
|
||||
if (this.files && this.files[0]) {
|
||||
params[this.name] = this.files[0];
|
||||
hasFile = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (confirm_msg && !confirm(confirm_msg)) return false;
|
||||
|
||||
//exec_xml(module, act, params, callback, responses, params, form);
|
||||
params['module'] = module;
|
||||
params['act'] = act;
|
||||
params['_rx_ajax_compat'] = 'XMLRPC';
|
||||
exec_json(module + '.' + act, params, function(result) {
|
||||
|
||||
var callback_wrapper = function(result) {
|
||||
if (!result) {
|
||||
result = {};
|
||||
}
|
||||
|
|
@ -454,7 +464,17 @@ function legacy_filter(filter_name, form, module, act, callback, responses, conf
|
|||
});
|
||||
callback(filtered_result, responses, params, form);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!hasFile) {
|
||||
exec_json(module + '.' + act, params, callback_wrapper);
|
||||
} else {
|
||||
var fd = new FormData();
|
||||
for (let key in params) {
|
||||
fd.append(key, params[key]);
|
||||
}
|
||||
exec_json('raw', fd, callback_wrapper);
|
||||
}
|
||||
};
|
||||
|
||||
v.cast('ADD_CALLBACK', args);
|
||||
|
|
|
|||
|
|
@ -328,6 +328,8 @@ $lang->column_type_list['language'] = 'Language';
|
|||
$lang->column_type_list['date'] = 'Date';
|
||||
$lang->column_type_list['time'] = 'Time';
|
||||
$lang->column_type_list['timezone'] = 'Time zone';
|
||||
$lang->column_type_list['number'] = 'Number';
|
||||
$lang->column_type_list['file'] = 'File upload';
|
||||
$lang->column_name = 'Column Name';
|
||||
$lang->column_title = 'Column Title';
|
||||
$lang->default_value = 'Default Value';
|
||||
|
|
|
|||
|
|
@ -330,6 +330,8 @@ $lang->column_type_list['language'] = '언어';
|
|||
$lang->column_type_list['date'] = '날짜';
|
||||
$lang->column_type_list['time'] = '시간';
|
||||
$lang->column_type_list['timezone'] = '표준 시간대';
|
||||
$lang->column_type_list['number'] = '숫자';
|
||||
$lang->column_type_list['file'] = '파일 업로드';
|
||||
$lang->column_name = '입력항목 이름';
|
||||
$lang->column_title = '입력항목 제목';
|
||||
$lang->default_value = '기본값';
|
||||
|
|
|
|||
91
common/scripts/cron.php
Normal file
91
common/scripts/cron.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This script runs the task queue.
|
||||
*
|
||||
* Unlike other scripts provided with Rhymix, it can be called
|
||||
* both on the command line and over the network.
|
||||
*/
|
||||
define('RXQUEUE_CRON', true);
|
||||
|
||||
// If called on the CLI, run additional checks.
|
||||
if (PHP_SAPI === 'cli')
|
||||
{
|
||||
require_once __DIR__ . '/common.php';
|
||||
}
|
||||
else
|
||||
{
|
||||
// If called over the network, load Rhymix directly.
|
||||
chdir(dirname(dirname(__DIR__)));
|
||||
require_once dirname(__DIR__) . '/autoload.php';
|
||||
Context::init();
|
||||
|
||||
// On the other hand, we should check the key.
|
||||
$key = (string)Context::get('key');
|
||||
if (!Rhymix\Framework\Queue::checkKey($key))
|
||||
{
|
||||
Context::setCacheControl(0);
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
echo "Invalid key\n";
|
||||
Context::close();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Get queue configuration set by the administrator.
|
||||
$timeout = (config('queue.interval') ?? 1) * 60;
|
||||
$process_count = config('queue.process_count') ?? 1;
|
||||
|
||||
// If called over the network, try to increase the timeout.
|
||||
if (PHP_SAPI !== 'cli')
|
||||
{
|
||||
ignore_user_abort(true);
|
||||
set_time_limit(max(60, $timeout));
|
||||
}
|
||||
|
||||
// Create multiple processes if configured.
|
||||
if (PHP_SAPI === 'cli' && $process_count > 1 && function_exists('pcntl_fork') && function_exists('pcntl_waitpid'))
|
||||
{
|
||||
// This array will keep a dictionary of subprocesses.
|
||||
$pids = [];
|
||||
|
||||
// The database connection must be closed before forking.
|
||||
Rhymix\Framework\DB::getInstance()->disconnect();
|
||||
Rhymix\Framework\Debug::disable();
|
||||
|
||||
// Create the required number of subprocesses.
|
||||
for ($i = 0; $i < $process_count; $i++)
|
||||
{
|
||||
$pid = pcntl_fork();
|
||||
if ($pid > 0)
|
||||
{
|
||||
$pids[$pid] = true;
|
||||
usleep(200000);
|
||||
}
|
||||
elseif ($pid == 0)
|
||||
{
|
||||
Rhymix\Framework\Queue::process($timeout);
|
||||
exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
error_log('RxQueue: could not fork!');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// The parent process waits for its children to finish.
|
||||
while (count($pids))
|
||||
{
|
||||
$pid = pcntl_waitpid(-1, $status, \WNOHANG);
|
||||
if ($pid)
|
||||
{
|
||||
unset($pids[$pid]);
|
||||
}
|
||||
usleep(200000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rhymix\Framework\Queue::process($timeout);
|
||||
}
|
||||
|
|
@ -27,7 +27,6 @@
|
|||
<th class="nowr">{$lang->cmd_setup}</th>
|
||||
<th class="nowr">PC</th>
|
||||
<th class="nowr">Mobile</th>
|
||||
<th class="nowr">{$lang->cmd_delete}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -61,7 +60,6 @@
|
|||
</td>
|
||||
<td><input type="checkbox" name="pc_on[]" title="PC" value="{escape($addon->addon_name, false)}" checked="checked"|cond="$addon->activated && !$addon->isBlacklisted" disabled="disabled"|cond="$addon->isBlacklisted" /></td>
|
||||
<td><input type="checkbox" name="mobile_on[]" title="Mobile" value="{escape($addon->addon_name, false)}" checked="checked"|cond="$addon->mactivated && !$addon->isBlacklisted" disabled="disabled"|cond="$addon->isBlacklisted" /></td>
|
||||
<td><a cond="$addon->remove_url && $addon->version !== 'RX_VERSION'" href="{$addon->remove_url}&return_url={urlencode(getRequestUriByServerEnviroment())}">{$lang->cmd_delete}</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@
|
|||
<action name="procAdminUpdateDebug" class="Controllers\SystemConfig\Debug" />
|
||||
<action name="dispAdminConfigSEO" class="Controllers\SystemConfig\SEO" menu_name="adminConfigurationGeneral" />
|
||||
<action name="procAdminUpdateSEO" class="Controllers\SystemConfig\SEO" />
|
||||
<action name="dispAdminConfigQueue" class="Controllers\SystemConfig\Queue" menu_name="adminConfigurationGeneral" />
|
||||
<action name="procAdminUpdateQueue" class="Controllers\SystemConfig\Queue" />
|
||||
<action name="dispAdminConfigSitelock" class="Controllers\SystemConfig\SiteLock" menu_name="adminConfigurationGeneral" />
|
||||
<action name="procAdminUpdateSitelock" class="Controllers\SystemConfig\SiteLock" />
|
||||
<!-- Admin Interface Config -->
|
||||
|
|
|
|||
135
modules/admin/controllers/systemconfig/Queue.php
Normal file
135
modules/admin/controllers/systemconfig/Queue.php
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Modules\Admin\Controllers\SystemConfig;
|
||||
|
||||
use Context;
|
||||
use Rhymix\Framework\Config;
|
||||
use Rhymix\Framework\Exception;
|
||||
use Rhymix\Framework\Queue as RFQueue;
|
||||
use Rhymix\Framework\Security;
|
||||
use Rhymix\Modules\Admin\Controllers\Base;
|
||||
|
||||
class Queue extends Base
|
||||
{
|
||||
/**
|
||||
* Display Notification Settings page
|
||||
*/
|
||||
public function dispAdminConfigQueue()
|
||||
{
|
||||
// Load queue drivers.
|
||||
$queue_drivers = RFQueue::getSupportedDrivers();
|
||||
uasort($queue_drivers, function($a, $b) {
|
||||
if ($a['name'] === 'Dummy') return -1;
|
||||
if ($b['name'] === 'Dummy') return 1;
|
||||
return strnatcasecmp($a['name'], $b['name']);
|
||||
});
|
||||
Context::set('queue_drivers', $queue_drivers);
|
||||
Context::set('queue_driver', config('queue.driver') ?: 'dummy');
|
||||
|
||||
// Set the default auth key.
|
||||
if (!config('queue.key'))
|
||||
{
|
||||
config('queue.key', Security::getRandom(32));
|
||||
}
|
||||
|
||||
// Set defaults for Redis.
|
||||
if (!config('queue.redis'))
|
||||
{
|
||||
config('queue.redis', [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6379',
|
||||
'dbnum' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->setTemplateFile('config_queue');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update notification configuration.
|
||||
*/
|
||||
public function procAdminUpdateQueue()
|
||||
{
|
||||
$vars = Context::getRequestVars();
|
||||
|
||||
// Enabled?
|
||||
$enabled = $vars->queue_enabled === 'Y';
|
||||
|
||||
// Validate the driver.
|
||||
$drivers = RFQueue::getSupportedDrivers();
|
||||
$driver = trim($vars->queue_driver);
|
||||
if (!array_key_exists($driver, $drivers))
|
||||
{
|
||||
throw new Exception('msg_queue_driver_not_found');
|
||||
}
|
||||
if ($enabled && (!$driver || $driver === 'dummy'))
|
||||
{
|
||||
throw new Exception('msg_queue_driver_cannot_be_dummy');
|
||||
}
|
||||
|
||||
// Validate required and optional driver settings.
|
||||
$driver_config = [];
|
||||
foreach ($drivers[$driver]['required'] as $conf_name)
|
||||
{
|
||||
$conf_value = trim($vars->{'queue_' . $driver . '_' . $conf_name} ?? '');
|
||||
if ($conf_value === '')
|
||||
{
|
||||
throw new Exception('msg_queue_invalid_config');
|
||||
}
|
||||
$driver_config[$conf_name] = $conf_value === '' ? null : $conf_value;
|
||||
}
|
||||
foreach ($drivers[$driver]['optional'] as $conf_name)
|
||||
{
|
||||
$conf_value = trim($vars->{'queue_' . $driver . '_' . $conf_name} ?? '');
|
||||
$driver_config[$conf_name] = $conf_value === '' ? null : $conf_value;
|
||||
}
|
||||
|
||||
// Validate the interval.
|
||||
$interval = intval($vars->queue_interval ?? 1);
|
||||
if ($interval < 1 || $interval > 10)
|
||||
{
|
||||
throw new Exception('msg_queue_invalid_interval');
|
||||
}
|
||||
|
||||
// Validate the process count.
|
||||
$process_count = intval($vars->queue_process_count ?? 1);
|
||||
if ($process_count < 1 || $process_count > 10)
|
||||
{
|
||||
throw new Exception('msg_queue_invalid_process_count');
|
||||
}
|
||||
|
||||
// Validate the key.
|
||||
$key = trim($vars->queue_key ?? '');
|
||||
if (strlen($key) < 16 || !ctype_alnum($key))
|
||||
{
|
||||
throw new Exception('msg_queue_invalid_key');
|
||||
}
|
||||
|
||||
// Validate actual operation of the driver.
|
||||
$driver_class = '\\Rhymix\\Framework\\Drivers\\Queue\\' . $driver;
|
||||
if (!class_exists($driver_class) || !$driver_class::isSupported())
|
||||
{
|
||||
throw new Exception('msg_queue_driver_not_found');
|
||||
}
|
||||
if (!$driver_class::validateConfig($driver_config))
|
||||
{
|
||||
throw new Exception('msg_queue_driver_not_usable');
|
||||
}
|
||||
|
||||
|
||||
// Save system config.
|
||||
Config::set("queue.enabled", $enabled);
|
||||
Config::set("queue.driver", $driver);
|
||||
Config::set("queue.interval", $interval);
|
||||
Config::set("queue.process_count", $process_count);
|
||||
Config::set("queue.key", $key);
|
||||
Config::set("queue.$driver", $driver_config);
|
||||
if (!Config::save())
|
||||
{
|
||||
throw new Exception('msg_failed_to_save_config');
|
||||
}
|
||||
|
||||
$this->setMessage('success_updated');
|
||||
$this->setRedirectUrl(Context::get('success_return_url') ?: getNotEncodedUrl('', 'module', 'admin', 'act', 'dispAdminConfigQueue'));
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ $lang->subtitle_security = 'Security';
|
|||
$lang->subtitle_advanced = 'Advanced';
|
||||
$lang->subtitle_domains = 'Domains';
|
||||
$lang->subtitle_debug = 'Debug';
|
||||
$lang->subtitle_queue = 'Async Queue';
|
||||
$lang->subtitle_seo = 'SEO Settings';
|
||||
$lang->subtitle_etc = 'Other Settings';
|
||||
$lang->current_state = 'Current state';
|
||||
|
|
@ -268,7 +269,7 @@ $lang->about_seo_main_title = 'This format will be used for the title of the mai
|
|||
$lang->seo_subpage_title = 'Subpage Title';
|
||||
$lang->about_seo_subpage_title = 'This format will be used for the title of lists and other major components of your website.<br>In additions to the variables above, you can use <b>$PAGE</b>.';
|
||||
$lang->seo_document_title = 'Document Page Title';
|
||||
$lang->about_seo_document_title = 'This format will be used for the title of individual documents.<br>In additions to the variables above, you can use <b>$DOCUMENT_TITLE</b>.';
|
||||
$lang->about_seo_document_title = 'This format will be used for the title of individual documents.<br>In additions to the variables above, you can use <b>$CATEGORY</b> and <b>$DOCUMENT_TITLE</b>.';
|
||||
$lang->site_meta_keywords = 'SEO Keywords';
|
||||
$lang->about_site_meta_keywords = 'These keywords will be used on pages that do not have their own keywords.';
|
||||
$lang->site_meta_description = 'SEO Description';
|
||||
|
|
@ -282,6 +283,36 @@ $lang->og_extract_images_fallback = 'Use site default image only';
|
|||
$lang->og_extract_hashtags = 'Extract Hashtags from Document';
|
||||
$lang->og_use_nick_name = 'Include Author Name';
|
||||
$lang->og_use_timestamps = 'Include Timestamps';
|
||||
$lang->cmd_queue_description = 'Improve response times by processing time-consuming tasks, such as sending notifications, asynchronously.<br>This is an experimental feature. It may not be stable depending on your hosting environment.';
|
||||
$lang->cmd_queue_enabled = 'Use Task Queue';
|
||||
$lang->cmd_queue_enabled_help = 'The task queue will stop accepting new tasks if you uncheck the above.';
|
||||
$lang->cmd_queue_driver = 'Queue Driver';
|
||||
$lang->cmd_queue_driver_help = 'Select the driver for the task queue that suits your hosting environment and website needs.<br>Some drivers such as Redis will need the corresponding program to be installed on the server.';
|
||||
$lang->cmd_queue_interval = 'Calling Interval';
|
||||
$lang->cmd_queue_interval_help = 'Use a scheduler such as crontab or systemd timer to call the script on a set interval.<br>All tasks are processed as soon as possible regardless of the interval, but a short interval means quick recovery from any error.<br>For web-based cron, this should not exceed the max_execution_time setting in php.ini.<br>The max_execution_time on this server is %d seconds.';
|
||||
$lang->cmd_queue_process_count = 'Process Count';
|
||||
$lang->cmd_queue_process_count_help = 'Use multiple processes to increase throughput. This may increase server load significantly.<br>Keep a value of 1 unless you have a high-performance dedicated server.<br>Multiprocessing is not supported when using web-based cron.';
|
||||
$lang->cmd_queue_call_script = 'Processing Script';
|
||||
$lang->cmd_queue_webcron_key = 'Webcron Auth Key';
|
||||
$lang->cmd_queue_config_keys['host'] = 'Host';
|
||||
$lang->cmd_queue_config_keys['port'] = 'Port';
|
||||
$lang->cmd_queue_config_keys['user'] = 'User';
|
||||
$lang->cmd_queue_config_keys['pass'] = 'Password';
|
||||
$lang->cmd_queue_config_keys['dbnum'] = 'DB Number';
|
||||
$lang->msg_queue_instructions['same_as_php'] = '(same as PHP)';
|
||||
$lang->msg_queue_instructions['crontab1'] = 'Log into the server as the <code>%s</code> account and run <code>crontab -e</code> to paste the following content into your crontab. (DO NOT run it as root!)<br>The <code>%s</code> directory in the example should be replaced with a path where logs can be recorded.';
|
||||
$lang->msg_queue_instructions['crontab2'] = 'If you change the calling interval below, the crontab interval must be adjusted accordingly.';
|
||||
$lang->msg_queue_instructions['webcron'] = 'Configure an external cron service to make a GET request to the following URL every minute, or following the interval set below.<br>Check your logs to make sure that the cron service is reaching your website.';
|
||||
$lang->msg_queue_instructions['systemd1'] = 'Put the following content in <code>/etc/systemd/system/rhymix-queue.service</code>';
|
||||
$lang->msg_queue_instructions['systemd2'] = 'Put the following content in <code>/etc/systemd/system/rhymix-queue.timer</code>';
|
||||
$lang->msg_queue_instructions['systemd3'] = 'Execute the following commands to enable the timer, and monitor your journal to make sure that it is operating as scheduled.';
|
||||
$lang->msg_queue_driver_not_found = 'The selected task queue driver is not supported on this server.';
|
||||
$lang->msg_queue_driver_not_usable = 'The selected task queue driver failed to initialize using the configuration values you entered.';
|
||||
$lang->msg_queue_driver_cannot_be_dummy = 'In otder to use the task queue, you must select a driver other than "Not use"';
|
||||
$lang->msg_queue_invalid_config = 'Missing or invalid configuration for the selected queue driver.';
|
||||
$lang->msg_queue_invalid_interval = 'The calling interval must be between 1 and 10 minutes.';
|
||||
$lang->msg_queue_invalid_process_count = 'The process count must be between 1 and 10.';
|
||||
$lang->msg_queue_invalid_key = 'The webcron auth key must be at least 16 characters long, and only consist of alphanumeric characters.';
|
||||
$lang->autoinstall = 'EasyInstall';
|
||||
$lang->last_week = 'Last Week';
|
||||
$lang->this_week = 'This Week';
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ $lang->subtitle_notification = '알림 설정';
|
|||
$lang->subtitle_security = '보안 설정';
|
||||
$lang->subtitle_advanced = '고급 설정';
|
||||
$lang->subtitle_debug = '디버그 설정';
|
||||
$lang->subtitle_queue = '비동기 작업';
|
||||
$lang->subtitle_seo = 'SEO 설정';
|
||||
$lang->subtitle_etc = '기타';
|
||||
$lang->current_state = '현황';
|
||||
|
|
@ -264,7 +265,7 @@ $lang->about_seo_main_title = '사이트 메인 화면에 표시되는 제목
|
|||
$lang->seo_subpage_title = '서브페이지 제목';
|
||||
$lang->about_seo_subpage_title = '문서 목록, 페이지 등 주요 메뉴를 방문하면 표시되는 제목 형태입니다. 위의 변수들과 함께 <b>$PAGE</b> (페이지)도 사용할 수 있습니다.';
|
||||
$lang->seo_document_title = '개별 문서 페이지 제목';
|
||||
$lang->about_seo_document_title = '게시물을 읽는 화면에서 표시되는 제목 형태입니다. 위에 변수들과 함께 <b>$DOCUMENT_TITLE</b> (문서 제목)도 사용할 수 있습니다.';
|
||||
$lang->about_seo_document_title = '게시물을 읽는 화면에서 표시되는 제목 형태입니다. 위에 변수들과 함께 <b>$CATEGORY</b> (카테고리명), <b>$DOCUMENT_TITLE</b> (문서 제목)도 사용할 수 있습니다.';
|
||||
$lang->site_meta_keywords = 'SEO 키워드';
|
||||
$lang->about_site_meta_keywords = '별도의 키워드를 지정하지 않은 페이지에서는 이 키워드 목록이 표시됩니다.';
|
||||
$lang->site_meta_description = 'SEO 설명';
|
||||
|
|
@ -278,6 +279,36 @@ $lang->og_extract_images_fallback = '사이트 대표 이미지 사용';
|
|||
$lang->og_extract_hashtags = '본문에서 해시태그 추출';
|
||||
$lang->og_use_nick_name = '글 작성자 이름 표시';
|
||||
$lang->og_use_timestamps = '글 작성/수정 시각 표시';
|
||||
$lang->cmd_queue_description = '메일 발송, 푸시알림 등 시간이 오래 걸리거나 외부 서비스와 연동하는 작업을 비동기 처리하여 응답 속도를 개선합니다.<br>실험적인 기능입니다. 호스팅 환경에 따라서는 안정적으로 작동하지 않을 수도 있습니다.';
|
||||
$lang->cmd_queue_enabled = '비동기 작업 사용';
|
||||
$lang->cmd_queue_enabled_help = '체크를 해제하면 더이상 작업을 접수하지 않습니다.';
|
||||
$lang->cmd_queue_driver = '비동기 드라이버';
|
||||
$lang->cmd_queue_driver_help = '비동기 작업을 관리할 방법을 설정합니다. 호스팅 환경과 사이트의 필요에 맞추어 선택하세요.<br>Redis 등 일부 드라이버는 서버에 해당 기능이 설치되어 있어야 사용할 수 있습니다.';
|
||||
$lang->cmd_queue_interval = '호출 간격';
|
||||
$lang->cmd_queue_interval_help = 'crontab, systemd timer, 웹크론 등을 사용하여 일정한 주기로 스크립트를 호출해 주십시오.<br>모든 비동기 작업은 호출 간격과 무관하게 실시간으로 처리되나, 호출 간격이 짧으면 장애 발생시 신속하게 복구됩니다.<br>웹크론 사용시에는 php.ini의 실행 시간 제한을 초과하지 않는 것이 좋습니다.<br>이 서버의 max_execution_time은 %d초로 설정되어 있습니다.';
|
||||
$lang->cmd_queue_process_count = '프로세스 갯수';
|
||||
$lang->cmd_queue_process_count_help = '여러 개의 프로세스를 동시에 실행하여 처리 용량을 늘립니다. 서버 부하가 증가할 수 있습니다.<br>고성능 단독서버가 아니라면 1을 유지하시기 바랍니다.<br>웹크론으로 호출한 경우에는 멀티프로세싱을 지원하지 않습니다.';
|
||||
$lang->cmd_queue_call_script = '작업 처리 스크립트';
|
||||
$lang->cmd_queue_webcron_key = '웹크론 인증키';
|
||||
$lang->cmd_queue_config_keys['host'] = '호스트';
|
||||
$lang->cmd_queue_config_keys['port'] = '포트';
|
||||
$lang->cmd_queue_config_keys['user'] = '아이디';
|
||||
$lang->cmd_queue_config_keys['pass'] = '암호';
|
||||
$lang->cmd_queue_config_keys['dbnum'] = 'DB 번호';
|
||||
$lang->msg_queue_instructions['same_as_php'] = 'PHP를 실행하는 계정과 동일한';
|
||||
$lang->msg_queue_instructions['crontab1'] = '%s 계정으로 서버에 로그인하여 <code>crontab -e</code> 명령을 실행한 후, 아래의 내용을 붙여넣으십시오. (root 권한으로 실행하지 마십시오.)<br>예제의 <code>%s</code> 디렉토리는 로그를 기록할 수 있는 경로로 변경하여 사용하십시오.';
|
||||
$lang->msg_queue_instructions['crontab2'] = '스크립트 호출 간격을 변경할 경우, 설정에 맞추어 crontab 실행 간격도 조절하여야 합니다.';
|
||||
$lang->msg_queue_instructions['webcron'] = '아래의 URL을 1분 간격 또는 아래에서 설정한 호출 간격에 맞추어 GET으로 호출하도록 합니다.<br>웹크론 서비스가 방화벽이나 CDN 등에 의해 차단되지 않도록 주의하고, 정상적으로 호출되는지 서버 로그를 확인하십시오.';
|
||||
$lang->msg_queue_instructions['systemd1'] = '<code>/etc/systemd/system/rhymix-queue.service</code> 파일에 아래와 같은 내용을 넣습니다.';
|
||||
$lang->msg_queue_instructions['systemd2'] = '<code>/etc/systemd/system/rhymix-queue.timer</code> 파일에 아래와 같은 내용을 넣습니다.';
|
||||
$lang->msg_queue_instructions['systemd3'] = '아래의 명령을 실행하여 타이머를 활성화하고, 정상 작동하는지 모니터링하십시오.';
|
||||
$lang->msg_queue_driver_not_found = '이 서버에서 지원하지 않는 비동기 드라이버입니다.';
|
||||
$lang->msg_queue_driver_not_usable = '입력하신 정보로 비동기 드라이버와 연동하는 데 실패했습니다. 드라이버 설정을 확인해 주십시오.';
|
||||
$lang->msg_queue_driver_cannot_be_dummy = '비동기 작업을 사용하려면 "미사용" 이외의 드라이버를 선택해야 합니다.';
|
||||
$lang->msg_queue_invalid_config = '비동기 드라이버의 필수 설정이 누락되었습니다.';
|
||||
$lang->msg_queue_invalid_interval = '호출 간격은 1~10분 이내여야 합니다.';
|
||||
$lang->msg_queue_invalid_process_count = '프로세스 갯수는 1~10개 이내여야 합니다.';
|
||||
$lang->msg_queue_invalid_key = '웹크론 인증키는 16자 이상으로, 알파벳 대소문자와 숫자만으로 이루어져야 합니다.';
|
||||
$lang->autoinstall = '쉬운 설치';
|
||||
$lang->last_week = '지난주';
|
||||
$lang->this_week = '이번주';
|
||||
|
|
|
|||
|
|
@ -12,5 +12,6 @@
|
|||
<li class="x_active"|cond="$act == 'dispAdminConfigAdvanced'"><a href="{getUrl('', 'module', 'admin', 'act', 'dispAdminConfigAdvanced')}">{$lang->subtitle_advanced}</a></li>
|
||||
<li class="x_active"|cond="$act == 'dispAdminConfigDebug'"><a href="{getUrl('', 'module', 'admin', 'act', 'dispAdminConfigDebug')}">{$lang->subtitle_debug}</a></li>
|
||||
<li class="x_active"|cond="$act == 'dispAdminConfigSEO'"><a href="{getUrl('', 'module', 'admin', 'act', 'dispAdminConfigSEO')}">{$lang->subtitle_seo}</a></li>
|
||||
<li class="x_active"|cond="$act == 'dispAdminConfigQueue'"><a href="{getUrl('', 'module', 'admin', 'act', 'dispAdminConfigQueue')}">{$lang->subtitle_queue}</a></li>
|
||||
<li class="x_active"|cond="$act == 'dispAdminConfigSitelock'"><a href="{getUrl('', 'module', 'admin', 'act', 'dispAdminConfigSitelock')}">{$lang->subtitle_sitelock}</a></li>
|
||||
</ul>
|
||||
|
|
|
|||
195
modules/admin/tpl/config_queue.html
Normal file
195
modules/admin/tpl/config_queue.html
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
<config autoescape="on" />
|
||||
|
||||
<include target="config_header.html" />
|
||||
<load target="css/queue_config.scss" />
|
||||
<load target="js/queue_config.js" />
|
||||
|
||||
<div class="message">
|
||||
<p>{$lang->cmd_queue_description}</p>
|
||||
</div>
|
||||
|
||||
<div cond="!empty($XE_VALIDATOR_MESSAGE) && $XE_VALIDATOR_ID == 'modules/admin/tpl/config_queue/1'" class="message {$XE_VALIDATOR_MESSAGE_TYPE}">
|
||||
<p>{$XE_VALIDATOR_MESSAGE}</p>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var queue_drivers = {json_encode($queue_drivers)|noescape};
|
||||
</script>
|
||||
|
||||
<form action="./" method="post" class="x_form-horizontal">
|
||||
<input type="hidden" name="module" value="admin" />
|
||||
<input type="hidden" name="act" value="procAdminUpdateQueue" />
|
||||
<input type="hidden" name="xe_validator_id" value="modules/admin/tpl/config_queue/1" />
|
||||
|
||||
<section class="section">
|
||||
|
||||
<h2>{$lang->subtitle_queue}</h2>
|
||||
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label">{$lang->cmd_queue_enabled}</label>
|
||||
<div class="x_controls">
|
||||
<label for="queue_enabled" class="x_inline">
|
||||
<input type="checkbox" name="queue_enabled" id="queue_enabled" value="Y" checked="checked"|cond="config('queue.enabled')" />
|
||||
{$lang->cmd_queue_enabled}
|
||||
</label>
|
||||
<br>
|
||||
<p class="x_help-block">{$lang->cmd_queue_enabled_help}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label" for="queue_driver">{$lang->cmd_queue_driver}</label>
|
||||
<div class="x_controls">
|
||||
<select name="queue_driver" id="queue_driver">
|
||||
<!--@foreach($queue_drivers as $driver_name => $driver_definition)-->
|
||||
<option value="{$driver_name}" selected="selected"|cond="$queue_driver === $driver_name">{$driver_name === 'dummy' ? $lang->notuse : $driver_definition['name']}</option>
|
||||
<!--@end-->
|
||||
</select>
|
||||
<p class="x_help-block">{$lang->cmd_queue_driver_help}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--@foreach($queue_drivers as $driver_name => $driver_definition)-->
|
||||
|
||||
{@ $conf_names = array_merge($driver_definition['required'], $driver_definition['optional'])}
|
||||
|
||||
<!--@foreach($conf_names as $conf_name)-->
|
||||
|
||||
{@ $conf_value = escape(config("queue.$driver_name.$conf_name"))}
|
||||
{@ $text_keys = ['host', 'user']}
|
||||
{@ $number_keys = ['port', 'dbnum']}
|
||||
{@ $password_keys = ['pass']}
|
||||
|
||||
<!--@if(in_array($conf_name, $text_keys))-->
|
||||
<div class="x_control-group hidden-by-default show-for-{$driver_name}">
|
||||
<label class="x_control-label" for="queue_{$driver_name}_{$conf_name}">{$lang->cmd_queue_config_keys[$conf_name]}</label>
|
||||
<div class="x_controls">
|
||||
<input type="text" name="queue_{$driver_name}_{$conf_name}" id="queue_{$driver_name}_{$conf_name}" value="{$conf_value}" />
|
||||
</div>
|
||||
</div>
|
||||
<!--@end-->
|
||||
|
||||
<!--@if(in_array($conf_name, $number_keys))-->
|
||||
<div class="x_control-group hidden-by-default show-for-{$driver_name}">
|
||||
<label class="x_control-label" for="queue_{$driver_name}_{$conf_name}">{$lang->cmd_queue_config_keys[$conf_name]}</label>
|
||||
<div class="x_controls">
|
||||
<input type="number" name="queue_{$driver_name}_{$conf_name}" id="queue_{$driver_name}_{$conf_name}" value="{$conf_value}" />
|
||||
</div>
|
||||
</div>
|
||||
<!--@end-->
|
||||
|
||||
<!--@if(in_array($conf_name, $password_keys))-->
|
||||
<div class="x_control-group hidden-by-default show-for-{$driver_name}">
|
||||
<label class="x_control-label" for="queue_{$driver_name}_{$conf_name}">{$lang->cmd_queue_config_keys[$conf_name]}</label>
|
||||
<div class="x_controls">
|
||||
<input type="password" name="queue_{$driver_name}_{$conf_name}" id="queue_{$driver_name}_{$conf_name}" value="{$conf_value}" autocomplete="new-password" />
|
||||
</div>
|
||||
</div>
|
||||
<!--@end-->
|
||||
|
||||
<!--@end-->
|
||||
|
||||
<!--@end-->
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
<h2>{$lang->cmd_queue_call_script}</h2>
|
||||
|
||||
<div class="queue-script-setup">
|
||||
<ul class="qss-tabs x_nav x_nav-tabs">
|
||||
<li class="x_active"><a href="#" data-value="crontab">crontab</a></li>
|
||||
<li><a href="#" data-value="webcron">webcron</a></li>
|
||||
<li><a href="#" data-value="systemd-timer">systemd timer</a></li>
|
||||
</ul>
|
||||
<div class="qss-content crontab active">
|
||||
{@
|
||||
if (function_exists('posix_getpwuid') && function_exists('posix_getuid')):
|
||||
$user_info = posix_getpwuid(posix_getuid());
|
||||
if (!empty($user_info['dir'])):
|
||||
$user_info['dir'] .= DIRECTORY_SEPARATOR;
|
||||
endif;
|
||||
else:
|
||||
$user_info = [];
|
||||
$user_info['name'] = $lang->msg_queue_instructions['same_as_php'];
|
||||
endif;
|
||||
}
|
||||
<p class="qss-instruction">
|
||||
{sprintf($lang->msg_queue_instructions['crontab1'], $user_info['name'] ?? 'PHP', $user_info['dir'] . 'logs')|noescape}
|
||||
</p>
|
||||
<pre><code>* * * * * php {\RX_BASEDIR}index.php common.cron >> {$user_info['dir']}logs{\DIRECTORY_SEPARATOR}queue.log 2>&1</code></pre>
|
||||
<p class="qss-instruction">
|
||||
{$lang->msg_queue_instructions['crontab2']|noescape}
|
||||
</p>
|
||||
</div>
|
||||
<div class="qss-content webcron">
|
||||
<p class="qss-instruction">
|
||||
{$lang->msg_queue_instructions['webcron']|noescape}
|
||||
</p>
|
||||
<pre><code class="webcron-url">{getFullUrl('')}common/scripts/cron.php?key={config('queue.key')}</code></pre>
|
||||
</div>
|
||||
<div class="qss-content systemd-timer">
|
||||
<p class="qss-instruction">
|
||||
{$lang->msg_queue_instructions['systemd1']|noescape}
|
||||
</p>
|
||||
<pre><code>[Unit]
|
||||
Description=Rhymix Queue Service
|
||||
|
||||
[Service]
|
||||
ExecStart=php {\RX_BASEDIR}index.php common.cron
|
||||
User={$user_info['name']}</code></pre>
|
||||
<p class="qss-instruction">
|
||||
{$lang->msg_queue_instructions['systemd2']|noescape}
|
||||
</p>
|
||||
<pre><code>[Unit]
|
||||
Description=Rhymix Queue Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* *:*:00
|
||||
Unit=rhymix-queue.service
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target</code></pre>
|
||||
<p class="qss-instruction">
|
||||
{$lang->msg_queue_instructions['systemd3']|noescape}
|
||||
</p>
|
||||
<pre><code>systemctl daemon-reload
|
||||
systemctl start rhymix-queue.timer
|
||||
systemctl enable rhymix-queue.timer</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label" for="queue_key">{$lang->cmd_queue_webcron_key}</label>
|
||||
<div class="x_controls">
|
||||
<input type="text" class="x_full-width" name="queue_key" id="queue_key" value="{config('queue.key')}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label" for="queue_interval">{$lang->cmd_queue_interval}</label>
|
||||
<div class="x_controls">
|
||||
<input type="number" name="queue_interval" id="queue_interval" min="1" max="10" value="{config('queue.interval') ?: 1}" />
|
||||
<span class="x_inline">{$lang->unit_min}</span>
|
||||
<br>
|
||||
<p class="x_help-block" style="margin-top:10px">{sprintf($lang->cmd_queue_interval_help, ini_get('max_execution_time'))|noescape}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label" for="queue_process_count">{$lang->cmd_queue_process_count}</label>
|
||||
<div class="x_controls">
|
||||
<input type="number" name="queue_process_count" id="queue_process_count" min="1" max="10" value="{config('queue.process_count') ?: 1}" />
|
||||
<p class="x_help-block">{$lang->cmd_queue_process_count_help}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<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>
|
||||
27
modules/admin/tpl/css/queue_config.scss
Normal file
27
modules/admin/tpl/css/queue_config.scss
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
.queue-script-setup {
|
||||
.qss-content {
|
||||
display: none;
|
||||
&.active {
|
||||
display: block;
|
||||
border: 1px solid #ddd;
|
||||
border-top: 0;
|
||||
margin-top: -20px;
|
||||
padding: 20px 12px 10px 12px;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.qss-instruction {
|
||||
margin-bottom: 10px;
|
||||
code {
|
||||
color: #333;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
padding: 0 1px;
|
||||
}
|
||||
}
|
||||
.pre {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
modules/admin/tpl/js/queue_config.js
Normal file
33
modules/admin/tpl/js/queue_config.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
(function($) {
|
||||
$(function() {
|
||||
|
||||
$("#queue_driver").on("change", function() {
|
||||
const selected_driver = $(this).val();
|
||||
$(this).parents("section").find("div.x_control-group.hidden-by-default, p.x_help-block.hidden-by-default").each(function() {
|
||||
if ($(this).hasClass("show-for-" + selected_driver)) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
}).triggerHandler("change");
|
||||
|
||||
$("#queue_key").on('change keyup paste', function() {
|
||||
const key = encodeURIComponent(String($(this).val()));
|
||||
$('.webcron-url').text($('.webcron-url').text().replace(/\?key=[a-zA-Z0-9]+/g, '?key=' + key));
|
||||
});
|
||||
|
||||
const qss = $('.queue-script-setup');
|
||||
const qss_tabs = qss.find('.qss-tabs');
|
||||
const qss_content = qss.find('.qss-content');
|
||||
qss_tabs.on('click', 'a', function(event) {
|
||||
const selected_tab = $(this).data('value');
|
||||
qss_tabs.find('li').removeClass('x_active');
|
||||
$(this).parent().addClass('x_active');
|
||||
qss_content.removeClass('active');
|
||||
qss_content.filter('.' + selected_tab).addClass('active');
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
});
|
||||
})(jQuery);
|
||||
|
|
@ -63,6 +63,17 @@ class BoardController extends Board
|
|||
throw new Rhymix\Framework\Exception('msg_content_too_long');
|
||||
}
|
||||
|
||||
// Return error if content conains excessively large data URLs.
|
||||
$inline_data_url_limit = ($this->module_info->inline_data_url_limit ?: 64) * 1024;
|
||||
preg_match_all('!src="\s*(data:[^,]*,[a-z0-9+/=%$!._-]+)!i', (string)$obj->content, $matches);
|
||||
foreach ($matches[1] as $match)
|
||||
{
|
||||
if (strlen($match) > $inline_data_url_limit)
|
||||
{
|
||||
throw new Rhymix\Framework\Exception('msg_data_url_restricted');
|
||||
}
|
||||
}
|
||||
|
||||
// Check category
|
||||
$category_list = DocumentModel::getCategoryList($this->module_srl);
|
||||
if (count($category_list) > 0)
|
||||
|
|
@ -472,6 +483,17 @@ class BoardController extends Board
|
|||
throw new Rhymix\Framework\Exception('msg_content_too_long');
|
||||
}
|
||||
|
||||
// Return error if content conains excessively large data URLs.
|
||||
$inline_data_url_limit = ($this->module_info->inline_data_url_limit ?: 64) * 1024;
|
||||
preg_match_all('!src="\s*(data:[^,]*,[a-z0-9+/=%$!._-]+)!i', (string)$obj->content, $matches);
|
||||
foreach ($matches[1] as $match)
|
||||
{
|
||||
if (strlen($match) > $inline_data_url_limit)
|
||||
{
|
||||
throw new Rhymix\Framework\Exception('msg_data_url_restricted');
|
||||
}
|
||||
}
|
||||
|
||||
if(!$this->module_info->use_status) $this->module_info->use_status = 'PUBLIC';
|
||||
if(!is_array($this->module_info->use_status))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -200,6 +200,12 @@ class BoardView extends Board
|
|||
// list
|
||||
$this->dispBoardContentList();
|
||||
|
||||
// Board features
|
||||
$oDocument = Context::get('oDocument');
|
||||
$document_module_srl = ($oDocument && $oDocument->isExists()) ? $oDocument->get('module_srl') : $this->module_srl;
|
||||
$board_features = Rhymix\Modules\Board\Models\Features::fromModuleInfo($this->module_info, $document_module_srl);
|
||||
Context::set('board_features', $board_features);
|
||||
|
||||
/**
|
||||
* add javascript filters
|
||||
**/
|
||||
|
|
@ -226,7 +232,23 @@ class BoardView extends Board
|
|||
return;
|
||||
}
|
||||
|
||||
Context::set('category_list', DocumentModel::getCategoryList($this->module_srl));
|
||||
// Get category list for documents belong to other modules. (i.e. submodule in combined board)
|
||||
if (empty($this->include_modules))
|
||||
{
|
||||
$category_list = DocumentModel::getCategoryList($this->module_srl);
|
||||
}
|
||||
else
|
||||
{
|
||||
$category_list = DocumentModel::getCategoryList($this->module_srl);
|
||||
foreach ($this->include_modules as $module_srl)
|
||||
{
|
||||
if ($module_srl != $this->module_srl)
|
||||
{
|
||||
$category_list += DocumentModel::getCategoryList($module_srl);
|
||||
}
|
||||
}
|
||||
}
|
||||
Context::set('category_list', $category_list);
|
||||
|
||||
$oSecurity = new Security();
|
||||
$oSecurity->encodeHTML('category_list.', 'category_list.childs.');
|
||||
|
|
@ -323,11 +345,13 @@ class BoardView extends Board
|
|||
Context::setCanonicalURL($oDocument->getPermanentUrl());
|
||||
$seo_title = config('seo.document_title') ?: '$SITE_TITLE - $DOCUMENT_TITLE';
|
||||
$seo_title = Context::replaceUserLang($seo_title);
|
||||
$category_list = Context::get('category_list');
|
||||
Context::setBrowserTitle($seo_title, array(
|
||||
'site_title' => Context::getSiteTitle(),
|
||||
'site_subtitle' => Context::getSiteSubtitle(),
|
||||
'subpage_title' => $this->module_info->browser_title,
|
||||
'document_title' => $oDocument->getTitleText(),
|
||||
'category' => ($oDocument->get('category_srl') && isset($category_list[$oDocument->get('category_srl')])) ? $category_list[$oDocument->get('category_srl')]->title : '',
|
||||
'page' => Context::get('page') ?: 1,
|
||||
));
|
||||
|
||||
|
|
@ -548,7 +572,7 @@ class BoardView extends Board
|
|||
|
||||
// set the current page of documents
|
||||
$document_srl = Context::get('document_srl');
|
||||
if($document_srl && $this->module_info->skip_bottom_list_for_robot === 'Y' && isCrawler())
|
||||
if($document_srl && $this->module_info->skip_bottom_list_for_robot !== 'N' && isCrawler())
|
||||
{
|
||||
Context::set('page', $args->page = null);
|
||||
}
|
||||
|
|
@ -1126,6 +1150,17 @@ class BoardView extends Board
|
|||
return $this->dispBoardMessage('msg_not_founded', 404);
|
||||
}
|
||||
|
||||
// Check thread depth
|
||||
$comment_config = ModuleModel::getModulePartConfig('comment', $this->module_srl);
|
||||
if (isset($comment_config->max_thread_depth) && $comment_config->max_thread_depth > 0)
|
||||
{
|
||||
$parent_depth = CommentModel::getCommentDepth($parent_srl);
|
||||
if ($parent_depth + 2 > $comment_config->max_thread_depth)
|
||||
{
|
||||
return $this->dispBoardMessage('msg_exceeds_max_thread_depth');
|
||||
}
|
||||
}
|
||||
|
||||
// Check allow comment
|
||||
$oDocument = DocumentModel::getDocument($oSourceComment->get('document_srl'));
|
||||
if(!$oDocument->allowComment())
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
<actions>
|
||||
<action name="dispBoardContent" type="view" permission="list" standalone="false" index="true">
|
||||
<route route="$document_srl:int" priority="100" />
|
||||
<route route="$document_srl:int/" priority="100" />
|
||||
<route route="$document_srl:int/comment/$comment_srl:int" priority="100" />
|
||||
<route route="$document_srl:int/page/$page:int" priority="100" />
|
||||
<route route="category/$category:int/search/$search_target:word/$search_keyword:any" priority="60" />
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ $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->admin_mail = 'Administrator\'s Mail';
|
||||
|
|
@ -36,7 +37,7 @@ $lang->about_use_anonymous_part2 = 'It is more useful if you also hide the nickn
|
|||
$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_board = 'This module is for creating and managing boards.';
|
||||
$lang->about_consultation = 'Non-administrator members would see their own articles. Non-members would not be able to write articles when using consultation.';
|
||||
$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_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';
|
||||
|
|
@ -63,8 +64,10 @@ $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->about_document_length_limit = 'Restrict documents that are too large. This limit may be triggered by copying and pasting a web page that contains a lot of unnecessary tags.';
|
||||
$lang->about_comment_length_limit = 'Restrict comments that are too large.';
|
||||
$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_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->about_filter_specialchars = 'Prevent use of excessive Unicode accents, RLO characters, and other symbols that hinder readability.';
|
||||
|
|
@ -83,6 +86,7 @@ $lang->msg_protect_regdate_document = 'You cannot update or delete a document af
|
|||
$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_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->comment_delete_message = 'Leave Placeholder for Deleted Comment';
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ $lang->last_post = '최종 글';
|
|||
$lang->board_management = '게시판 관리';
|
||||
$lang->search_result = '검색결과';
|
||||
$lang->consultation = '상담 기능';
|
||||
$lang->use_consultation = '상담 기능 사용';
|
||||
$lang->secret = '비밀글 기능';
|
||||
$lang->thisissecret = '비밀글입니다.';
|
||||
$lang->admin_mail = '관리자 메일';
|
||||
|
|
@ -37,7 +38,7 @@ $lang->about_use_anonymous_part2 = '스킨 설정에서 글쓴이 정보 등을
|
|||
$lang->about_anonymous_except_admin = '관리권한이 있는 회원은 익명으로 표시되지 않도록 합니다.';
|
||||
$lang->about_anonymous_name = '익명 기능을 사용할 때 표시할 익명 닉네임을 정할 수 있습니다.<br><b>$NUM</b>을 사용하면 회원마다 고유한 난수를 부여할 수 있습니다. (예: 익명_$NUM → 익명_12345678)<br><b>$DAILYNUM</b>을 사용하면 매일 난수가 변경되고, <b>$DOCNUM</b>을 사용하면 문서마다 변경됩니다.<br><b>$DOCDAILYNUM</b>을 사용하면 문서마다 각각, 그리고 매일 변경됩니다.<br>각 변수 뒤에 <strong>$DAILYNUM:5</strong>와 같이 1~8 숫자를 붙여 자릿수를 조정할 수 있습니다.<br><strong>NUM</strong> 대신 <strong>STR</strong>를 사용하면 일부 알파벳이 포함된 16진수를 사용합니다.';
|
||||
$lang->about_board = '게시판을 생성하고 관리할 수 있습니다.';
|
||||
$lang->about_consultation = '상담 기능은 관리권한이 없는 회원은 자신이 쓴 글만 보이도록 하는 기능입니다. 단 상담기능 사용시 비회원 글쓰기는 자동으로 금지됩니다.';
|
||||
$lang->about_consultation = '관리 권한이 없는 회원은 자신이 쓴 글만 보이도록 합니다.<br>상담 기능 사용시 비회원 글쓰기는 금지됩니다.';
|
||||
$lang->about_secret = '게시판 및 댓글의 비밀글 기능을 사용할 수 있도록 합니다.';
|
||||
$lang->about_admin_mail = '글이나 댓글이 등록될때 등록된 메일주소로 메일이 발송됩니다. 콤마(,)로 연결시 다수의 메일주소로 발송할 수 있습니다.';
|
||||
$lang->about_list_config = '게시판의 목록형식 사용시 원하는 항목들로 배치를 할 수 있습니다. 단 스킨에서 지원하지 않는 경우 불가능합니다. 대상항목/ 표시항목의 항목을 더블클릭하면 추가/ 제거가 됩니다.';
|
||||
|
|
@ -64,8 +65,10 @@ $lang->protect_regdate = '기간 제한 기능';
|
|||
$lang->filter_specialchars = '유니코드 특수문자 오남용 금지';
|
||||
$lang->document_length_limit = '문서 길이 제한';
|
||||
$lang->comment_length_limit = '댓글 길이 제한';
|
||||
$lang->about_document_length_limit = '지나치게 용량이 큰 글을 작성하지 못하도록 합니다. 지저분한 태그가 많이 붙은 글을 붙여넣기하면 제한을 초과할 수도 있습니다.';
|
||||
$lang->about_comment_length_limit = '지나치게 용량이 큰 댓글을 작성하지 못하도록 합니다.';
|
||||
$lang->inline_data_url_limit = 'Data URL 제한';
|
||||
$lang->about_document_length_limit = '지나치게 용량이 큰 글을 작성하지 못하도록 합니다. 지저분한 태그가 많이 붙은 글을 붙여넣으면 제한을 초과할 수도 있습니다.<br>관리자에게는 적용되지 않습니다.';
|
||||
$lang->about_comment_length_limit = '지나치게 용량이 큰 댓글을 작성하지 못하도록 합니다.<br>관리자에게는 적용되지 않습니다.';
|
||||
$lang->about_inline_data_url_limit = 'data: URL을 사용하여 첨부 제한을 우회하거나 처리 장애를 일으키는 내용을 제한합니다.<br>이 설정은 관리자에게도 적용됩니다.';
|
||||
$lang->update_order_on_comment = '댓글 작성시 글 수정 시각 갱신';
|
||||
$lang->about_update_order_on_comment = '댓글이 작성되면 해당 글의 수정 시각을 갱신합니다. 포럼형 게시판, 최근 댓글 표시 기능 등에 필요합니다.';
|
||||
$lang->about_filter_specialchars = '가독성에 악영향을 주는 과도한 유니코드 악센트 기호의 조합, RLO 문자 등의 사용을 금지합니다.';
|
||||
|
|
@ -82,8 +85,9 @@ $lang->msg_protect_regdate_document = '%d일 이상 지난 글은 수정 또는
|
|||
$lang->msg_protect_regdate_comment = '%d일 이상 지난 댓글은 수정 또는 삭제할 수 없습니다.';
|
||||
$lang->msg_dont_have_update_log = '업데이트 로그가 기록되어 있지 않은 게시글입니다.';
|
||||
$lang->msg_content_too_long = '내용이 너무 깁니다.';
|
||||
$lang->msg_data_url_restricted = 'Data URL 분량이 너무 많아서 작성이 제한되었습니다.';
|
||||
$lang->original_letter = '원본글';
|
||||
$lang->msg_warning_update_log = '<span class="x_label x_label-important">주의!</span> 사용시 디비가 많이 늘어날 수 있습니다.';
|
||||
$lang->msg_warning_update_log = '<span class="x_label x_label-important">주의!</span> 사용시 DB 용량이 많이 늘어날 수 있습니다.';
|
||||
$lang->reason_update = '수정한 이유';
|
||||
$lang->msg_no_update_id = '업데이트 고유 번호는 필수입니다.';
|
||||
$lang->msg_no_update_log = '업데이트 로그가 존재하지 않습니다.';
|
||||
|
|
|
|||
127
modules/board/models/Features.php
Normal file
127
modules/board/models/Features.php
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Modules\Board\Models;
|
||||
|
||||
use ModuleModel;
|
||||
|
||||
/**
|
||||
* The board features model.
|
||||
*
|
||||
* This is a new data structure that summarizes currently enabled features
|
||||
* of a board in a format that is easier to use in skins.
|
||||
* It also uses words that are more accurate than old XE variable names,
|
||||
* such as 'vote down' instead of 'blame' and 'report' instead of 'declare'.
|
||||
*/
|
||||
class Features
|
||||
{
|
||||
/**
|
||||
* Public properties.
|
||||
*/
|
||||
public $document;
|
||||
public $comment;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->document = new \stdClass;
|
||||
$this->comment = new \stdClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get board features from module_srl.
|
||||
*
|
||||
* @param int $module_srl
|
||||
* @param ?int $document_module_srl
|
||||
* @return self
|
||||
*/
|
||||
public static function fromModuleSrl(int $module_srl, ?int $document_module_srl = null): self
|
||||
{
|
||||
$module_info = ModuleModel::getModuleInfoByModuleSrl($module_srl) ?: new \stdClass;
|
||||
return self::fromModuleInfo($module_info, $document_module_srl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get board features from an already created module info object.
|
||||
*
|
||||
* @param object $module_info
|
||||
* @param ?int $document_module_srl
|
||||
* @return self
|
||||
*/
|
||||
public static function fromModuleInfo(object $module_info, ?int $document_module_srl = null): self
|
||||
{
|
||||
if (!$document_module_srl)
|
||||
{
|
||||
$document_module_srl = $module_info->module_srl;
|
||||
}
|
||||
$document_config = ModuleModel::getModulePartConfig('document', $document_module_srl);
|
||||
$comment_config = ModuleModel::getModulePartConfig('comment', $document_module_srl);
|
||||
$features = new self;
|
||||
|
||||
// Document features
|
||||
$features->document->vote_up = ($document_config->use_vote_up ?? 'Y') !== 'N';
|
||||
$features->document->vote_down = ($document_config->use_vote_down ?? 'Y') !== 'N';
|
||||
$features->document->vote_log = ($document_config->use_vote_up ?? 'Y') === 'S' || ($document_config->use_vote_down ?? 'Y') === 'S';
|
||||
if (isset($document_config->allow_vote_cancel))
|
||||
{
|
||||
$features->document->cancel_vote = $document_config->allow_vote_cancel === 'Y';
|
||||
}
|
||||
else
|
||||
{
|
||||
$features->document->cancel_vote = ($module_info->cancel_vote ?? 'N') === 'Y';
|
||||
}
|
||||
if (isset($document_config->allow_vote_non_member))
|
||||
{
|
||||
$features->document->non_member_vote = $document_config->allow_vote_non_member === 'Y';
|
||||
}
|
||||
else
|
||||
{
|
||||
$features->document->non_member_vote = ($module_info->non_login_vote ?? 'N') === 'Y';
|
||||
}
|
||||
$features->document->report = true;
|
||||
if (isset($document_config->allow_declare_cancel))
|
||||
{
|
||||
$features->document->cancel_report = $document_config->allow_declare_cancel === 'Y';
|
||||
}
|
||||
else
|
||||
{
|
||||
$features->document->cancel_report = ($module_info->cancel_vote ?? 'N') === 'Y';
|
||||
}
|
||||
$features->document->history = ($document_config->use_history ?? 'N') === 'Y';
|
||||
|
||||
// Comment features
|
||||
$features->comment->vote_up = ($comment_config->use_vote_up ?? 'Y') !== 'N';
|
||||
$features->comment->vote_down = ($comment_config->use_vote_down ?? 'Y') !== 'N';
|
||||
$features->comment->vote_log = ($comment_config->use_vote_up ?? 'Y') === 'S' || ($comment_config->use_vote_down ?? 'Y') === 'S';
|
||||
if (isset($comment_config->allow_vote_cancel))
|
||||
{
|
||||
$features->comment->cancel_vote = $comment_config->allow_vote_cancel === 'Y';
|
||||
}
|
||||
else
|
||||
{
|
||||
$features->comment->cancel_vote = ($module_info->cancel_vote ?? 'N') === 'Y';
|
||||
}
|
||||
if (isset($comment_config->allow_vote_non_member))
|
||||
{
|
||||
$features->comment->non_member_vote = $comment_config->allow_vote_non_member === 'Y';
|
||||
}
|
||||
else
|
||||
{
|
||||
$features->comment->non_member_vote = ($module_info->non_login_vote ?? 'N') === 'Y';
|
||||
}
|
||||
$features->comment->report = true;
|
||||
if (isset($comment_config->allow_declare_cancel))
|
||||
{
|
||||
$features->comment->cancel_report = $comment_config->allow_declare_cancel === 'Y';
|
||||
}
|
||||
else
|
||||
{
|
||||
$features->comment->cancel_report = ($module_info->cancel_vote ?? 'N') === 'Y';
|
||||
}
|
||||
$features->comment->max_thread_depth = $comment_config->max_thread_depth ?? 0;
|
||||
$features->comment->default_page = $comment_config->default_page ?? 'last';
|
||||
|
||||
return $features;
|
||||
}
|
||||
}
|
||||
|
|
@ -38,12 +38,20 @@
|
|||
</ul>
|
||||
</div>
|
||||
<p class="action" cond="!$comment->isDeleted()">
|
||||
<!--@if($board_features->comment->vote_log)-->
|
||||
<a href="{getUrl('', 'mid', $mid, 'act', 'dispBoardVoteLog', 'target_srl', $comment->comment_srl, 'target', 'comment')}">{$lang->cmd_comment_vote_user}</a>
|
||||
<!--@endif-->
|
||||
<!--@if($board_features->comment->vote_up)-->
|
||||
<a cond="$comment->getMyVote() === false || $comment->getMyVote() < 0" href="#" onclick="doCallModuleAction('comment','procCommentVoteUp','{$comment->comment_srl}');return false;" class="voted"><i class="xi-thumbs-up"></i>{$lang->cmd_vote}{$comment->get('voted_count')}</a>
|
||||
<a cond="$comment->getMyVote() > 0" href="#" onclick="doCallModuleAction('comment','procCommentVoteUpCancel','{$comment->comment_srl}');return false;" class="voted"><i class="xi-thumbs-up"></i>{$lang->cmd_vote}{$comment->get('voted_count')}</a>
|
||||
<!--@endif-->
|
||||
<!--@if($board_features->comment->vote_down)-->
|
||||
<a cond="$comment->getMyVote() === false || $comment->getMyVote() > 0" href="#" onclick="doCallModuleAction('comment','procCommentVoteDown','{$comment->comment_srl}');return false;" class="voted"><i class="xi-thumbs-up"></i>{$lang->cmd_vote_down}{$comment->get('blamed_count')}</a>
|
||||
<a cond="$comment->getMyVote() < 0" href="#" onclick="doCallModuleAction('comment','procCommentVoteDownCancel','{$comment->comment_srl}');return false;" class="voted"><i class="xi-thumbs-up"></i>{$lang->cmd_vote_down}{$comment->get('blamed_count')}</a>
|
||||
<!--@endif-->
|
||||
<!--@if(!$board_features->comment->max_thread_depth || $comment->depth + 1 < $board_features->comment->max_thread_depth)-->
|
||||
<a cond="$oDocument->allowComment()" href="{getUrl('', 'mid', $mid, 'act', 'dispBoardReplyComment', 'comment_srl', $comment->comment_srl)}" class="reply"><i class="xi-reply"></i> {$lang->cmd_reply}</a>
|
||||
<!--@endif-->
|
||||
<a cond="$comment->isGranted()||!$comment->get('member_srl')" href="{getUrl('', 'mid', $mid, 'act', 'dispBoardModifyComment', 'comment_srl', $comment->comment_srl)}" class="modify"><i class="xi-eraser"></i> {$lang->cmd_modify}</a>
|
||||
<a cond="$comment->isGranted()||!$comment->get('member_srl')" href="{getUrl('', 'mid', $mid, 'act', 'dispBoardDeleteComment', 'comment_srl', $comment->comment_srl)}" class="delete"><i class="xi-trash"></i> {$lang->cmd_delete}</a>
|
||||
<a cond="$is_logged" class="comment_{$comment->comment_srl} this" href="#popup_menu_area" onclick="return false">{$lang->cmd_comment_do}</a>
|
||||
|
|
|
|||
|
|
@ -83,11 +83,11 @@
|
|||
</div>
|
||||
<div class="vote">
|
||||
<ul>
|
||||
<li>
|
||||
<li cond="$board_features->document->vote_up">
|
||||
<a cond="$oDocument->getMyVote() === false || $oDocument->getMyVote() < 0" href="#" onclick="doCallModuleAction('document','procDocumentVoteUp','{$oDocument->document_srl}');return false;" class="voted"> <i class="xi-thumbs-up"></i><br>{$lang->cmd_vote} {$oDocument->get('voted_count')}</a>
|
||||
<a cond="$oDocument->getMyVote() > 0" href="#" onclick="doCallModuleAction('document','procDocumentVoteUpCancel','{$oDocument->document_srl}');return false;" class="voted"> <i class="xi-thumbs-up"></i><br>{$lang->cmd_vote} {$oDocument->get('voted_count')}</a>
|
||||
</li>
|
||||
<li>
|
||||
<li cond="$board_features->document->vote_down">
|
||||
<a cond="$oDocument->getMyVote() === false || $oDocument->getMyVote() > 0" href="#" onclick="doCallModuleAction('document','procDocumentVoteDown','{$oDocument->document_srl}');return false;" class="voted"> <i class="xi-thumbs-down"></i><br>{$lang->cmd_vote_down} {$oDocument->get('blamed_count')}</a>
|
||||
<a cond="$oDocument->getMyVote() < 0" href="#" onclick="doCallModuleAction('document','procDocumentVoteDownCancel','{$oDocument->document_srl}');return false;" class="voted"> <i class="xi-thumbs-down"></i><br>{$lang->cmd_vote_down} {$oDocument->get('blamed_count')}</a>
|
||||
</li>
|
||||
|
|
@ -114,8 +114,12 @@
|
|||
<div cond="$oDocument->getSignature()" class="tx">{$oDocument->getSignature()}</div>
|
||||
</div>
|
||||
<div class="btnArea">
|
||||
<!--@if($board_features->document->vote_log)-->
|
||||
<a class="btn" href="{getUrl('', 'mid', $mid, 'act', 'dispBoardVoteLog', 'target_srl', $oDocument->document_srl, 'target', 'document')}"><i class="xi-list-ul"></i>{$lang->cmd_document_vote_user}</a>
|
||||
<!--@endif-->
|
||||
<!--@if($board_features->document->history)-->
|
||||
<a cond="$update_view" class="btn" href="{getUrl('', 'mid', $mid, 'act', 'dispBoardUpdateLog', 'document_srl', $oDocument->document_srl)}"><i class="xi-list-ul"></i>{$lang->update_log}</a>
|
||||
<!--@endif-->
|
||||
<a cond="$oDocument->isEditable()" class="btn" href="{getUrl('', 'mid', $mid, 'act', 'dispBoardWrite', 'document_srl', $oDocument->document_srl)}"><i class="xi-eraser"></i>{$lang->cmd_modify}</a>
|
||||
<a cond="$oDocument->isEditable()" class="btn" href="{getUrl('', 'mid', $mid, 'act', 'dispBoardDelete', 'document_srl', $oDocument->document_srl)}"><i class="xi-trash"></i>{$lang->cmd_delete}</a>
|
||||
<span class="etc">
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@
|
|||
<input type="number" min="1" name="skip_bottom_list_days" value="{$module_info->skip_bottom_list_days ?: 30}" /> {$lang->unit_day}
|
||||
<br />
|
||||
<label for="skip_bottom_list_for_robot">
|
||||
<input type="checkbox" name="skip_bottom_list_for_robot" id="skip_bottom_list_for_robot" value="Y" checked="checked"|cond="$module_info->skip_bottom_list_for_robot === 'Y'" />
|
||||
<input type="checkbox" name="skip_bottom_list_for_robot" id="skip_bottom_list_for_robot" value="Y" checked="checked"|cond="$module_info->skip_bottom_list_for_robot !== 'N'" />
|
||||
{$lang->skip_bottom_list_for_robot}
|
||||
</label>
|
||||
<p class="x_help-block">{$lang->about_customize_bottom_list}</p>
|
||||
|
|
@ -289,10 +289,18 @@
|
|||
<p class="x_help-block">{$lang->about_comment_length_limit}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label">{$lang->inline_data_url_limit}</label>
|
||||
<div class="x_controls">
|
||||
<input type="number" min="1" name="inline_data_url_limit" id="inline_data_url_limit" value="{$module_info->inline_data_url_limit ?: 64}" /> KB
|
||||
<p class="x_help-block">{$lang->about_inline_data_url_limit}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label">{$lang->consultation}</label>
|
||||
<div class="x_controls">
|
||||
<label class="x_inline" for="consultation"><input type="checkbox" name="consultation" id="consultation" value="Y" checked="checked"|cond="$module_info->consultation == 'Y'" /> {$lang->about_consultation}</label>
|
||||
<label class="x_inline" for="consultation"><input type="checkbox" name="consultation" id="consultation" value="Y" checked="checked"|cond="$module_info->consultation == 'Y'" /> {$lang->use_consultation}</label>
|
||||
<p class="x_help-block">{$lang->about_consultation}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
|
|
|
|||
|
|
@ -735,6 +735,17 @@ class CommentController extends Comment
|
|||
$list_args->head = $parent->head;
|
||||
$list_args->depth = $parent->depth + 1;
|
||||
|
||||
// Check max thread depth.
|
||||
$comment_config = ModuleModel::getModulePartConfig('comment', $obj->module_srl);
|
||||
if (isset($comment_config->max_thread_depth) && $comment_config->max_thread_depth > 0)
|
||||
{
|
||||
if ($list_args->depth + 1 > $comment_config->max_thread_depth)
|
||||
{
|
||||
$oDB->rollback();
|
||||
return new BaseObject(-1, 'msg_exceeds_max_thread_depth');
|
||||
}
|
||||
}
|
||||
|
||||
// if the depth of comments is less than 2, execute insert.
|
||||
if($list_args->depth < 2)
|
||||
{
|
||||
|
|
@ -1554,23 +1565,23 @@ class CommentController extends Comment
|
|||
|
||||
/**
|
||||
* delete declared comment, log
|
||||
* @param array|string $commentSrls : srls string (ex: 1, 2,56, 88)
|
||||
* @param object $args should contain comment_srl
|
||||
* @return void
|
||||
*/
|
||||
function _deleteDeclaredComments($commentSrls)
|
||||
function _deleteDeclaredComments($args)
|
||||
{
|
||||
executeQuery('comment.deleteDeclaredComments', $commentSrls);
|
||||
executeQuery('comment.deleteCommentDeclaredLog', $commentSrls);
|
||||
executeQuery('comment.deleteDeclaredComments', $args);
|
||||
executeQuery('comment.deleteDeclaredCommentLog', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete voted comment log
|
||||
* @param array|string $commentSrls : srls string (ex: 1, 2,56, 88)
|
||||
* @param object $args should contain comment_srl
|
||||
* @return void
|
||||
*/
|
||||
function _deleteVotedComments($commentSrls)
|
||||
function _deleteVotedComments($args)
|
||||
{
|
||||
executeQuery('comment.deleteCommentVotedLog', $commentSrls);
|
||||
executeQuery('comment.deleteCommentVotedLog', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2042,6 +2053,7 @@ class CommentController extends Comment
|
|||
{
|
||||
$comment_config->comment_page_count = 10;
|
||||
}
|
||||
$comment_config->max_thread_depth = (int)Context::get('max_thread_depth') ?: 0;
|
||||
|
||||
$comment_config->default_page = Context::get('default_page');
|
||||
if($comment_config->default_page !== 'first')
|
||||
|
|
|
|||
|
|
@ -246,6 +246,18 @@ class CommentModel extends Comment
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comment depth.
|
||||
*
|
||||
* @param int $comment_srl
|
||||
* @return ?int
|
||||
*/
|
||||
public static function getCommentDepth(int $comment_srl): ?int
|
||||
{
|
||||
$output = executeQuery('comment.getCommentDepth', ['comment_srl' => $comment_srl]);
|
||||
return isset($output->data->depth) ? (int)$output->data->depth : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of comments in corresponding with document_srl.
|
||||
* @param int $document_srl
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ $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';
|
||||
$lang->about_max_thread_depth = '0: Unlimited, 1: No replies, 2 or more: Allow replies';
|
||||
$lang->msg_cart_is_null = 'Please select an article to delete.';
|
||||
$lang->msg_checked_comment_is_deleted = '%d comment(s) is(are) successfully deleted.';
|
||||
$lang->msg_exceeds_max_thread_depth = 'You have exceeded the maximum thread depth. Please leave a reply elsewhere in the thread.';
|
||||
$lang->search_target_list['content'] = 'Content';
|
||||
$lang->search_target_list['user_id'] = 'ID';
|
||||
$lang->search_target_list['user_name'] = 'Name';
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ $lang->comment_default_page_first = '첫 페이지';
|
|||
$lang->comment_default_page_last = '마지막 페이지';
|
||||
$lang->about_comment_count = '댓글을 정해진 수 만큼만 표시하고, 그 이상일 경우 페이지 번호를 표시해서 이동할 수 있게 합니다.';
|
||||
$lang->about_comment_page_count = '하단에 표시할 페이지 링크 수를 설정할 수 있습니다.';
|
||||
$lang->max_thread_depth = '대댓글 최대 깊이';
|
||||
$lang->about_max_thread_depth = '0: 무제한, 1: 대댓글 금지, 2 이상: 대댓글 허용';
|
||||
$lang->msg_cart_is_null = '삭제할 글을 선택해주세요.';
|
||||
$lang->msg_checked_comment_is_deleted = '%d개의 댓글을 삭제했습니다.';
|
||||
$lang->msg_exceeds_max_thread_depth = '대댓글 최대 깊이를 초과했습니다. 다른 자리에 댓글을 남겨 주세요.';
|
||||
$lang->search_target_list['content'] = '내용';
|
||||
$lang->search_target_list['user_id'] = '아이디';
|
||||
$lang->search_target_list['user_name'] = '이름';
|
||||
|
|
|
|||
11
modules/comment/queries/getCommentDepth.xml
Normal file
11
modules/comment/queries/getCommentDepth.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<query id="getCommentDepth" action="select">
|
||||
<tables>
|
||||
<table name="comments_list" />
|
||||
</tables>
|
||||
<columns>
|
||||
<column name="depth" />
|
||||
</columns>
|
||||
<conditions>
|
||||
<condition operation="equal" column="comment_srl" var="comment_srl" notnull="notnull" />
|
||||
</conditions>
|
||||
</query>
|
||||
|
|
@ -19,6 +19,13 @@
|
|||
<p class="x_help-inline">{$lang->about_comment_page_count}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
<label for="max_thread_depth" class="x_control-label">{$lang->max_thread_depth}</label>
|
||||
<div class="x_controls">
|
||||
<input type="number" min="0" name="max_thread_depth" id="max_thread_depth" value="{$comment_config->max_thread_depth ?? ''}" />
|
||||
<p class="x_help-inline">{$lang->about_max_thread_depth}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
<label for="comment_count" class="x_control-label">{$lang->comment_default_page}</label>
|
||||
<div class="x_controls">
|
||||
|
|
|
|||
|
|
@ -174,8 +174,14 @@ class DocumentAdminController extends Document
|
|||
$var_idx = Context::get('var_idx');
|
||||
$name = Context::get('name');
|
||||
$type = Context::get('type');
|
||||
$is_required = Context::get('is_required');
|
||||
$default = Context::get('default');
|
||||
$is_required = Context::get('is_required') === 'Y' ? 'Y' : 'N';
|
||||
$is_strict = Context::get('is_strict') === 'Y' ? 'Y' : 'N';
|
||||
$default = trim(utf8_clean(Context::get('default')));
|
||||
$options = trim(utf8_clean(Context::get('options')));
|
||||
if ($options !== '')
|
||||
{
|
||||
$options = array_map('trim', explode("\n", $options));
|
||||
}
|
||||
$desc = Context::get('desc') ? Context::get('desc') : '';
|
||||
$search = Context::get('search');
|
||||
$eid = Context::get('eid');
|
||||
|
|
@ -201,8 +207,11 @@ class DocumentAdminController extends Document
|
|||
}
|
||||
|
||||
// insert or update
|
||||
$oDocumentController = getController('document');
|
||||
$output = $oDocumentController->insertDocumentExtraKey($module_srl, $var_idx, $name, $type, $is_required, $search, $default, $desc, $eid);
|
||||
$oDocumentController = DocumentController::getInstance();
|
||||
$output = $oDocumentController->insertDocumentExtraKey(
|
||||
$module_srl, $var_idx, $name, $type, $is_required, $search,
|
||||
$default, $desc, $eid, $is_strict, $options
|
||||
);
|
||||
if(!$output->toBool()) return $output;
|
||||
|
||||
$this->setMessage('success_registed');
|
||||
|
|
|
|||
|
|
@ -109,6 +109,10 @@ class Document extends ModuleObject
|
|||
if(!$oDB->isColumnExists('document_categories', 'is_default')) return true;
|
||||
if(!$oDB->isIndexExists('document_categories', 'idx_list_order')) return true;
|
||||
|
||||
// 2024.10.08 Add columns to document_extra_keys table
|
||||
if(!$oDB->isColumnExists('document_extra_keys', 'var_is_strict')) return true;
|
||||
if(!$oDB->isColumnExists('document_extra_keys', 'var_options')) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -219,6 +223,16 @@ class Document extends ModuleObject
|
|||
{
|
||||
$oDB->addIndex('document_categories', 'idx_list_order', array('list_order'));
|
||||
}
|
||||
|
||||
// 2024.10.08 Add columns to document_extra_keys table
|
||||
if(!$oDB->isColumnExists('document_extra_keys', 'var_is_strict'))
|
||||
{
|
||||
$oDB->addColumn('document_extra_keys', 'var_is_strict', 'char', '1', 'N', true, 'var_is_required');
|
||||
}
|
||||
if(!$oDB->isColumnExists('document_extra_keys', 'var_options'))
|
||||
{
|
||||
$oDB->addColumn('document_extra_keys', 'var_options', 'text', '', '', false, 'var_default');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -818,7 +818,11 @@ class DocumentController extends Document
|
|||
if(isset($obj->{'extra_vars'.$idx}))
|
||||
{
|
||||
$tmp = $obj->{'extra_vars'.$idx};
|
||||
if(is_array($tmp))
|
||||
if ($extra_item->type === 'file')
|
||||
{
|
||||
$value = $tmp;
|
||||
}
|
||||
elseif (is_array($tmp))
|
||||
{
|
||||
$value = implode('|@|', $tmp);
|
||||
}
|
||||
|
|
@ -831,7 +835,37 @@ class DocumentController extends Document
|
|||
{
|
||||
$value = trim($obj->{$extra_item->name});
|
||||
}
|
||||
if($value == NULL) continue;
|
||||
|
||||
// Validate and process the extra value.
|
||||
if ($value == NULL && $manual_inserted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!$manual_inserted)
|
||||
{
|
||||
$ev_output = $extra_item->validate($value);
|
||||
if ($ev_output && !$output->toBool())
|
||||
{
|
||||
$oDB->rollback();
|
||||
return $ev_output;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle extra vars that support file upload.
|
||||
if ($extra_item->type === 'file' && is_array($value))
|
||||
{
|
||||
$ev_output = $extra_item->uploadFile($value, $obj->document_srl, 'doc');
|
||||
if (!$ev_output->toBool())
|
||||
{
|
||||
$oDB->rollback();
|
||||
return $ev_output;
|
||||
}
|
||||
$value = $ev_output->get('file_srl');
|
||||
}
|
||||
}
|
||||
|
||||
$extra_vars[$extra_item->name] = $value;
|
||||
$this->insertDocumentExtraVar($obj->module_srl, $obj->document_srl, $idx, $value, $extra_item->eid);
|
||||
}
|
||||
|
|
@ -1153,7 +1187,10 @@ class DocumentController extends Document
|
|||
$extra_vars = array();
|
||||
if(Context::get('act')!='procFileDelete')
|
||||
{
|
||||
// Get a copy of current extra vars before deleting all existing data.
|
||||
$old_extra_vars = DocumentModel::getExtraVars($obj->module_srl, $obj->document_srl);
|
||||
$this->deleteDocumentExtraVars($source_obj->get('module_srl'), $obj->document_srl, null, Context::getLangType());
|
||||
|
||||
// Insert extra variables if the document successfully inserted.
|
||||
$extra_keys = DocumentModel::getExtraKeys($obj->module_srl);
|
||||
if(count($extra_keys))
|
||||
|
|
@ -1164,13 +1201,98 @@ class DocumentController extends Document
|
|||
if(isset($obj->{'extra_vars'.$idx}))
|
||||
{
|
||||
$tmp = $obj->{'extra_vars'.$idx};
|
||||
if(is_array($tmp))
|
||||
if ($extra_item->type === 'file')
|
||||
{
|
||||
$value = $tmp;
|
||||
}
|
||||
elseif (is_array($tmp))
|
||||
{
|
||||
$value = implode('|@|', $tmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = trim($tmp);
|
||||
}
|
||||
}
|
||||
elseif (isset($obj->{$extra_item->name}))
|
||||
{
|
||||
$value = trim($obj->{$extra_item->name});
|
||||
}
|
||||
|
||||
// Validate and process the extra value.
|
||||
if ($value == NULL && $manual_updated && $extra_item->type !== 'file')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for required and strict values.
|
||||
if (!$manual_updated)
|
||||
{
|
||||
$ev_output = $extra_item->validate($value, $old_extra_vars[$idx]->value ?? null);
|
||||
if ($ev_output && !$ev_output->toBool())
|
||||
{
|
||||
$oDB->rollback();
|
||||
return $ev_output;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle extra vars that support file upload.
|
||||
if ($extra_item->type === 'file')
|
||||
{
|
||||
// New upload
|
||||
if (is_array($value) && isset($value['name']))
|
||||
{
|
||||
// Delete old file
|
||||
if (isset($old_extra_vars[$idx]->value))
|
||||
{
|
||||
$fc_output = FileController::getInstance()->deleteFile($old_extra_vars[$idx]->value);
|
||||
if (!$fc_output->toBool())
|
||||
{
|
||||
$oDB->rollback();
|
||||
return $fc_output;
|
||||
}
|
||||
}
|
||||
// Insert new file
|
||||
$ev_output = $extra_item->uploadFile($value, $obj->document_srl, 'doc');
|
||||
if (!$ev_output->toBool())
|
||||
{
|
||||
$oDB->rollback();
|
||||
return $ev_output;
|
||||
}
|
||||
$value = $ev_output->get('file_srl');
|
||||
}
|
||||
// Delete current file
|
||||
elseif (isset($obj->{'_delete_extra_vars'.$idx}) && $obj->{'_delete_extra_vars'.$idx} === 'Y')
|
||||
{
|
||||
if (isset($old_extra_vars[$idx]->value))
|
||||
{
|
||||
// Check if deletion is allowed
|
||||
$ev_output = $extra_item->validate(null);
|
||||
if (!$ev_output->toBool())
|
||||
{
|
||||
$oDB->rollback();
|
||||
return $ev_output;
|
||||
}
|
||||
// Delete old file
|
||||
$fc_output = FileController::getInstance()->deleteFile($old_extra_vars[$idx]->value);
|
||||
if (!$fc_output->toBool())
|
||||
{
|
||||
$oDB->rollback();
|
||||
return $fc_output;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Leave current file unchanged
|
||||
elseif (!$value)
|
||||
{
|
||||
if (isset($old_extra_vars[$idx]->value))
|
||||
{
|
||||
$value = $old_extra_vars[$idx]->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(isset($obj->{$extra_item->name})) $value = trim($obj->{$extra_item->name});
|
||||
if($value == NULL) continue;
|
||||
$extra_vars[$extra_item->name] = $value;
|
||||
$this->insertDocumentExtraVar($obj->module_srl, $obj->document_srl, $idx, $value, $extra_item->eid);
|
||||
}
|
||||
|
|
@ -1611,11 +1733,16 @@ class DocumentController extends Document
|
|||
* @param string $var_default
|
||||
* @param string $var_desc
|
||||
* @param int $eid
|
||||
* @param string $var_is_strict
|
||||
* @param array $var_options
|
||||
* @return object
|
||||
*/
|
||||
function insertDocumentExtraKey($module_srl, $var_idx, $var_name, $var_type, $var_is_required = 'N', $var_search = 'N', $var_default = '', $var_desc = '', $eid = 0)
|
||||
function insertDocumentExtraKey($module_srl, $var_idx, $var_name, $var_type, $var_is_required = 'N', $var_search = 'N', $var_default = '', $var_desc = '', $eid = 0, $var_is_strict = 'N', $var_options = null)
|
||||
{
|
||||
if(!$module_srl || !$var_idx || !$var_name || !$var_type || !$eid) return new BaseObject(-1, 'msg_invalid_request');
|
||||
if (!$module_srl || !$var_idx || !$var_name || !$var_type || !$eid)
|
||||
{
|
||||
return new BaseObject(-1, 'msg_invalid_request');
|
||||
}
|
||||
|
||||
$obj = new stdClass();
|
||||
$obj->module_srl = $module_srl;
|
||||
|
|
@ -1623,8 +1750,10 @@ class DocumentController extends Document
|
|||
$obj->var_name = $var_name;
|
||||
$obj->var_type = $var_type;
|
||||
$obj->var_is_required = $var_is_required=='Y'?'Y':'N';
|
||||
$obj->var_is_strict = $var_is_strict=='Y'?'Y':'N';
|
||||
$obj->var_search = $var_search=='Y'?'Y':'N';
|
||||
$obj->var_default = $var_default;
|
||||
$obj->var_options = $var_options ? json_encode($var_options, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES) : null;
|
||||
$obj->var_desc = $var_desc;
|
||||
$obj->eid = $eid;
|
||||
|
||||
|
|
@ -2554,8 +2683,6 @@ class DocumentController extends Document
|
|||
$js_code[] = 'var validator = xe.getApp("validator")[0];';
|
||||
$js_code[] = 'if(!validator) return false;';
|
||||
|
||||
$logged_info = Context::get('logged_info');
|
||||
|
||||
foreach($extra_keys as $idx => $val)
|
||||
{
|
||||
$idx = $val->idx;
|
||||
|
|
@ -2563,9 +2690,11 @@ class DocumentController extends Document
|
|||
{
|
||||
$idx .= '[]';
|
||||
}
|
||||
$name = str_ireplace(array('<script', '</script'), array('<scr" + "ipt', '</scr" + "ipt'), $val->name);
|
||||
$js_code[] = sprintf('validator.cast("ADD_MESSAGE", ["extra_vars%s","%s"]);', $idx, $name);
|
||||
if($val->is_required == 'Y') $js_code[] = sprintf('validator.cast("ADD_EXTRA_FIELD", ["extra_vars%s", { required:true }]);', $idx);
|
||||
$js_code[] = sprintf('validator.cast("ADD_MESSAGE", ["extra_vars%s", %s]);', $idx, var_export($val->name, true));
|
||||
if($val->is_required == 'Y' && $val->type !== 'file')
|
||||
{
|
||||
$js_code[] = sprintf('validator.cast("ADD_EXTRA_FIELD", ["extra_vars%s", { required:true }]);', $idx);
|
||||
}
|
||||
}
|
||||
|
||||
$js_code[] = '})(jQuery);';
|
||||
|
|
@ -3654,7 +3783,7 @@ Content;
|
|||
{
|
||||
foreach($documentExtraKeys AS $extraItem)
|
||||
{
|
||||
$this->insertDocumentExtraKey($value, $extraItem->idx, $extraItem->name, $extraItem->type, $extraItem->is_required , $extraItem->search , $extraItem->default , $extraItem->desc, $extraItem->eid) ;
|
||||
$this->insertDocumentExtraKey($value, $extraItem->idx, $extraItem->name, $extraItem->type, $extraItem->is_required , $extraItem->search , $extraItem->default , $extraItem->desc, $extraItem->eid, $extraItem->is_strict, $extraItem->options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -965,7 +965,7 @@ class DocumentItem extends BaseObject
|
|||
return $this->get('comment_count');
|
||||
}
|
||||
|
||||
function getComments()
|
||||
function getComments(?int $page = null)
|
||||
{
|
||||
if(!$this->getCommentCount())
|
||||
{
|
||||
|
|
@ -979,7 +979,7 @@ class DocumentItem extends BaseObject
|
|||
|
||||
// cpage is a number of comment pages
|
||||
$cpageStr = sprintf('%d_cpage', $this->document_srl);
|
||||
$cpage = Context::get($cpageStr);
|
||||
$cpage = $page ? $page : Context::get($cpageStr);
|
||||
if(!$cpage)
|
||||
{
|
||||
$cpage = Context::get('cpage');
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@
|
|||
<column name="var_name" alias="name" />
|
||||
<column name="var_type" alias="type" />
|
||||
<column name="var_is_required" alias="is_required" />
|
||||
<column name="var_is_strict" alias="is_strict" />
|
||||
<column name="var_search" alias="search" />
|
||||
<column name="var_default" alias="default" />
|
||||
<column name="var_options" alias="options" />
|
||||
<column name="var_desc" alias="desc" />
|
||||
<column name="eid" alias="eid" />
|
||||
<column name="eid" alias="eid" />
|
||||
</columns>
|
||||
<conditions>
|
||||
<condition operation="equal" column="module_srl" var="module_srl" filter="number" notnull="notnull" />
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
<query id="insertDocumentExtraKey" action="insert">
|
||||
<tables>
|
||||
<table name="document_extra_keys" />
|
||||
</tables>
|
||||
<columns>
|
||||
<column name="module_srl" var="module_srl" filter="number" notnull="notnull" />
|
||||
<column name="var_idx" var="var_idx" filter="number" notnull="notnull" />
|
||||
<column name="var_name" var="var_name" notnull="notnull" />
|
||||
<column name="var_type" var="var_type" notnull="notnull" />
|
||||
<column name="var_is_required" var="var_is_required" default="N" notnull="notnull" />
|
||||
<column name="var_search" var="var_search" default="N" notnull="notnull" />
|
||||
<column name="var_default" var="var_default" />
|
||||
<column name="var_desc" var="var_desc" />
|
||||
<column name="eid" var="eid" notnull="notnull" />
|
||||
</columns>
|
||||
<tables>
|
||||
<table name="document_extra_keys" />
|
||||
</tables>
|
||||
<columns>
|
||||
<column name="module_srl" var="module_srl" filter="number" notnull="notnull" />
|
||||
<column name="var_idx" var="var_idx" filter="number" notnull="notnull" />
|
||||
<column name="var_name" var="var_name" notnull="notnull" />
|
||||
<column name="var_type" var="var_type" notnull="notnull" />
|
||||
<column name="var_is_required" var="var_is_required" default="N" notnull="notnull" />
|
||||
<column name="var_is_strict" var="var_is_strict" default="N" notnull="notnull" />
|
||||
<column name="var_search" var="var_search" default="N" notnull="notnull" />
|
||||
<column name="var_default" var="var_default" />
|
||||
<column name="var_options" var="var_options" />
|
||||
<column name="var_desc" var="var_desc" />
|
||||
<column name="eid" var="eid" notnull="notnull" />
|
||||
</columns>
|
||||
</query>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
<query id="updateDocumentExtraKey" action="update">
|
||||
<tables>
|
||||
<table name="document_extra_keys" />
|
||||
</tables>
|
||||
<columns>
|
||||
<column name="var_name" var="var_name" notnull="notnull" />
|
||||
<column name="var_type" var="var_type" notnull="notnull" />
|
||||
<column name="var_is_required" var="var_is_required" default="N" notnull="notnull" />
|
||||
<column name="var_search" var="var_search" default="N" notnull="notnull" />
|
||||
<column name="var_default" var="var_default" default="" />
|
||||
<column name="var_desc" var="var_desc" />
|
||||
<column name="eid" var="eid" notnull="notnull" />
|
||||
</columns>
|
||||
<conditions>
|
||||
<condition operation="equal" column="module_srl" var="module_srl" filter="number" notnull="notnull" />
|
||||
<condition operation="equal" column="var_idx" var="var_idx" filter="number" notnull="notnull" pipe="and" />
|
||||
</conditions>
|
||||
<tables>
|
||||
<table name="document_extra_keys" />
|
||||
</tables>
|
||||
<columns>
|
||||
<column name="var_name" var="var_name" notnull="notnull" />
|
||||
<column name="var_type" var="var_type" notnull="notnull" />
|
||||
<column name="var_is_required" var="var_is_required" default="N" notnull="notnull" />
|
||||
<column name="var_is_strict" var="var_is_strict" default="N" notnull="notnull" />
|
||||
<column name="var_search" var="var_search" default="N" notnull="notnull" />
|
||||
<column name="var_default" var="var_default" default="" />
|
||||
<column name="var_options" var="var_options" default="" />
|
||||
<column name="var_desc" var="var_desc" />
|
||||
<column name="eid" var="eid" notnull="notnull" />
|
||||
</columns>
|
||||
<conditions>
|
||||
<condition operation="equal" column="module_srl" var="module_srl" filter="number" notnull="notnull" />
|
||||
<condition operation="equal" column="var_idx" var="var_idx" filter="number" notnull="notnull" pipe="and" />
|
||||
</conditions>
|
||||
</query>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@
|
|||
<column name="var_name" type="varchar" size="250" notnull="notnull" />
|
||||
<column name="var_type" type="varchar" size="50" notnull="notnull" />
|
||||
<column name="var_is_required" type="char" size="1" default="N" notnull="notnull" />
|
||||
<column name="var_is_strict" type="char" size="1" default="N" notnull="notnull" />
|
||||
<column name="var_search" type="char" size="1" default="N" notnull="notnull" />
|
||||
<column name="var_default" type="text" />
|
||||
<column name="var_options" type="text" />
|
||||
<column name="var_desc" type="text" />
|
||||
<column name="eid" type="varchar" size="40" />
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -49,6 +49,14 @@
|
|||
<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">
|
||||
<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>
|
||||
<label class="x_inline" for="is_strict_n"><input type="radio" name="is_strict" id="is_strict_n" value="N" checked="checked"|cond="$selected_var->is_strict != 'Y'" /> {$lang->not}</label>
|
||||
<p class="x_help-block">{$lang->about_extra_vars_is_strict}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label" for="default">{$lang->default_value}</label>
|
||||
<div class="x_controls">
|
||||
|
|
@ -56,6 +64,13 @@
|
|||
<p class="x_help-block">{$lang->about_extra_vars_default_value}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
<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>
|
||||
<p class="x_help-block">{$lang->about_extra_vars_options}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label" for="lang_desc">{$lang->description}</label>
|
||||
<div class="x_controls">
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ class EditorModel extends Editor
|
|||
$file_config->allowed_chunk_size = 0;
|
||||
}
|
||||
|
||||
Context::set('file_config',$file_config);
|
||||
Context::set('file_config', $file_config);
|
||||
|
||||
// Configure upload status such as file size
|
||||
$upload_status = FileModel::getUploadStatus();
|
||||
|
|
@ -293,7 +293,7 @@ class EditorModel extends Editor
|
|||
// Check if the file already exists
|
||||
if ($upload_target_srl)
|
||||
{
|
||||
$files_count = FileModel::getFilesCount($upload_target_srl);
|
||||
$files_count = FileModel::getFilesCount($upload_target_srl, $option->upload_target_type ?? null);
|
||||
}
|
||||
}
|
||||
Context::set('files_count', (int)$files_count);
|
||||
|
|
|
|||
|
|
@ -145,6 +145,9 @@
|
|||
});
|
||||
|
||||
// Simulate CKEditor for file upload integration.
|
||||
window._getCkeContainer = function(editor_sequence) {
|
||||
return $('#simpleeditor_instance_' + editor_sequence);
|
||||
};
|
||||
window._getCkeInstance = function(editor_sequence) {
|
||||
var instance = $('#simpleeditor_instance_' + editor_sequence);
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
namespace Rhymix\Modules\Extravar\Models;
|
||||
|
||||
use BaseObject;
|
||||
use Context;
|
||||
use FileController;
|
||||
use FileHandler;
|
||||
use FileModel;
|
||||
use ModuleModel;
|
||||
use Rhymix\Framework\DateTime;
|
||||
use Rhymix\Framework\i18n;
|
||||
|
|
@ -25,7 +30,9 @@ class Value
|
|||
public $name = '';
|
||||
public $desc = '';
|
||||
public $default = null;
|
||||
public $options = null;
|
||||
public $is_required = 'N';
|
||||
public $is_strict = 'N';
|
||||
public $is_disabled = 'N';
|
||||
public $is_readonly = 'N';
|
||||
public $search = 'N';
|
||||
|
|
@ -55,6 +62,15 @@ class Value
|
|||
'kr_zip' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* List of types that can have options.
|
||||
*/
|
||||
public const OPTION_TYPES = [
|
||||
'checkbox' => true,
|
||||
'radio' => true,
|
||||
'select' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor for compatibility with legacy ExtraItem class.
|
||||
*
|
||||
|
|
@ -68,8 +84,11 @@ class Value
|
|||
* @param string $search (Y, N)
|
||||
* @param string $value
|
||||
* @param string $eid
|
||||
* @param string $parent_type
|
||||
* @param string $is_strict
|
||||
* @param string $options
|
||||
*/
|
||||
function __construct(int $module_srl, int $idx, string $name, string $type = 'text', $default = null, $desc = '', $is_required = 'N', $search = 'N', $value = null, string $eid = '')
|
||||
function __construct(int $module_srl, int $idx, string $name, string $type = 'text', $default = null, $desc = '', $is_required = 'N', $search = 'N', $value = null, $eid = '', $parent_type = 'document', $is_strict = '', $options = null)
|
||||
{
|
||||
if (!$idx)
|
||||
{
|
||||
|
|
@ -80,11 +99,14 @@ class Value
|
|||
$this->idx = $idx;
|
||||
$this->eid = $eid;
|
||||
$this->type = $type;
|
||||
$this->parent_type = $parent_type;
|
||||
$this->value = $value;
|
||||
$this->name = $name;
|
||||
$this->desc = $desc;
|
||||
$this->default = $default;
|
||||
$this->options = $options ? json_decode($options) : null;
|
||||
$this->is_required = $is_required;
|
||||
$this->is_strict = $is_strict;
|
||||
$this->search = $search;
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +161,162 @@ class Value
|
|||
return $template->compile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDefaultValue()
|
||||
{
|
||||
if (!$this->canHaveOptions())
|
||||
{
|
||||
return $this->default;
|
||||
}
|
||||
elseif (is_array($this->options))
|
||||
{
|
||||
return $this->default;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options specified by the administrator.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOptions(): array
|
||||
{
|
||||
if (!$this->canHaveOptions())
|
||||
{
|
||||
return $this->options ?? [];
|
||||
}
|
||||
|
||||
if (is_array($this->options))
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
elseif ($this->default)
|
||||
{
|
||||
return is_array($this->default) ? $this->default : explode(',', $this->default);
|
||||
}
|
||||
else
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current value can have options.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canHaveOptions(): bool
|
||||
{
|
||||
return isset(self::OPTION_TYPES[$this->type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current value is an array type.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArrayType(): bool
|
||||
{
|
||||
return isset(self::ARRAY_TYPES[$this->type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $old_value
|
||||
* @return ?BaseObject
|
||||
*/
|
||||
public function validate($value, $old_value = null): ?BaseObject
|
||||
{
|
||||
// Take legacy encoding into consideration.
|
||||
if (is_array($value))
|
||||
{
|
||||
$is_array = true;
|
||||
$values = $value;
|
||||
}
|
||||
elseif (str_contains($value, '|@|'))
|
||||
{
|
||||
$is_array = true;
|
||||
$values = explode('|@|', $value);
|
||||
}
|
||||
else
|
||||
{
|
||||
$is_array = false;
|
||||
$values = [$value];
|
||||
}
|
||||
|
||||
// Check if a required value is empty.
|
||||
if ($this->is_required === 'Y')
|
||||
{
|
||||
if ($this->type === 'file' && !$value && $old_value)
|
||||
{
|
||||
$value = $old_value;
|
||||
$values = (array)$old_value;
|
||||
}
|
||||
if ($is_array && trim(implode('', $values)) === '')
|
||||
{
|
||||
return new BaseObject(-1, sprintf(lang('common.filter.isnull'), Context::replaceUserLang($this->name)));
|
||||
}
|
||||
if (!$is_array && trim(strval($value)) === '')
|
||||
{
|
||||
return new BaseObject(-1, sprintf(lang('common.filter.isnull'), Context::replaceUserLang($this->name)));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a strict value is not one of the specified options.
|
||||
if ($this->is_strict === 'Y' && $value)
|
||||
{
|
||||
if ($this->canHaveOptions())
|
||||
{
|
||||
$options = $this->getOptions();
|
||||
foreach ($values as $v)
|
||||
{
|
||||
if (!in_array($v, $options))
|
||||
{
|
||||
return new BaseObject(-1, sprintf(lang('common.filter.equalto'), Context::replaceUserLang($this->name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($this->isArrayType())
|
||||
{
|
||||
if (!$is_array)
|
||||
{
|
||||
return new BaseObject(-1, sprintf(lang('common.filter.equalto'), Context::replaceUserLang($this->name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file.
|
||||
*
|
||||
* @param array $file
|
||||
* @param int $target_srl
|
||||
* @param string $target_type
|
||||
* @return BaseObject
|
||||
*/
|
||||
public function uploadFile(array $file, int $target_srl, string $target_type): BaseObject
|
||||
{
|
||||
$oFileController = FileController::getInstance();
|
||||
$output = $oFileController->insertFile($file, $this->module_srl, $target_srl);
|
||||
if ($output->toBool())
|
||||
{
|
||||
$oFileController->setFilesValid($target_srl, "ev:$target_type", $output->get('file_srl'));
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next temporary ID.
|
||||
*
|
||||
|
|
@ -185,14 +363,24 @@ class Value
|
|||
{
|
||||
$values = $value;
|
||||
}
|
||||
elseif (preg_match('/^[\[\{].*[\]\}]$/', $value))
|
||||
{
|
||||
$values = json_decode($value, true);
|
||||
if (!is_array($values))
|
||||
{
|
||||
$values = [];
|
||||
}
|
||||
}
|
||||
elseif (str_contains($value, '|@|'))
|
||||
{
|
||||
$values = explode('|@|', $value);
|
||||
}
|
||||
/*
|
||||
elseif (str_contains($value, ',') && $type !== 'kr_zip')
|
||||
{
|
||||
$values = explode(',', $value);
|
||||
}
|
||||
*/
|
||||
else
|
||||
{
|
||||
$values = [$value];
|
||||
|
|
@ -213,6 +401,12 @@ class Value
|
|||
return escape($value, false);
|
||||
}
|
||||
|
||||
// Process the file upload type.
|
||||
if ($type === 'file')
|
||||
{
|
||||
return $value ? intval($value) : null;
|
||||
}
|
||||
|
||||
// Escape and return all other types.
|
||||
return escape($value, false);
|
||||
}
|
||||
|
|
@ -269,7 +463,7 @@ class Value
|
|||
case 'email':
|
||||
return sprintf('<a href="mailto:%s">%s</a>', $value, $value);
|
||||
case 'kr_zip':
|
||||
return is_array($value) ? implode(' ', $value) : $value;
|
||||
return is_array($value) ? trim(implode(' ', $value)) : $value;
|
||||
case 'country':
|
||||
$country = i18n::listCountries()[$value] ?? '';
|
||||
if ($country)
|
||||
|
|
@ -287,6 +481,23 @@ class Value
|
|||
return sprintf('%s-%s-%s', substr($value, 0, 4), substr($value, 4, 2), substr($value, 6, 2));
|
||||
case 'timezone':
|
||||
return DateTime::getTimezoneList()[$value] ?? '';
|
||||
case 'file':
|
||||
if ($value)
|
||||
{
|
||||
$file = FileModel::getFile($value);
|
||||
if ($file)
|
||||
{
|
||||
return sprintf('<span><a href="%s">%s</a> (%s)</span>', \RX_BASEURL . ltrim($file->download_url, './'), $file->source_filename, FileHandler::filesize($file->file_size));
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class ValueCollection
|
|||
|
||||
foreach ($keys as $val)
|
||||
{
|
||||
$this->keys[$val->idx] = new Value($val->module_srl, $val->idx, $val->name, $val->type, $val->default, $val->desc, $val->is_required, $val->search, $val->value ?? null, $val->eid, $val->parent_type ?? 'document');
|
||||
$this->keys[$val->idx] = new Value($val->module_srl, $val->idx, $val->name, $val->type, $val->default, $val->desc, $val->is_required, $val->search, $val->value ?? null, $val->eid, $val->parent_type ?? 'document', $val->is_strict, $val->options);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
26
modules/extravar/skins/default/assets/file_upload.js
Normal file
26
modules/extravar/skins/default/assets/file_upload.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
(function($) {
|
||||
$(function() {
|
||||
$('button.evFileRemover').on('click', function() {
|
||||
const container = $(this).parents('.ev_file_upload');
|
||||
container.find('span.filename').text('');
|
||||
container.find('span.filesize').text('');
|
||||
container.find('input[type=hidden][name^=_delete_]').val('Y');
|
||||
container.find('input[type=file]').val('');
|
||||
});
|
||||
$('input.rx_ev_file').on('change', function() {
|
||||
const container = $(this).parents('.ev_file_upload');
|
||||
const max_size = parseInt($(this).data('allowedFilesize'), 10);
|
||||
const file_count = this.files.length;
|
||||
for (let i = 0; i < file_count; i++) {
|
||||
if (max_size && this.files[i].size > max_size) {
|
||||
alert($(this).data('msgFilesize'));
|
||||
$(this).val('');
|
||||
return;
|
||||
}
|
||||
}
|
||||
container.find('input[type=hidden][name^=_delete_]').val('N');
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
|
|
@ -41,6 +41,9 @@
|
|||
@case ('time')
|
||||
@include ('form_types/datetime')
|
||||
@break
|
||||
@case ('file')
|
||||
@include ('form_types/file_upload')
|
||||
@break
|
||||
@default
|
||||
@include ('form_types/text')
|
||||
@endswitch
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
@php
|
||||
$has_value = is_array($value);
|
||||
$default_value = $definition->getDefaultValue();
|
||||
@endphp
|
||||
|
||||
@if ($parent_type === 'member')
|
||||
<div class="rx_ev_{{ $type }}" style="padding-top:5px">
|
||||
@foreach ($default ?? [] as $v)
|
||||
@foreach ($definition->getOptions() as $v)
|
||||
@php
|
||||
$column_suffix = $type === 'checkbox' ? '[]' : '';
|
||||
$tempid = $definition->getNextTempID();
|
||||
$is_checked = is_array($value) && in_array(trim($v), $value);
|
||||
$is_checked = $has_value ? in_array($v, $value) : ($v === $default_value);
|
||||
@endphp
|
||||
<label for="{{ $tempid }}">
|
||||
<input type="{{ $type }}" name="{{ $input_name . $column_suffix }}"
|
||||
|
|
@ -19,11 +24,11 @@
|
|||
</div>
|
||||
@else
|
||||
<ul class="rx_ev_{{ $type }}">
|
||||
@foreach ($default ?? [] as $v)
|
||||
@foreach ($definition->getOptions() as $v)
|
||||
@php
|
||||
$column_suffix = $type === 'checkbox' ? '[]' : '';
|
||||
$tempid = $definition->getNextTempID();
|
||||
$is_checked = is_array($value) && in_array(trim($v), $value);
|
||||
$is_checked = $has_value ? in_array($v, $value) : ($v === $default_value);
|
||||
@endphp
|
||||
<li>
|
||||
<input type="{{ $type }}" name="{{ $input_name . $column_suffix }}"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
@load('../assets/file_upload.js')
|
||||
|
||||
<div class="ev_file_upload">
|
||||
|
||||
@if ($value)
|
||||
@php
|
||||
$file = FileModel::getFile(intval($value));
|
||||
@endphp
|
||||
@if ($file)
|
||||
<div class="ev_file_info">
|
||||
<span class="filename">{{ $file->source_filename }}</span>
|
||||
<span class="filesize">({{ FileHandler::filesize($file->file_size) }})</span>
|
||||
<input type="hidden" name="_delete_{{ $input_name }}" value="N" />
|
||||
<button type="button" class="btn evFileRemover">{{ lang('cmd_delete') }}</button>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@php
|
||||
$file_config = FileModel::getUploadConfig($definition->module_srl);
|
||||
$allowed_filetypes = strtr($file_config->allowed_filetypes ?? '', ['*.' => '.', ';' => ',']);
|
||||
$allowed_filesize = ($file_config->allowed_filesize ?? 0) * 1024 * 1024;
|
||||
@endphp
|
||||
|
||||
<div class="ev_file_input">
|
||||
<input type="file" name="{{ $input_name }}"
|
||||
id="{{ $input_id }}"|if="$input_id" class="file rx_ev_file"
|
||||
style="{{ $definition->style }}"|if="$definition->style"
|
||||
accept="{{ $allowed_filetypes }}"|if="$allowed_filetypes !== '' && $allowed_filetypes !== '.*'"
|
||||
data-allowed-filesize="{{ $allowed_filesize }}"
|
||||
data-msg-filesize="{{ lang('file.msg_exceeds_limit_size') }}"
|
||||
@required(toBool($definition->is_required) && !$value)
|
||||
@disabled(toBool($definition->is_disabled))
|
||||
@readonly(toBool($definition->is_readonly))
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -1,10 +1,15 @@
|
|||
@php
|
||||
$has_value = is_array($value);
|
||||
$default_value = $definition->getDefaultValue();
|
||||
@endphp
|
||||
|
||||
<select name="{{ $input_name }}"
|
||||
id="{{ $input_id }}"|if="$input_id" class="select rx_ev_select"
|
||||
style="{{ $definition->style }}"|if="$definition->style"
|
||||
@required(toBool($definition->is_required))
|
||||
@disabled(toBool($definition->is_disabled))
|
||||
@readonly(toBool($definition->is_readonly))>
|
||||
@foreach ($default ?: [] as $v)
|
||||
<option value="{{ $v }}" @selected(is_array($value) && in_array(trim($v), $value))>{{ $v }}</option>
|
||||
@foreach ($definition->getOptions() as $v)
|
||||
<option value="{{ $v }}" @selected($has_value ? in_array($v, $value) : ($v === $default_value))>{{ $v }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<input type="url" name="{{ $input_name }}"
|
||||
id="{{ $input_id }}"|if="$input_id" class="homepage rx_ev_url"
|
||||
style="{{ $definition->style }}"|if="$definition->style"
|
||||
value="{{ $value }}"
|
||||
value="{{ strval($value) !== '' ? $value : $default }}"
|
||||
@required(toBool($definition->is_required))
|
||||
@disabled(toBool($definition->is_disabled))
|
||||
@readonly(toBool($definition->is_readonly))
|
||||
|
|
@ -11,7 +11,16 @@
|
|||
<input type="email" name="{{ $input_name }}"
|
||||
id="{{ $input_id }}"|if="$input_id" class="email_address rx_ev_email"
|
||||
style="{{ $definition->style }}"|if="$definition->style"
|
||||
value="{{ $value }}"
|
||||
value="{{ strval($value) !== '' ? $value : $default }}"
|
||||
@required(toBool($definition->is_required))
|
||||
@disabled(toBool($definition->is_disabled))
|
||||
@readonly(toBool($definition->is_readonly))
|
||||
/>
|
||||
@elseif ($type === 'number')
|
||||
<input type="number" name="{{ $input_name }}"
|
||||
id="{{ $input_id }}"|if="$input_id" class="number rx_ev_number"
|
||||
style="{{ $definition->style }}"|if="$definition->style"
|
||||
value="{{ strval($value) !== '' ? $value : $default }}"
|
||||
@required(toBool($definition->is_required))
|
||||
@disabled(toBool($definition->is_disabled))
|
||||
@readonly(toBool($definition->is_readonly))
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@
|
|||
@required(toBool($definition->is_required))
|
||||
@disabled(toBool($definition->is_disabled))
|
||||
@readonly(toBool($definition->is_readonly))
|
||||
rows="8" cols="42">{{ $value }}</textarea>
|
||||
rows="8" cols="42">{{ $value !== null ? $value : $definition->getDefaultValue() }}</textarea>
|
||||
|
|
|
|||
|
|
@ -127,9 +127,9 @@ class FileAdminView extends File
|
|||
}
|
||||
}
|
||||
|
||||
if (in_array($file->upload_target_type, ['doc', 'com']))
|
||||
if (in_array($file->upload_target_type, ['doc', 'com', 'ev:doc', 'ev:com']))
|
||||
{
|
||||
$var = $file->upload_target_type . '_srls';
|
||||
$var = str_replace('ev:', '', $file->upload_target_type) . '_srls';
|
||||
if (!in_array($target_srl, $$var))
|
||||
{
|
||||
$$var[] = $target_srl;
|
||||
|
|
@ -187,7 +187,7 @@ class FileAdminView extends File
|
|||
|
||||
foreach($file_list as $srl => $file)
|
||||
{
|
||||
if($file->upload_target_type == 'com')
|
||||
if($file->upload_target_type === 'com' || $file->upload_target_type === 'ev:com')
|
||||
{
|
||||
$file_list[$srl]->target_document_srl = $comment_list[$file->upload_target_srl]->document_srl;
|
||||
}
|
||||
|
|
@ -224,6 +224,7 @@ class FileAdminView extends File
|
|||
Context::set('config', $config);
|
||||
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));
|
||||
Context::set('is_exec_available', function_exists('exec'));
|
||||
|
||||
// Set a template file
|
||||
$this->setTemplatePath($this->module_path.'tpl');
|
||||
|
|
|
|||
|
|
@ -799,16 +799,22 @@ class FileController extends File
|
|||
* By changing its state to valid when a document is inserted, it prevents from being considered as a unnecessary file
|
||||
*
|
||||
* @param int $upload_target_srl
|
||||
* @param ?string $upload_target_type
|
||||
* @param ?array $file_srl
|
||||
* @return BaseObject
|
||||
*/
|
||||
function setFilesValid($upload_target_srl, $upload_target_type = null)
|
||||
function setFilesValid($upload_target_srl, $upload_target_type = null, $file_srl = null)
|
||||
{
|
||||
$args = new stdClass();
|
||||
$args->upload_target_srl = $upload_target_srl;
|
||||
$args->old_isvalid = 'N';
|
||||
if($upload_target_type)
|
||||
if ($upload_target_type)
|
||||
{
|
||||
$args->upload_target_type = substr($upload_target_type, 0, 3);
|
||||
$args->upload_target_type = $upload_target_type;
|
||||
}
|
||||
if ($file_srl)
|
||||
{
|
||||
$args->file_srl = $file_srl;
|
||||
}
|
||||
$output = executeQuery('file.updateFileValid', $args);
|
||||
$output->add('updated_file_count', intval(DB::getInstance()->getAffectedRows()));
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class FileModel extends File
|
|||
// Get uploaded files
|
||||
if($upload_target_srl)
|
||||
{
|
||||
if (!$upload_target_type || $upload_target_type === 'document')
|
||||
if (!$upload_target_type || $upload_target_type === 'doc' || $upload_target_type === 'document')
|
||||
{
|
||||
$oDocument = DocumentModel::getDocument($upload_target_srl);
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ class FileModel extends File
|
|||
// Check permissions of the comment
|
||||
if(!$oDocument || !$oDocument->isExists())
|
||||
{
|
||||
if (!$upload_target_type || $upload_target_type === 'comment')
|
||||
if (!$upload_target_type || $upload_target_type === 'com' || $upload_target_type === 'comment')
|
||||
{
|
||||
$oComment = CommentModel::getComment($upload_target_srl);
|
||||
if($oComment->isExists())
|
||||
|
|
@ -81,7 +81,9 @@ class FileModel extends File
|
|||
}
|
||||
|
||||
// Set file list
|
||||
foreach(self::getFiles($upload_target_srl) as $file_info)
|
||||
$filter_type = $_SESSION['upload_info'][$editor_sequence]->upload_target_type ?? null;
|
||||
$files = self::getFiles($upload_target_srl, [], 'file_srl', false, $filter_type);
|
||||
foreach ($files as $file_info)
|
||||
{
|
||||
$obj = new stdClass;
|
||||
$obj->file_srl = $file_info->file_srl;
|
||||
|
|
@ -208,7 +210,7 @@ class FileModel extends File
|
|||
|
||||
/**
|
||||
* Check if the file is indexable
|
||||
* @param object $filename
|
||||
* @param string $filename
|
||||
* @param object $file_module_config
|
||||
* @return bool
|
||||
*/
|
||||
|
|
@ -288,12 +290,17 @@ class FileModel extends File
|
|||
* Return number of attachments which belongs to a specific document
|
||||
*
|
||||
* @param int $upload_target_srl The sequence to get a number of files
|
||||
* @param ?string $upload_target_type
|
||||
* @return int Returns a number of files
|
||||
*/
|
||||
public static function getFilesCount($upload_target_srl)
|
||||
public static function getFilesCount($upload_target_srl, $upload_target_type = null)
|
||||
{
|
||||
$args = new stdClass();
|
||||
$args->upload_target_srl = $upload_target_srl;
|
||||
if ($upload_target_type)
|
||||
{
|
||||
$args->upload_target_type = $upload_target_type;
|
||||
}
|
||||
$output = executeQuery('file.getFilesCount', $args);
|
||||
return (int)$output->data->count;
|
||||
}
|
||||
|
|
@ -430,12 +437,20 @@ class FileModel extends File
|
|||
* @param string $sortIndex The column that used as sort index
|
||||
* @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', $ckValid = false)
|
||||
public static function getFiles($upload_target_srl, $columnList = array(), $sortIndex = 'file_srl', $valid_files_only = false, $upload_target_type = null)
|
||||
{
|
||||
$args = new stdClass();
|
||||
$args->upload_target_srl = $upload_target_srl;
|
||||
$args->sort_index = $sortIndex;
|
||||
if($ckValid) $args->isvalid = 'Y';
|
||||
if ($valid_files_only)
|
||||
{
|
||||
$args->isvalid = 'Y';
|
||||
}
|
||||
if ($upload_target_type)
|
||||
{
|
||||
$args->upload_target_type = $upload_target_type;
|
||||
}
|
||||
|
||||
$output = executeQueryArray('file.getFiles', $args, $columnList);
|
||||
if(!$output->data)
|
||||
{
|
||||
|
|
@ -457,9 +472,12 @@ class FileModel extends File
|
|||
*
|
||||
* @return object Returns a file configuration of current module. If user is admin, returns PHP's max file size and allow all file types.
|
||||
*/
|
||||
public static function getUploadConfig()
|
||||
public static function getUploadConfig($module_srl = 0)
|
||||
{
|
||||
$module_srl = Context::get('module_srl') ?: (Context::get('current_module_info')->module_srl ?? 0);
|
||||
if (!$module_srl)
|
||||
{
|
||||
$module_srl = Context::get('module_srl') ?: (Context::get('current_module_info')->module_srl ?? 0);
|
||||
}
|
||||
$config = self::getFileConfig($module_srl);
|
||||
if (Rhymix\Framework\Session::isAdmin())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ $lang->ffprobe_path = 'Absolute Path to ffprobe';
|
|||
$lang->magick_path = 'Absolute Path to magick';
|
||||
$lang->about_ffmpeg_path = 'Rhymix uses ffmpeg to convert video files.';
|
||||
$lang->about_magick_path = 'Rhymix uses magick to convert newer image formats such as AVIF and HEIC.<br />Note that the \'convert\' command from previous versions of ImageMagick doesn\'t support these formats.<br />The latest version can be downloaded from their <a href="https://imagemagick.org/script/download.php" target="_blank">official site</a>.';
|
||||
$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.';
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ $lang->ffprobe_path = 'ffprobe 절대경로';
|
|||
$lang->magick_path = 'magick 절대경로';
|
||||
$lang->about_ffmpeg_path = '동영상 변환에 사용합니다.';
|
||||
$lang->about_magick_path = 'AVIF, HEIC 등 일부 이미지 변환에 사용합니다.<br />구 버전 ImageMagick의 convert 명령은 이러한 포맷을 지원하지 않습니다.<br />새 버전은 <a href="https://imagemagick.org/script/download.php" target="_blank">공식 사이트</a>에서 다운받을 수 있습니다.';
|
||||
$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 명령을 실행할 수 있어야 합니다.';
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
</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" />
|
||||
<condition operation="equal" column="isvalid" var="isvalid" pipe="and" />
|
||||
</conditions>
|
||||
<navigation>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
</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" />
|
||||
<condition operation="like_prefix" column="regdate" var="regDate" pipe="and" />
|
||||
</conditions>
|
||||
</query>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
<query id="updateFileValid" action="update">
|
||||
<tables>
|
||||
<table name="files" />
|
||||
</tables>
|
||||
<columns>
|
||||
<tables>
|
||||
<table name="files" />
|
||||
</tables>
|
||||
<columns>
|
||||
<column name="upload_target_type" var="upload_target_type" />
|
||||
<column name="isvalid" var="isvalid" default="Y" notnull="notnull" />
|
||||
</columns>
|
||||
<conditions>
|
||||
<condition operation="equal" column="upload_target_srl" var="upload_target_srl" filter="number" notnull="notnull" />
|
||||
<condition operation="equal" column="isvalid" var="old_isvalid" pipe="and" />
|
||||
</conditions>
|
||||
<column name="isvalid" var="isvalid" default="Y" notnull="notnull" />
|
||||
</columns>
|
||||
<conditions>
|
||||
<condition operation="equal" column="upload_target_srl" var="upload_target_srl" filter="number" notnull="notnull" />
|
||||
<condition operation="equal" column="isvalid" var="old_isvalid" />
|
||||
<condition operation="in" column="file_srl" var="file_srl" />
|
||||
</conditions>
|
||||
</query>
|
||||
|
|
|
|||
|
|
@ -57,12 +57,12 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
|
|||
<!--@foreach($file_list as $no => $val)-->
|
||||
<!-- one document start -->
|
||||
<!--@if($val->upload_target_srl != $cur_upload_target_srl)-->
|
||||
<!--@if($val->upload_target_type == 'com')-->
|
||||
<!--@if(in_array($val->upload_target_type, ['com', 'ev:com']))-->
|
||||
{@ $document_srl = $val->target_document_srl}
|
||||
{@ $move_uri = getUrl('', 'document_srl', $document_srl).'#comment_'.$val->upload_target_srl}
|
||||
<!--@elseif($val->upload_target_type == 'doc')-->
|
||||
<!--@elseif(in_array($val->upload_target_type, ['doc', 'ev:doc']))-->
|
||||
{@ $document_srl = $val->upload_target_srl}
|
||||
{@ $move_uri = getUrl('', 'document_srl', $document_srl)}
|
||||
{@ $move_uri = getUrl('', 'mid', $module_list[$val->module_srl]->mid, 'document_srl', $document_srl)}
|
||||
<!--@end-->
|
||||
{@ $cur_upload_target_srl = $val->upload_target_srl}
|
||||
<tr>
|
||||
|
|
@ -78,12 +78,12 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
|
|||
<!--@if($val->upload_target_type == 'mod')-->[{$lang->module}] <!--@end-->
|
||||
<!--@if($val->upload_target_type == 'msg')-->[{$lang->member_message}] <!--@end-->
|
||||
|
||||
<!--@if($val->upload_target_type == 'doc' && $document_list[$document_srl] && $document_list[$document_srl]->get('module_srl') == $document_list[$document_srl]->get('member_srl'))-->[{$lang->cmd_temp_save}] <!--@end-->
|
||||
<!--@if($val->upload_target_type == 'doc' && $document_list[$document_srl] && $document_list[$document_srl]->get('module_srl') == 0)-->[{$lang->cmd_trash}] <!--@end-->
|
||||
<!--@if(preg_match('/^(?:ev:)?doc$/', $val->upload_target_type) && $document_list[$document_srl] && $document_list[$document_srl]->get('module_srl') == $document_list[$document_srl]->get('member_srl'))-->[{$lang->cmd_temp_save}] <!--@end-->
|
||||
<!--@if(preg_match('/^(?:ev:)?doc$/', $val->upload_target_type) && $document_list[$document_srl] && $document_list[$document_srl]->get('module_srl') == 0)-->[{$lang->cmd_trash}] <!--@end-->
|
||||
<!--@if($val->module_srl && isset($module_list[$val->module_srl]))-->
|
||||
<a href="{getUrl('', 'mid', $module_list[$val->module_srl]->mid)}" target="_blank">{$module_list[$val->module_srl]->browser_title}</a>
|
||||
<!--@end-->
|
||||
<!--@if($document_list[$document_srl] && ($val->upload_target_type == 'doc' || $val->upload_target_type == 'com'))-->
|
||||
<!--@if($document_list[$document_srl] && preg_match('/^(?:ev:)?(?:doc|com)$/', $val->upload_target_type))-->
|
||||
- <!--@if($document_list[$document_srl]->get('module_srl') != $document_list[$document_srl]->get('member_srl'))--><a href="{$move_uri}" target="_blank">{$document_list[$document_srl]->getTitle()}</a><!--@else-->{$document_list[$document_srl]->getTitle()}<!--@end-->
|
||||
<!--@end-->
|
||||
<!--@end-->
|
||||
|
|
@ -97,7 +97,7 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
|
|||
<!--@elseif($val->thumbnail_filename)-->
|
||||
<img src="{$val->thumbnail_filename}" style="width:50px;height:50px;object-fit:cover;margin-right:4px">
|
||||
<!--@end-->
|
||||
<a href="{htmlspecialchars_decode($val->download_url)}">{htmlspecialchars($val->source_filename, ENT_COMPAT | ENT_HTML401, 'UTF-8', false)}</a></td>
|
||||
<a href="{$val->download_url}">{escape($val->source_filename, false)}</a></td>
|
||||
<td class="nowr">{FileHandler::filesize($val->file_size)}</td>
|
||||
<td class="nowr">{$val->download_count}</td>
|
||||
<td class="nowr">
|
||||
|
|
|
|||
|
|
@ -253,6 +253,7 @@
|
|||
<div class="x_controls">
|
||||
<input type="text" name="ffmpeg_command" value="{$config->ffmpeg_command}" />
|
||||
<p class="x_help-block">{$lang->about_ffmpeg_path}</p>
|
||||
<p class="x_text-info" cond="!$is_exec_available">{$lang->msg_cannot_use_exec}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
|
|
@ -260,6 +261,7 @@
|
|||
<div class="x_controls">
|
||||
<input type="text" name="ffprobe_command" value="{$config->ffprobe_command}" />
|
||||
<p class="x_help-block">{$lang->about_ffmpeg_path}</p>
|
||||
<p class="x_text-info" cond="!$is_exec_available">{$lang->msg_cannot_use_exec}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
|
|
@ -267,6 +269,7 @@
|
|||
<div class="x_controls">
|
||||
<input type="text" name="magick_command" value="{$config->magick_command}" />
|
||||
<p class="x_help-block">{$lang->about_magick_path}</p>
|
||||
<p class="x_text-info" cond="!$is_exec_available">{$lang->msg_cannot_use_exec}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
<div class="krzip-postcode-wrap">
|
||||
<label>{$lang->cmd_krzip_postcode}</label>
|
||||
<input type="text" class="krzip-postcode" value="{$template_config->values[0]}" disabled="disabled" />
|
||||
<input type="button" class="krzip-search btn" value="{$lang->cmd_search}" />
|
||||
<button type="button" class="krzip-search btn">{$lang->cmd_search}</button>
|
||||
<button type="button" class="krzip-delete btn">{$lang->cmd_delete}</button>
|
||||
</div>
|
||||
<div class="krzip-address-wrap">
|
||||
<label>{$lang->cmd_krzip_address}</label>
|
||||
|
|
@ -32,9 +33,10 @@
|
|||
|
||||
<!--// FOOTER -->
|
||||
<script>
|
||||
//<![CDATA[
|
||||
xe.lang.msg_krzip_road_address_expectation = "{$lang->msg_krzip_road_address_expectation}";
|
||||
xe.lang.msg_krzip_jibun_address_expectation = "{$lang->msg_krzip_jibun_address_expectation}";
|
||||
jQuery("#krzip-{$template_config->sequence_id}").Krzip();
|
||||
//]]>
|
||||
jQuery("#krzip-{$template_config->sequence_id}").find('.krzip-delete').on('click', function() {
|
||||
$(this).parents('.krZip').find('input').val('');
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
<div class="krzip-postcode-wrap">
|
||||
<label>{$lang->cmd_krzip_postcode}</label>
|
||||
<input type="text" class="krzip-postcode" value="{$template_config->values[0]}" disabled="disabled" />
|
||||
<input type="button" class="krzip-search btn" value="{$lang->cmd_search}" />
|
||||
<button type="button" class="krzip-search btn">{$lang->cmd_search}</button>
|
||||
<button type="button" class="krzip-delete btn">{$lang->cmd_delete}</button>
|
||||
</div>
|
||||
<div class="krzip-address-wrap">
|
||||
<label>{$lang->cmd_krzip_address}</label>
|
||||
|
|
@ -29,7 +30,8 @@
|
|||
|
||||
<!--// FOOTER -->
|
||||
<script>
|
||||
//<![CDATA[
|
||||
jQuery("#krzip-{$template_config->sequence_id}").Krzip();
|
||||
//]]>
|
||||
jQuery("#krzip-{$template_config->sequence_id}").find('.krzip-delete').on('click', function() {
|
||||
$(this).parents('.krZip').find('input').val('');
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@
|
|||
<div class="krzip-postcode-wrap">
|
||||
<label>{$lang->cmd_krzip_postcode}</label>
|
||||
<input type="text" class="krzip-postcode" value="{$template_config->values[0]}" disabled="disabled" />
|
||||
<input type="button" class="krzip-search btn" value="{$lang->cmd_search}" />
|
||||
<button type="button" class="krzip-search btn">{$lang->cmd_search}</button>
|
||||
<button type="button" class="krzip-delete btn">{$lang->cmd_delete}</button>
|
||||
</div>
|
||||
<div class="krzip-address-wrap">
|
||||
<label>{$lang->cmd_krzip_address}</label>
|
||||
|
|
@ -30,7 +31,8 @@
|
|||
|
||||
<!--// FOOTER -->
|
||||
<script>
|
||||
//<![CDATA[
|
||||
jQuery("#krzip-{$template_config->sequence_id}").Krzip();
|
||||
//]]>
|
||||
jQuery("#krzip-{$template_config->sequence_id}").find('.krzip-delete').on('click', function() {
|
||||
$(this).parents('.krZip').find('input').val('');
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<action name="dispLayoutPreview" type="view" permission="root" meta-noindex="true" />
|
||||
<action name="dispLayoutPreviewWithModule" type="view" permission="root" meta-noindex="true" />
|
||||
<action name="getLayoutInstanceListForJSONP" type="model" permission="root" />
|
||||
|
||||
|
||||
<action name="dispLayoutAdminInstalledList" type="view" admin_index="true" menu_name="installedLayout" menu_index="true" />
|
||||
<action name="dispLayoutAdminAllInstanceList" type="view" menu_name="installedLayout" />
|
||||
<action name="dispLayoutAdminInstanceList" type="view" menu_name="installedLayout" />
|
||||
|
|
@ -14,11 +14,10 @@
|
|||
<action name="dispLayoutAdminEdit" type="view" menu_name="installedLayout" />
|
||||
<action name="dispLayoutAdminCopyLayout" type="view" />
|
||||
<action name="dispLayoutAdminLayoutModify" type="view" />
|
||||
|
||||
|
||||
<action name="getLayoutAdminSetInfoView" type="model" />
|
||||
<action name="getLayoutAdminSetHTMLCSS" type="model" />
|
||||
<action name="getLayoutAdminSiteDefaultLayout" type="model" />
|
||||
|
||||
|
||||
<action name="procLayoutAdminInsert" type="controller" ruleset="insertLayout" />
|
||||
<action name="procLayoutAdminUpdate" type="controller" ruleset="updateLayout" />
|
||||
<action name="procLayoutAdminDelete" type="controller" ruleset="deleteLayout" />
|
||||
|
|
|
|||
|
|
@ -92,73 +92,6 @@ class LayoutAdminModel extends Layout
|
|||
Context::set('selected_layout', $layout_info);
|
||||
}
|
||||
|
||||
public function getLayoutAdminSetHTMLCSS()
|
||||
{
|
||||
// Set the layout with its information
|
||||
$layout_srl = Context::get('layout_srl');
|
||||
// Get layout information
|
||||
$oLayoutModel = getModel('layout');
|
||||
$layout_info = $oLayoutModel->getLayout($layout_srl);
|
||||
// Error appears if there is no layout information is registered
|
||||
if(!$layout_info)
|
||||
{
|
||||
return $this->dispLayoutAdminInstalledList();
|
||||
}
|
||||
|
||||
// Get Layout Code
|
||||
if($oLayoutModel->useDefaultLayout($layout_info->layout_srl))
|
||||
{
|
||||
$layout_file = $oLayoutModel->getDefaultLayoutHtml($layout_info->layout);
|
||||
$layout_css_file = $oLayoutModel->getDefaultLayoutCss($layout_info->layout);
|
||||
}
|
||||
else
|
||||
{
|
||||
$layout_file = $oLayoutModel->getUserLayoutHtml($layout_info->layout_srl);
|
||||
$layout_css_file = $oLayoutModel->getUserLayoutCss($layout_info->layout_srl);
|
||||
|
||||
if(!file_exists($layout_file)) $layout_file = $layout_info->path . 'layout.html';
|
||||
if(!file_exists($layout_css_file)) $layout_css_file = $layout_info->path . 'layout.css';
|
||||
}
|
||||
|
||||
if(file_exists($layout_css_file))
|
||||
{
|
||||
$layout_code_css = FileHandler::readFile($layout_css_file);
|
||||
Context::set('layout_code_css', $layout_code_css);
|
||||
}
|
||||
|
||||
$layout_code = FileHandler::readFile($layout_file);
|
||||
Context::set('layout_code', $layout_code);
|
||||
|
||||
// set User Images
|
||||
$layout_image_list = $oLayoutModel->getUserLayoutImageList($layout_info->layout_srl);
|
||||
Context::set('layout_image_list', $layout_image_list);
|
||||
|
||||
$layout_image_path = $oLayoutModel->getUserLayoutImagePath($layout_info->layout_srl);
|
||||
Context::set('layout_image_path', $layout_image_path);
|
||||
// Set widget list
|
||||
$oWidgetModel = getModel('widget');
|
||||
$widget_list = $oWidgetModel->getDownloadedWidgetList();
|
||||
Context::set('widget_list', $widget_list);
|
||||
|
||||
$security = new Security($layout_info);
|
||||
$layout_info = $security->encodeHTML('.', '.author..');
|
||||
Context::set('selected_layout', $layout_info);
|
||||
|
||||
//Security
|
||||
$security = new Security();
|
||||
$security->encodeHTML('layout_list..');
|
||||
$security->encodeHTML('layout_list..author..');
|
||||
|
||||
$security = new Security();
|
||||
$security->encodeHTML('layout_code_css', 'layout_code', 'widget_list..title');
|
||||
|
||||
$script = '<script src="./modules/layout/tpl/js/layout_admin_set_html.js"></script>';
|
||||
$oTemplate = TemplateHandler::getInstance();
|
||||
$html = $oTemplate->compile($this->module_path.'tpl/', 'layout_html_css_view');
|
||||
|
||||
$this->add('html', $script.$html);
|
||||
}
|
||||
|
||||
public function getLayoutAdminSiteDefaultLayout()
|
||||
{
|
||||
$type = Context::get('type');
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
<th scope="col" class="nowr">{$lang->version}</th>
|
||||
<th scope="col" class="nowr">{$lang->author}</th>
|
||||
<th scope="col" class="nowr rx_detail_marks">{$lang->path}</th>
|
||||
<th scope="col" class="nowr rx_detail_marks">{$lang->cmd_delete}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -53,7 +52,6 @@
|
|||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td class="rx_detail_marks">{$layout->path}</td>
|
||||
<td class="nowr rx_detail_marks"><a cond="$layout->remove_url" class="x_btn x_btn-link" href="{$layout->remove_url}&return_url={urlencodegetRequestUriByServerEnviroment()}">{$lang->cmd_delete}</a></td>
|
||||
</block>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -307,6 +307,10 @@ class MemberAdminModel extends Member
|
|||
$id_list = implode(',',$list);
|
||||
Context::set('id_list',$id_list);
|
||||
|
||||
$extravar_types = lang('common.column_type_list');
|
||||
unset($extravar_types['file']);
|
||||
Context::set('extravar_types', $extravar_types);
|
||||
|
||||
$oTemplate = TemplateHandler::getInstance();
|
||||
$tpl = $oTemplate->compile($this->module_path.'tpl', 'insert_join_form');
|
||||
|
||||
|
|
|
|||
|
|
@ -657,8 +657,7 @@ class Member extends ModuleObject
|
|||
if($error == 0) return new BaseObject($error, $message);
|
||||
|
||||
// Create a member model object
|
||||
$oMemberModel = getModel('member');
|
||||
$config = $oMemberModel->getMemberConfig();
|
||||
$config = MemberModel::getMemberConfig();
|
||||
|
||||
// Check if there is recoding table.
|
||||
$oDB = DB::getInstance();
|
||||
|
|
@ -681,8 +680,6 @@ class Member extends ModuleObject
|
|||
{
|
||||
$args->count = 1;
|
||||
}
|
||||
unset($oMemberModel);
|
||||
unset($config);
|
||||
$output = executeQuery('member.updateLoginCountByIp', $args);
|
||||
}
|
||||
else
|
||||
|
|
@ -714,6 +711,10 @@ class Member extends ModuleObject
|
|||
{
|
||||
//update
|
||||
$content = unserialize($output->data->content);
|
||||
if (is_array($content) && count($content) >= 250)
|
||||
{
|
||||
$content = array_slice($content, -200);
|
||||
}
|
||||
$content[] = array(\RX_CLIENT_IP, lang($message), \RX_TIME);
|
||||
$args->content = serialize($content);
|
||||
$output = executeQuery('member.updateLoginCountHistoryByMemberSrl', $args);
|
||||
|
|
|
|||
|
|
@ -2575,7 +2575,7 @@ class MemberController extends Member
|
|||
}
|
||||
else
|
||||
{
|
||||
$refused_reason = $member_info->refused_reason ? ('<br>' . lang('refused_reason') . ': ' . $member_info->refused_reason) : '';
|
||||
$refused_reason = $member_info->refused_reason ? ("\n" . lang('refused_reason') . ': ' . $member_info->refused_reason) : '';
|
||||
return new BaseObject(-1, lang('msg_user_denied') . $refused_reason);
|
||||
}
|
||||
}
|
||||
|
|
@ -2583,7 +2583,7 @@ class MemberController extends Member
|
|||
// Notify if user is limited
|
||||
if($member_info->limit_date && substr($member_info->limit_date,0,8) >= date("Ymd"))
|
||||
{
|
||||
$limited_reason = $member_info->limited_reason ? ('<br>' . lang('refused_reason') . ': ' . $member_info->limited_reason) : '';
|
||||
$limited_reason = $member_info->limited_reason ? ("\n" . lang('refused_reason') . ': ' . $member_info->limited_reason) : '';
|
||||
return new BaseObject(-9, sprintf(lang('msg_user_limited'), zdate($member_info->limit_date,"Y-m-d")) . $limited_reason);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class MemberView extends Member
|
|||
{
|
||||
$this->member_config = MemberModel::getMemberConfig();
|
||||
}
|
||||
if (!$this->member_config->mid || !$this->member_config->force_mid)
|
||||
if (empty($this->member_config->mid) || empty($this->member_config->force_mid))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
<label for="columnType" class="x_control-label"><em style="color:red">*</em> {$lang->column_type}</label>
|
||||
<div class="x_controls">
|
||||
<select id="columnType" class="typeSelect" name="column_type">
|
||||
<option loop="$lang->column_type_list=>$key,$type_info" value="{$key}" selected="selected"|cond="$formInfo->column_type == $key" >{$lang->column_type_list[$key]}</option>
|
||||
<option loop="$extravar_types=>$key,$type_info" value="{$key}" selected="selected"|cond="$formInfo->column_type == $key" >{$lang->column_type_list[$key]}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -251,8 +251,8 @@
|
|||
<td style="text-align:center"><input type="checkbox" name="usable_list[]" value="{$item->name}" title="{$lang->use}" checked="checked"|cond="$item->isUse" /></td>
|
||||
<td style="text-align:center"><input type="checkbox" name="is_{$item->name}_public" value="Y" checked="checked"|cond="$item->isPublic == 'Y'" disabled="disabled"|cond="!$item->isUse" /></td>
|
||||
<td class="nowr">
|
||||
<label for="{$item->name}_re" class="x_inline"><input type="radio" id="{$item->name}_re" name="{$item->name}" value="required" checked="checked"|cond="$item->required" disabled="disabled"|cond="!$item->isUse"/> {$lang->cmd_required}</label>
|
||||
<label for="{$item->name}_op" class="x_inline"><input type="radio" id="{$item->name}_op" name="{$item->name}" value="option" checked="checked"|cond="$item->isUse && !$item->required" disabled="disabled"|cond="!$item->isUse" /> {$lang->cmd_optional}</label>
|
||||
<label for="{$item->name}_re" class="x_inline"><input type="radio" id="{$item->name}_re" name="{$item->name}" class="item_required" value="required" checked="checked"|cond="$item->required" disabled="disabled"|cond="!$item->isUse" /> {$lang->cmd_required}</label>
|
||||
<label for="{$item->name}_op" class="x_inline"><input type="radio" id="{$item->name}_op" name="{$item->name}" class="item_optional" value="option" checked="checked"|cond="$item->isUse && !$item->required" disabled="disabled"|cond="!$item->isUse" /> {$lang->cmd_optional}</label>
|
||||
</td>
|
||||
<td class="desc" title="{$item->description}">{$item->description}</td>
|
||||
<td id="{$item->member_join_form_srl}" class="nowr" style="text-align:center"><a href="#userDefine" class="modalAnchor _extendFormEdit">{$lang->cmd_edit}</a> <i>|</i> <a href="#" class="_extendFormDelete">{$lang->cmd_delete}</a></td>
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@ $lang->go_to_site_design_setup = 'サイトでフォルトデザイン設定ペ
|
|||
$lang->about_menu_type_in_default = 'メニュータイプ変更のためにメニューを削除後再作成が必要です。';
|
||||
$lang->how_to_modify_menu = '作成したメニューは「メニュー修正」で修正できます。';
|
||||
$lang->can_drag_menu = 'サイトマップでメニューをドラッグしても位置変更ができます。';
|
||||
$lang->good_to_duplicate_layout = 'レイアウト設定変更時、「コピーして作成」ブタンをクリックしてコピーを作成して設定を変更することが望ましいです。';
|
||||
$lang->img_uploaded = 'ブタンイメージが登録されました。';
|
||||
$lang->img_deleted = 'ブタンイメージが削除されました。';
|
||||
$lang->do_not_display_again = '再び表示しない。';
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@ $lang->go_to_site_design_setup = '사이트 기본 디자인 설정 페이지로
|
|||
$lang->about_menu_type_in_default = '메뉴 타입 변경을 위해서는 메뉴를 삭제 후 재생성 해야 됩니다.';
|
||||
$lang->how_to_modify_menu = '생성한 메뉴는 [메뉴 수정]에서 수정 할 수 있습니다.';
|
||||
$lang->can_drag_menu = '사이트 맵에서 메뉴를 드래그 해서도 위치를 변경 할 수 있습니다.';
|
||||
$lang->good_to_duplicate_layout = '레이아웃 설정 변경 시, [복사본 생성] 버튼을 눌러 복사본을 만들어 설정을 변경 하는 것이 좋습니다.';
|
||||
$lang->img_uploaded = '버튼 이미지가 등록 됐습니다.';
|
||||
$lang->img_deleted = '버튼 이미지가 삭제 됐습니다.';
|
||||
$lang->do_not_display_again = '다시 보지 않기.';
|
||||
|
|
|
|||
|
|
@ -498,7 +498,6 @@
|
|||
</button>
|
||||
<ul class="_edit_menu">
|
||||
<li><a href="#layoutSetup" data-admin-show="#layoutSetup" data-param='{ "sLayoutSrl":"${LayoutSrl}"}'>{$lang->full_settings} <i class="x_icon-circle-arrow-right"></i></a></li>
|
||||
<li><a href="#layoutMarkupSetup" data-admin-show="#layoutMarkupSetup" data-param='{ "sLayoutSrl":"${LayoutSrl}"}'>HTML/CSS <i class="x_icon-circle-arrow-right"></i></a></li>
|
||||
<li><button type="button" class="_duplicateLayout">{$lang->make_copy}</button></li>
|
||||
<li><button type="button" class="_deleteLayout">{$lang->cmd_delete}</button></li>
|
||||
</ul>
|
||||
|
|
@ -523,16 +522,6 @@
|
|||
</section>
|
||||
<button type="button" class="x_close" data-admin-hide="#layoutSetup">×</button>
|
||||
</div>
|
||||
<div class="col layoutMarkupSetup" id="layoutMarkupSetup" style="display:none">
|
||||
<section>
|
||||
<h1>{$lang->layout_html_css_settings}</h1>
|
||||
<div class="cnt">
|
||||
<div class="_contents"></div>
|
||||
</div>
|
||||
</section>
|
||||
<button type="button" class="x_close" data-admin-hide="#layoutMarkupSetup">×</button>
|
||||
</div>
|
||||
|
||||
<div class="col download" id="downloadLayout" style="display:none">
|
||||
<section>
|
||||
<h1>{$lang->install_other_layouts}</h1>
|
||||
|
|
@ -753,7 +742,6 @@
|
|||
xe.lang.go_to_site_design_setup = '{$lang->go_to_site_design_setup}';
|
||||
xe.lang.how_to_modify_menu = '{$lang->how_to_modify_menu}';
|
||||
xe.lang.can_drag_menu = '{$lang->can_drag_menu}';
|
||||
xe.lang.good_to_duplicate_layout = '{$lang->good_to_duplicate_layout}';
|
||||
xe.lang.img_uploaded = '{$lang->img_uploaded}';
|
||||
xe.lang.img_deleted = '{$lang->img_deleted}';
|
||||
xe.lang.no_use_layout = '{$lang->no_use_layout}';
|
||||
|
|
@ -3389,8 +3377,6 @@ jQuery(function($){
|
|||
$('#layoutSetup').on('show', function(ev){
|
||||
if(this !== ev.target) return;
|
||||
|
||||
showQuickHelp('good_to_duplicate_layout');
|
||||
|
||||
//console.log($._htMarkupActionParam.sLayoutSrl);
|
||||
var sLayoutSrl = $._htMarkupActionParam.sLayoutSrl;
|
||||
$.exec_json("layout.getLayoutAdminSetInfoView", {layout_srl: sLayoutSrl}, function(htData){
|
||||
|
|
@ -3423,62 +3409,6 @@ jQuery(function($){
|
|||
top.refreshLayoutList = function(htData){
|
||||
$('#layout').show();
|
||||
}
|
||||
top.refreshLayoutMarkupSetup = function(htData){
|
||||
$('#layoutMarkupSetup').show();
|
||||
}
|
||||
$('#layoutMarkupSetup').on('show', function(ev){
|
||||
var sLayoutSrl = $._htMarkupActionParam.sLayoutSrl;
|
||||
$.exec_json("layout.getLayoutAdminSetHTMLCSS", {layout_srl: sLayoutSrl}, function(htData){
|
||||
//console.log(htData);
|
||||
$contents = $('#layoutMarkupSetup ._contents');
|
||||
$contents.html(htData.html);
|
||||
|
||||
$allForms = $('#layoutMarkupSetup form');
|
||||
|
||||
$fileChest = $contents.find('form[enctype="multipart/form-data"]');
|
||||
$markup = $contents.find('#fo_layout');
|
||||
|
||||
$fileDelete = $allForms.not($fileChest).not($markup);
|
||||
|
||||
/*
|
||||
$fileChest.append($("<INPUT>").attr("type", "hidden").attr("name", "xe_js_callback").attr("value", "top.refreshLayoutMarkupSetup"));
|
||||
$fileChest.attr('target', 'submitTarget');
|
||||
*/
|
||||
|
||||
$fileChest.add($fileDelete).append($("<INPUT>").attr("type", "hidden").attr("name", "xe_js_callback").attr("value", "top.refreshLayoutMarkupSetup"));
|
||||
$markup.append($("<INPUT>").attr("type", "hidden").attr("name", "xe_js_callback").attr("value", "top.refreshLayoutList"));
|
||||
|
||||
$allForms.attr('target', 'submitTarget');
|
||||
|
||||
var $configForm = $('#config_form');
|
||||
$configForm.on('submit', function(ev){
|
||||
var htData = {};
|
||||
var sKey, sValue;
|
||||
$(this).find('input').each(function(){
|
||||
sKey = $(this).attr('name');
|
||||
sValue = $(this).val();
|
||||
htData[sKey] = sValue;
|
||||
});
|
||||
|
||||
ev.preventDefault();
|
||||
|
||||
var sUrl = $('#config_form').attr('action');
|
||||
$.post( sUrl, htData,
|
||||
function( strRes ) {
|
||||
var htData = $.parseJSON(strRes);
|
||||
|
||||
$('#layoutSetup').hide();
|
||||
//console.log(999, strRes, htData);
|
||||
}
|
||||
);
|
||||
})
|
||||
|
||||
scrollToRight();
|
||||
});
|
||||
});
|
||||
$('#layoutMarkupSetup').on('after-show', function(ev){
|
||||
scrollToRight();
|
||||
});
|
||||
|
||||
function doDelete(sMenuSrl, bForce, sSelectOnload, deleteModule){
|
||||
var params = {
|
||||
|
|
@ -3770,7 +3700,6 @@ jQuery(function($){
|
|||
var htQuickHelpMsg = {
|
||||
'how_to_modify_menu' : xe.lang.how_to_modify_menu,
|
||||
'can_drag_menu' : xe.lang.can_drag_menu,
|
||||
'good_to_duplicate_layout' : xe.lang.good_to_duplicate_layout,
|
||||
'img_uploaded' : xe.lang.img_uploaded,
|
||||
'img_deleted' : xe.lang.img_deleted
|
||||
};
|
||||
|
|
|
|||
|
|
@ -97,7 +97,11 @@ $lang->about_mobile_page_count = 'You can set the number of page links to move p
|
|||
$lang->about_admin_id = 'You can grant someone permission to manage this module. Please enter the user ID or email address of the person you wish to add.';
|
||||
$lang->about_grant_deatil = 'Registered users mean users who signed-up to the virtual sites (e.g., cafeXE).';
|
||||
$lang->about_module = 'Rhymix consists of modules except the basic library. [Module Manage] module will show all installed modules and help you to manage them.';
|
||||
$lang->about_extra_vars_default_value = 'If you need to provide multiple choices, you can separate them with a comma.';
|
||||
$lang->extra_vars_is_strict = 'Specified values only';
|
||||
$lang->extra_vars_options = 'Options';
|
||||
$lang->about_extra_vars_is_strict = 'In single and multiple choice fields, only allow the values specified below. If you change the allowed values, it may affect previous posts.';
|
||||
$lang->about_extra_vars_default_value = 'In single and multiple choice fields, the default value should be one of the values specified below.';
|
||||
$lang->about_extra_vars_options = 'In single and multiple choice fields, please enter the options one in each line.';
|
||||
$lang->about_search_virtual_site = 'Enter domain of virtual sites. To search modules of non-virtual site, search with blank';
|
||||
$lang->about_extra_vars_eid_value = 'This is the unique ID for this field.<br>You can use a combination of Latin alphabets, numbers and underscore(_), as long as it starts with an alphabet.';
|
||||
$lang->about_extra_vars_column_name = 'This is the name that is actually shown to users.';
|
||||
|
|
|
|||
|
|
@ -96,7 +96,11 @@ $lang->about_mobile_page_count = '목록 하단, 페이지를 이동하는 링
|
|||
$lang->about_admin_id = '특정 회원에게 이 모듈의 관리 권한을 부여할 수 있습니다. 권한을 부여할 회원의 아이디 또는 이메일 주소를 입력해 주세요.';
|
||||
$lang->about_grant_deatil = '가입한 사용자는 cafeXE 등 분양형 가상 사이트에 가입을 한 로그인 사용자를 의미합니다.';
|
||||
$lang->about_module = 'Rhymix는 기본 라이브러리를 제외한 나머지는 모두 모듈로 구성되어 있습니다. 모듈 관리 모듈은 설치된 모든 모듈을 보여주고 관리를 돕습니다.';
|
||||
$lang->about_extra_vars_default_value = '다중/단일 선택 등 여러 선택지가 주어지는 경우 ','(쉼표)로 구분하면 됩니다.';
|
||||
$lang->extra_vars_is_strict = '임의입력 금지';
|
||||
$lang->extra_vars_options = '선택지';
|
||||
$lang->about_extra_vars_is_strict = '단일/다중 선택에서 미리 주어진 선택지만 입력할 수 있도록 합니다. 선택지를 변경할 경우 기존 게시물에 영향을 줄 수 있습니다.';
|
||||
$lang->about_extra_vars_default_value = '단일/다중 선택의 경우, 아래에 입력한 선택지 중 하나여야 합니다.';
|
||||
$lang->about_extra_vars_options = '단일/다중 선택 등 여러 선택지가 주어지는 경우, 한 줄에 하나씩 입력하십시오.<br><span style="color:red">구 버전과 달리, 쉼표로 구분하지 않으니 주의하십시오.</span>';
|
||||
$lang->about_search_virtual_site = '가상 사이트(예:cafeXE) 도메인을 입력한 후 검색하세요. 가상 사이트 이외의 모듈은 내용을 비우고 검색하면 됩니다. (http:// 는 제외)';
|
||||
$lang->about_extra_vars_eid_value = '시스템 내에서 이 변수를 식별하기 위한 ID값입니다. 예: school<br>영문, 숫자, _를 조합해서 사용할 수 있으며 첫 글자는 영문이어야 합니다.';
|
||||
$lang->about_extra_vars_column_name = '사용자에게 보여지는 입력 항목 이름입니다. 예: 학교';
|
||||
|
|
|
|||
7
modules/module/schemas/task_queue.xml
Normal file
7
modules/module/schemas/task_queue.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<table name="task_queue">
|
||||
<column name="id" type="bigint" notnull="notnull" primary_key="primary_key" auto_increment="auto_increment" />
|
||||
<column name="handler" type="varchar" size="191" notnull="notnull" />
|
||||
<column name="args" type="longtext" notnull="notnull" />
|
||||
<column name="options" type="longtext" notnull="notnull" />
|
||||
<column name="regdate" type="datetime" notnull="notnull" default="current_timestamp()" index="idx_regdate" />
|
||||
</table>
|
||||
|
|
@ -17,7 +17,6 @@
|
|||
<th class="nowr">{$lang->version}</th>
|
||||
<th class="nowr rx_detail_marks">{$lang->author}</th>
|
||||
<th class="nowr rx_detail_marks">{$lang->path}</th>
|
||||
<th class="nowr rx_detail_marks">{$lang->cmd_delete}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -55,9 +54,6 @@
|
|||
<!--@endforeach-->
|
||||
</td>
|
||||
<td class="rx_detail_marks">{$val->path}</td>
|
||||
<td class="rx_detail_marks">
|
||||
<a href="{$val->delete_url}&return_url={urlencode(getRequestUriByServerEnviroment())}" cond="$val->delete_url && $val->version !== 'RX_VERSION'">{$lang->cmd_delete}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue