Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Lastorder 2025-12-24 03:07:48 +00:00
commit 881b0fbac1
58 changed files with 606 additions and 200 deletions

View file

@ -4,47 +4,27 @@
sudo add-apt-repository -y ppa:ondrej/php
# Install all required packages
if [[ "$1" == "8.5" ]]; then
sudo apt -y install \
php$1-bcmath \
php$1-cli \
php$1-common \
php$1-curl \
php$1-gd \
php$1-intl \
php$1-mbstring \
php$1-mysql \
php$1-readline \
php$1-sqlite3 \
php$1-xml \
php$1-zip
else
sudo apt -y install \
php$1-apcu \
php$1-bcmath \
php$1-cli \
php$1-common \
php$1-curl \
php$1-gd \
php$1-intl \
php$1-mbstring \
php$1-mysql \
php$1-opcache \
php$1-readline \
php$1-sqlite3 \
php$1-xml \
php$1-zip
fi
sudo apt -y install \
php$1-apcu \
php$1-bcmath \
php$1-cli \
php$1-common \
php$1-curl \
php$1-gd \
php$1-intl \
php$1-mbstring \
php$1-mysql \
php$1-readline \
php$1-sqlite3 \
php$1-xml \
php$1-zip
# Adjust php.ini settings
if [[ "$1" == "8.5" ]]; then
sudo bash -c "echo 'register_argc_argv = On' >> /etc/php/$1/cli/php.ini"
else
sudo bash -c "echo 'opcache.enable = 1' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
sudo bash -c "echo 'opcache.enable_cli = 1' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
sudo bash -c "echo 'opcache.jit = tracing' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
sudo bash -c "echo 'opcache.jit_buffer_size = 128M' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
fi
sudo bash -c "echo 'register_argc_argv = On' >> /etc/php/$1/cli/php.ini"
sudo bash -c "echo 'opcache.enable = 1' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
sudo bash -c "echo 'opcache.enable_cli = 1' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
sudo bash -c "echo 'opcache.jit = tracing' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
sudo bash -c "echo 'opcache.jit_buffer_size = 128M' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
# Enable APCu
if [ -f "/etc/php/$1/cli/conf.d/20-apcu.ini" ]; then

View file

@ -139,7 +139,7 @@ class Context
*/
private static $_check_patterns = array(
'@<(?:\?|%)@' => 'DENY ALL',
'@<script\s*?language\s*?=@i' => 'DENY ALL',
'@<script\s*?language\s*?=\s*?(?![\'"]javascript[\'"])@i' => 'DENY ALL',
'@</?script@i' => 'ALLOW ADMIN ONLY',
);

View file

@ -728,7 +728,7 @@ class HTMLDisplayHandler
* import basic .js files.
* @return void
*/
function _loadDesktopJSCSS()
public function _loadDesktopJSCSS()
{
$this->_loadCommonJSCSS();
}

View file

@ -461,18 +461,28 @@ class FileHandler
* Check available memory to load image file
*
* @param array $imageInfo Image info retrieved by getimagesize function
* @param array $resizeInfo Resize width and height
* @return bool TRUE: it's ok, FALSE: otherwise
*/
public static function checkMemoryLoadImage(&$imageInfo)
public static function checkMemoryLoadImage($imageInfo, $resizeInfo = [])
{
$K64 = 65536;
$TWEAKFACTOR = 2.0;
$channels = $imageInfo['channels'] ?? 6;
if(!$channels)
$bits = $imageInfo['bits'] ?? 8;
$channels = ($imageInfo['channels'] ?? 6) ?: 6; // for png
if (!$resizeInfo)
{
$channels = 6; //for png
$resizeInfo = $imageInfo;
}
$memoryNeeded = round(($imageInfo[0] * $imageInfo[1] * $imageInfo['bits'] * $channels / 8 + $K64 ) * $TWEAKFACTOR);
$src_memory = round($imageInfo[0] * $imageInfo[1] * $bits * $channels / 8) + 65536;
$dst_memory = round($resizeInfo[0] * $resizeInfo[1] * 8 * $channels / 8) + 65536;
$gd_info = gd_info();
$gd_version = $gd_info['GD Version'] ?? '';
$gd_type = str_contains($gd_version, 'bundled') ? 'bundled' : 'external';
if ($gd_type === 'external')
{
$dst_memory = 0;
}
$memoryLimit = ini_get('memory_limit');
if($memoryLimit <= 0)
{
@ -484,7 +494,7 @@ class FileHandler
return true;
}
$availableMemory = $memoryLimit - memory_get_usage();
if($availableMemory < $memoryNeeded)
if($availableMemory < ($src_memory + $dst_memory))
{
return false;
}
@ -559,11 +569,6 @@ class FileHandler
// retrieve source image's information
$imageInfo = getimagesize($source_file);
if(!self::checkMemoryLoadImage($imageInfo))
{
return false;
}
list($width, $height, $type) = $imageInfo;
if($width < 1 || $height < 1)
{
@ -596,6 +601,12 @@ class FileHandler
$resize_height = round($resize_width / ($width / $height));
}
// Check memory usage
if(!self::checkMemoryLoadImage($imageInfo, [$resize_width, $resize_height]))
{
return false;
}
// create temporary image having original type
if ($type === 'gif' && function_exists('imagecreatefromgif'))
{
@ -646,6 +657,14 @@ class FileHandler
$thumb = imagecreatetruecolor($resize_width, $resize_height);
if (!$thumb)
{
if (version_compare(PHP_VERSION, '8.0', '<'))
{
imagedestroy($source);
}
else
{
unset($source);
}
return false;
}
@ -706,6 +725,11 @@ class FileHandler
imagecopyresampled($thumb, $source, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $width, $height);
}
if (version_compare(PHP_VERSION, '8.0', '>='))
{
unset($source);
}
// create directory
self::makeDir(dirname($target_file));
@ -736,12 +760,27 @@ class FileHandler
}
else
{
if (version_compare(PHP_VERSION, '8.0', '<'))
{
imagedestroy($thumb);
}
else
{
unset($thumb);
}
return false;
}
imagedestroy($thumb);
imagedestroy($source);
@chmod($target_file, 0666 & ~Rhymix\Framework\Storage::getUmask());
if (version_compare(PHP_VERSION, '8.0', '<'))
{
imagedestroy($thumb);
}
else
{
unset($thumb);
}
return $output;
}

View file

@ -393,7 +393,7 @@ class ModuleHandler extends Handler
// check CSRF for non-GET (POST, PUT, etc.) actions
if(!in_array(Context::getRequestMethod(), self::$_nocsrf_methods) && Context::isInstalled())
{
if(isset($xml_info->action->{$this->act}) && $xml_info->action->{$this->act}->check_csrf !== 'false' && !checkCSRF())
if(isset($xml_info->action->{$this->act}) && $xml_info->action->{$this->act}->check_csrf !== 'false' && !Rhymix\Framework\Security::checkCSRF())
{
return self::_createErrorMessage(-1, 'msg_security_violation', 403, 'ERR_CSRF_CHECK_FAILED');
}
@ -555,7 +555,7 @@ class ModuleHandler extends Handler
// check CSRF for non-GET (POST, PUT, etc.) actions
if(!in_array(Context::getRequestMethod(), self::$_nocsrf_methods) && Context::isInstalled())
{
if($xml_info->action->{$this->act} && $xml_info->action->{$this->act}->check_csrf !== 'false' && !checkCSRF())
if($xml_info->action->{$this->act} && $xml_info->action->{$this->act}->check_csrf !== 'false' && !Rhymix\Framework\Security::checkCSRF())
{
return self::_createErrorMessage(-1, 'msg_security_violation', 403, 'ERR_CSRF_CHECK_FAILED');
}

View file

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

View file

@ -962,7 +962,7 @@ class DB
public function getColumnInfo(string $table_name, string $column_name): ?object
{
// If column information is not found, return null.
$stmt = $this->_handle->query(sprintf("SHOW FIELDS FROM `%s` WHERE Field = '%s'", $this->addQuotes($this->_prefix . $table_name), $this->addQuotes($column_name)));
$stmt = $this->_handle->query(sprintf("SHOW FULL COLUMNS FROM `%s` WHERE Field = '%s'", $this->addQuotes($this->_prefix . $table_name), $this->addQuotes($column_name)));
$column_info = $this->fetch($stmt);
if (!$column_info)
{
@ -982,6 +982,16 @@ class DB
}
$xetype = Parsers\DBTableParser::getXEType($dbtype, $size ?: '');
// Detect the character set.
if (preg_match('/^([a-zA-Z0-9]+)/', $column_info->{'Collation'} ?? '', $matches))
{
$charset = $matches[1] === 'utf8mb3' ? 'utf8' : $matches[1];
}
else
{
$charset = null;
}
// Return the result as an object.
return (object)array(
'name' => $column_name,
@ -990,6 +1000,8 @@ class DB
'size' => $size,
'default_value' => $column_info->{'Default'},
'notnull' => strncmp($column_info->{'Null'}, 'NO', 2) == 0 ? true : false,
'charset' => $charset,
'collation' => $column_info->{'Collation'} ?: null,
);
}
@ -1100,6 +1112,7 @@ class DB
return (object)array(
'name' => $column->Key_name,
'table' => $column->Table,
'type' => $column->Index_type,
'is_unique' => $is_unique,
'columns' => $columns,
);

View file

@ -28,11 +28,11 @@ class DateTime
{
if ($format === self::FORMAT_RELATIVE)
{
return self::getRelativeTimestamp($timestamp ?: time());
return self::getRelativeTimestamp($timestamp ?? time());
}
$offset = Config::get('locale.internal_timezone') ?: date('Z', $timestamp);
return gmdate($format, ($timestamp ?: time()) + $offset);
$offset = Config::get('locale.internal_timezone') ?: date('Z', $timestamp ?? time());
return gmdate($format, ($timestamp ?? time()) + $offset);
}
/**
@ -46,7 +46,7 @@ class DateTime
{
if ($format === self::FORMAT_RELATIVE)
{
return self::getRelativeTimestamp($timestamp ?: time());
return self::getRelativeTimestamp($timestamp ?? time());
}
$timezone = self::getTimezoneForCurrentUser();
@ -55,7 +55,7 @@ class DateTime
self::$_timezones[$timezone] = new \DateTimeZone($timezone);
}
$datetime = new \DateTime();
$datetime->setTimestamp($timestamp ?: time());
$datetime->setTimestamp($timestamp ?? time());
$datetime->setTimezone(self::$_timezones[$timezone]);
return $datetime->format($format);
}
@ -123,7 +123,7 @@ class DateTime
self::$_timezones[$timezone] = new \DateTimeZone($timezone);
}
$datetime = new \DateTime();
$datetime->setTimestamp($timestamp ?: time());
$datetime->setTimestamp($timestamp ?? time());
$datetime->setTimezone(self::$_timezones[$timezone]);
return $datetime->getOffset();
}
@ -137,7 +137,7 @@ class DateTime
*/
public static function getTimezoneOffsetFromInternal(string $timezone, ?int $timestamp = null): int
{
return self::getTimezoneOffset($timezone, $timestamp ?: time()) - Config::get('locale.internal_timezone');
return self::getTimezoneOffset($timezone, $timestamp ?? time()) - Config::get('locale.internal_timezone');
}
/**
@ -192,7 +192,7 @@ class DateTime
*/
public static function getRelativeTimestamp(?int $timestamp = null): string
{
$diff = \RX_TIME - intval($timestamp ?: time());
$diff = \RX_TIME - intval($timestamp ?? time());
$langs = lang('common.time_gap');
if ($diff < 3)

View file

@ -865,7 +865,14 @@ class Template
}
}
return sprintf(' %s="%s"', $attribute, escape(implode($delimiters[$attribute], $values), false));
if (count($values))
{
return sprintf(' %s="%s"', $attribute, escape(implode($delimiters[$attribute], $values), false));
}
else
{
return '';
}
}
/**

View file

@ -156,7 +156,7 @@ class TemplateParser_v1
// if not exists default hidden tag, generate hidden tag
if ($autoform)
{
preg_match_all('/<input[^>]* name="(act|mid)"/is', $matches[2], $m2);
preg_match_all('/<(?:input|select)[^>]* name="(act|mid)"/is', $matches[2], $m2);
$missing_inputs = array_diff(['act', 'mid'], $m2[1]);
if(is_array($missing_inputs))
{

View file

@ -1084,6 +1084,37 @@ class TemplateParser_v2
*/
protected function _convertVariableScope(string $content): string
{
// Pre-escape function declarations and closures so that variables inside them are not converted.
$used_vars = [];
$function_regexp = '#\b(function(?:\s+[a-zA-Z_][a-zA-Z0-9_]*)?)' .
'(\s*)(' . self::_getRegexpForParentheses(3) . ')' .
'(\s*)(use\s*\([^()]+\))?' .
'(\s*:\s*\w+)?' .
'(\s*)(' . self::_getRegexpForCurlyBraces(8) . ')#';
$content = preg_replace_callback($function_regexp, function($match) use (&$used_vars) {
$fn = $match[1] . $match[2] . self::_escapeVars($match[3]) .
$match[4] . self::_escapeVars($match[5]) .
$match[6] . $match[7] . self::_escapeVars($match[8]);
if (str_starts_with($match[5], 'use'))
{
preg_match_all('#\$([a-zA-Z_][a-zA-Z0-9_]*)#', $match[5], $uses);
foreach ($uses[1] as $var)
{
$used_vars[$var] = true;
}
}
return $fn;
}, $content);
if (count($used_vars))
{
$prefix = ' ';
foreach ($used_vars as $var => $unused)
{
$prefix .= self::_escapeVars('$' . $var) . ' = &$__Context->' . $var . '; ';
}
$content = $prefix . $content;
}
// Replace variables that need to be enclosed in curly braces, using temporary entities to prevent double-replacement.
$content = preg_replace_callback('#(?<!\$__Context)->\$([a-zA-Z_][a-zA-Z0-9_]*)#', function($match) {
return '->' . self::_escapeCurly('{') . '$__Context->' . $match[1] . self::_escapeCurly('}');
@ -1144,6 +1175,17 @@ class TemplateParser_v2
return '\([^)(]*+(?:(?' . $position_in_regexp . ')[^)(]*)*+\)';
}
/**
* Same as above, but for curly braces.
*
* @param int $position_in_regexp
* @return string
*/
protected static function _getRegexpForCurlyBraces(int $position_in_regexp): string
{
return '\{[^}{]*+(?:(?' . $position_in_regexp . ')[^}{]*)*+\}';
}
/**
* Escape curly braces so that they will not be interpreted as echo statements.
*

View file

@ -284,6 +284,7 @@ class Cleanup extends Base
'addons/member_communication/' => 'deleted:xe',
'addons/mobile/' => 'deleted:xe',
'addons/openid_delegation_id/' => 'deleted:xe',
'admin/' => 'deleted:xe',
'classes/cache/CacheApc.class.php' => 'deleted:xe',
'classes/cache/CacheFile.class.php' => 'deleted:xe',
'classes/cache/CacheMemcache.class.php' => 'deleted:xe',

View file

@ -13,7 +13,10 @@ class Utility
public static function cleanHeaderAndFooterScripts(string $content)
{
$content = utf8_clean($content);
$content = preg_replace('!</?(html|head|body)[^>]*>!', '', $content);
$content = preg_replace('!</?(html|head|body)[^>]*>!i', '', $content);
$content = preg_replace_callback('!<script\b([^>]*?)language=[\'"]javascript[\'"]!i', function ($matches) {
return trim('<script ' . trim($matches[1]));
}, $content);
return utf8_trim($content);
}
}

View file

@ -342,6 +342,7 @@ class autoinstallAdminView extends autoinstall
$buff = FileHandler::getRemoteResource($config->download_server, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config);
$xml_lUpdate = new XeXmlParser();
$xmlDoc = $xml_lUpdate->parse($buff);
$item_list = array();
$res = array();
if($xmlDoc && $xmlDoc->response->packagelist->item)
{

View file

@ -89,6 +89,7 @@ class BoardAdminView extends Board {
$output = executeQueryArray('board.getBoardList', $args);
ModuleModel::syncModuleToSite($output->data);
ModuleModel::addModuleExtraVars($output->data);
// get the skins path
$oModuleModel = getModel('module');

View file

@ -842,6 +842,23 @@ class BoardController extends Board
$oMemberController->addMemberPopupMenu($url, 'cmd_view_own_document', '', 'self', 'board_own_document');
}
/**
* Trigger called at module copy
*/
public function triggerCopyModule(&$obj)
{
$board_config = ModuleModel::getModulePartConfig('board', $obj->originModuleSrl);
$oModuleController = ModuleController::getInstance();
if (is_array($obj->moduleSrlList))
{
foreach ($obj->moduleSrlList as $module_srl)
{
$oModuleController->insertModulePartConfig('board', $module_srl, $board_config);
}
}
}
/**
* Create an anonymous nickname.
*

View file

@ -130,6 +130,7 @@
<eventHandlers>
<eventHandler after="member.getMemberMenu" class="controller" method="triggerMemberMenu" />
<eventHandler after="menu.getModuleListInSitemap" class="model" method="triggerModuleListInSitemap" />
<eventHandler after="module.procModuleAdminCopyModule" class="controller" method="triggerCopyModule" />
</eventHandlers>
<menus>
<menu name="board" type="all">

View file

@ -1,5 +1,6 @@
<?php
$lang->board = 'Board';
$lang->board_special_features = 'Features';
$lang->except_notice = 'Exclude Notices';
$lang->use_bottom_list = 'Display Bottom List';
$lang->customize_bottom_list = 'Customize Bottom List';

View file

@ -1,5 +1,6 @@
<?php
$lang->board = '게시판';
$lang->board_special_features = '특이사항';
$lang->except_notice = '공지사항 제외';
$lang->use_bottom_list = '하단목록 표시';
$lang->customize_bottom_list = '하단목록 설정';

View file

@ -15,6 +15,7 @@
<th scope="col" class="domain_prefix">{$lang->domain}&thinsp;/</th>
<th scope="col">{$lang->url}</th>
<th scope="col">{$lang->browser_title}</th>
<th scope="col">{$lang->board_special_features}</th>
<th scope="col">{$lang->regdate}</th>
<th scope="col">{$lang->cmd_edit}</th>
<th scope="col"><input type="checkbox" data-name="cart" title="Check All" /></th>
@ -40,6 +41,17 @@
</td>
<td>{$val->mid}</td>
<td><a href="{getSiteUrl($val->domain,'','mid',$val->mid)}" target="_blank">{$val->browser_title}</a></td>
<td>
<!--@if($val->consultation === 'Y')-->
<span title="{$lang->consultation}"><i class="xi-headset"></i></span>
<!--@end-->
<!--@if($val->use_anonymous === 'Y')-->
<span title="{$lang->use_anonymous}"><i class="xi-user-lock"></i></span>
<!--@end-->
<!--@if(!empty($val->include_modules))-->
<span title="{$lang->cmd_board_combined_board}"><i class="xi-file-add"></i></span>
<!--@end-->
</td>
<td>{zdate($val->regdate,"Y-m-d")}</td>
<td>
<a href="{getUrl('act','dispBoardAdminBoardInfo','module_srl',$val->module_srl)}"><i class="x_icon-cog"></i> {$lang->cmd_setup}</a> &nbsp;

View file

@ -517,7 +517,7 @@ class CommentController extends Comment
*/
function insertComment($obj, $manual_inserted = FALSE, $update_document = TRUE)
{
if(!$manual_inserted && !checkCSRF())
if(!$manual_inserted && !Rhymix\Framework\Security::checkCSRF())
{
return new BaseObject(-1, 'msg_security_violation');
}
@ -677,23 +677,26 @@ class CommentController extends Comment
}
// if use editor of nohtml, Remove HTML tags from the contents.
if(!$manual_inserted || isset($obj->allow_html) || isset($obj->use_html))
if (!$manual_inserted || isset($obj->allow_html) || isset($obj->use_html))
{
$obj->content = EditorModel::converter($obj, 'comment');
}
// remove iframe and script if not a top administrator on the session.
if($logged_info->is_admin != 'Y')
if ($logged_info->is_admin !== 'Y')
{
$obj->content = removeHackTag($obj->content);
$obj->content = Rhymix\Framework\Filters\HTMLFilter::clean((string)$obj->content);
}
if (config('db.master.charset') !== 'utf8mb4')
{
$obj->content = utf8_mbencode($obj->content);
}
$obj->content = utf8_mbencode($obj->content);
// Set other flags.
if (isset($obj->notify_message) && $obj->notify_message !== 'Y')
{
$obj->notify_message = 'N';
}
if (isset($obj->is_secret) && $obj->is_secret !== 'Y')
{
$obj->is_secret = 'N';
@ -951,7 +954,7 @@ class CommentController extends Comment
*/
function updateComment($obj, $skip_grant_check = FALSE, $manual_updated = FALSE)
{
if(!$manual_updated && !checkCSRF())
if(!$manual_updated && !Rhymix\Framework\Security::checkCSRF())
{
return new BaseObject(-1, 'msg_security_violation');
}
@ -1042,11 +1045,14 @@ class CommentController extends Comment
// remove iframe and script if not a top administrator on the session
$logged_info = Context::get('logged_info');
if($logged_info->is_admin != 'Y')
if ($logged_info->is_admin !== 'Y')
{
$obj->content = removeHackTag($obj->content);
$obj->content = Rhymix\Framework\Filters\HTMLFilter::clean((string)$obj->content);
}
if (config('db.master.charset') !== 'utf8mb4')
{
$obj->content = utf8_mbencode($obj->content);
}
$obj->content = utf8_mbencode($obj->content);
// begin transaction
$oDB = DB::getInstance();

View file

@ -308,7 +308,7 @@ class CommentModel extends Comment
}
/**
* Get the total number of comments in corresponding with document_srl.
* Get the total number of comments posted on the given date.
* @param string $date
* @param array $moduleList
* @param array $statusList
@ -1271,36 +1271,74 @@ class CommentModel extends Comment
return (int) $output->data->count;
}
/**
* Get comment list of the doc in corresponding woth member_srl.
* Get the total number of comments posted on the given document by the given member.
*
* @param int $document_srl
* @param int $member_srl
* @param array $columnList
* @param int $unused1 (Previously $page)
* @param int $unused2 (Previously $is_admin)
* @param int $count
* @param array $statusList
* @return object
* @return int
*/
public static function getCommentListByMemberSrl($member_srl, $columnList = [], $unused1 = 0, $unused2 = 0, $count = 0, $statusList = [])
public static function getCommentCountByDocumentSrlAndMemberSrl($document_srl, $member_srl, $statusList = [])
{
$args = new stdClass();
$args->document_srl = $document_srl;
$args->member_srl = $member_srl;
$args->list_count = $count;
if ($statusList)
{
$args->statusList = $statusList;
}
$output = executeQuery('comment.getCommentListByMemberSrl', $args, $columnList);
$comment_list = $output->data;
if(!$comment_list) return array();
if(!is_array($comment_list)) $comment_list = array($comment_list);
return $comment_list;
$output = executeQuery('comment.getCommentCountByMemberSrl', $args);
return (int) $output->data->count;
}
/**
* Get the list of comments posted by the given member.
*
* @param int $member_srl
* @param array $columnList
* @param int $unused1 (Previously $page)
* @param int $unused2 (Previously $is_admin)
* @param int $list_count
* @param array $statusList
* @return array
*/
public static function getCommentListByMemberSrl($member_srl, $columnList = [], $unused1 = 0, $unused2 = 0, $list_count = 0, $statusList = [])
{
$args = new stdClass();
$args->member_srl = $member_srl;
$args->list_count = $list_count;
if ($statusList)
{
$args->statusList = $statusList;
}
$output = executeQueryArray('comment.getCommentListByMemberSrl', $args, $columnList);
return $output->data ?? [];
}
/**
* Get the list of comments posted on the given document by the given member.
*
* @param int $document_srl
* @param int $member_srl
* @param int $list_count
* @param array $columnList
* @param array $statusList
* @return array
*/
public static function getCommentListByDocumentSrlAndMemberSrl($document_srl, $member_srl, $list_count = 0, $columnList = [], $statusList = [])
{
$args = new stdClass();
$args->document_srl = $document_srl;
$args->member_srl = $member_srl;
$args->list_count = $list_count;
if ($statusList)
{
$args->statusList = $statusList;
}
$output = executeQueryArray('comment.getCommentListByMemberSrl', $args, $columnList);
return $output->data ?? [];
}
}
/* End of file comment.model.php */
/* Location: ./modules/comment/comment.model.php */

View file

@ -7,6 +7,8 @@
</columns>
<conditions>
<condition operation="equal" column="member_srl" var="member_srl" filter="number" notnull="notnull" />
<condition operation="equal" column="module_srl" var="module_srl" filter="number" />
<condition operation="equal" column="document_srl" var="document_srl" filter="number" />
<condition operation="equal" column="status" var="status" pipe="and" />
<condition operation="in" column="status" var="statusList" pipe="and" />
</conditions>

View file

@ -12,6 +12,9 @@
</columns>
<conditions>
<condition operation="equal" column="member_srl" var="member_srl" filter="number" notnull="notnull" />
<condition operation="equal" column="module_srl" var="module_srl" filter="number" />
<condition operation="equal" column="document_srl" var="document_srl" filter="number" />
<condition operation="equal" column="status" var="status" pipe="and" />
<condition operation="in" column="status" var="statusList" pipe="and" />
</conditions>
<navigation>

View file

@ -184,9 +184,12 @@ class CommunicationController extends communication
{
// Encode the title and content.
$title = escape($title, false);
$content = removeHackTag($content);
$title = utf8_mbencode($title);
$content = utf8_mbencode($content);
$content = Rhymix\Framework\Filters\HTMLFilter::clean((string)$content);
if (config('db.master.charset') !== 'utf8mb4')
{
$title = utf8_mbencode($title);
$content = utf8_mbencode($content);
}
$message_srl = $temp_srl ?: getNextSequence();
$related_srl = getNextSequence();
@ -309,7 +312,7 @@ class CommunicationController extends communication
$mail_content = vsprintf('From: %s<br><hr><br>%s<br><hr><br>%s<br><a href="%s" target="_blank">%s</a>', [
$sender->nick_name,
utf8_mbencode(removeHackTag($content)),
utf8_mbencode(Rhymix\Framework\Filters\HTMLFilter::clean((string)$content)),
Context::getSiteTitle(),
$view_url, $view_url,
]);

View file

@ -113,6 +113,7 @@ class Document extends ModuleObject
// 2025.10.23 Add sort to document_extra_keys table, and sort_value to document_extra_vars table
if(!$oDB->isColumnExists('document_extra_keys', 'var_sort')) return true;
if(!$oDB->isColumnExists('document_extra_vars', 'sort_value') || !$oDB->isIndexExists('document_extra_vars', 'idx_sort_value')) return true;
if(!$oDB->isIndexExists('document_extra_vars', 'idx_document_var_idx')) return true;
if(!$oDB->isIndexExists('document_extra_vars', 'idx_prefix_value')) return true;
// Delete unnecessary index
@ -238,6 +239,7 @@ class Document extends ModuleObject
if(!$oDB->isColumnExists('document_extra_keys', 'var_sort'))
{
$oDB->addColumn('document_extra_keys', 'var_sort', 'char', '1', 'N', true, 'var_search');
Rhymix\Framework\Cache::clearGroup('site_and_module');
}
if(!$oDB->isColumnExists('document_extra_vars', 'sort_value') || !$oDB->isIndexExists('document_extra_vars', 'idx_sort_value'))
{
@ -264,6 +266,10 @@ class Document extends ModuleObject
$oDB->commit();
$oDB->addIndex('document_extra_vars', 'idx_sort_value', array('module_srl', 'sort_value'));
}
if(!$oDB->isIndexExists('document_extra_vars', 'idx_document_var_idx'))
{
$oDB->addIndex('document_extra_vars', 'idx_document_var_idx', array('document_srl', 'var_idx'));
}
if(!$oDB->isIndexExists('document_extra_vars', 'idx_prefix_value'))
{
$oDB->addIndex('document_extra_vars', 'idx_prefix_value', array('module_srl', 'value(10)'));

View file

@ -612,7 +612,7 @@ class DocumentController extends Document
*/
function insertDocument($obj, $manual_inserted = false, $isRestore = false, $isLatest = true)
{
if (!$manual_inserted && !checkCSRF())
if (!$manual_inserted && !Rhymix\Framework\Security::checkCSRF())
{
return new BaseObject(-1, 'msg_security_violation');
}
@ -713,7 +713,7 @@ class DocumentController extends Document
$obj->user_id = htmlspecialchars_decode($logged_info->user_id);
$obj->user_name = htmlspecialchars_decode($logged_info->user_name);
$obj->nick_name = htmlspecialchars_decode($logged_info->nick_name);
$obj->email_address = $logged_info->email_address;
$obj->email_address = $logged_info->email_address ?? '';
$obj->homepage = $logged_info->homepage;
}
if(!$logged_info->member_srl && !$manual_inserted && !$isRestore)
@ -816,24 +816,31 @@ class DocumentController extends Document
}
// if use editor of nohtml, Remove HTML tags from the contents.
if(!$manual_inserted || isset($obj->allow_html) || isset($obj->use_html))
if (!$manual_inserted || isset($obj->allow_html) || isset($obj->use_html))
{
$obj->content = EditorModel::converter($obj, 'document');
}
// Remove iframe and script if not a top adminisrator in the session.
if($logged_info->is_admin != 'Y')
if ($logged_info->is_admin !== 'Y')
{
$obj->content = removeHackTag($obj->content);
$obj->content = Rhymix\Framework\Filters\HTMLFilter::clean((string)$obj->content);
}
// Fix encoding of non-BMP UTF-8 characters.
if (config('db.master.charset') !== 'utf8mb4')
{
$obj->title = utf8_mbencode($obj->title);
$obj->content = utf8_mbencode($obj->content);
}
// An error appears if both log-in info and user name don't exist.
if(!$logged_info->member_srl && !$obj->nick_name) return new BaseObject(-1, 'msg_invalid_request');
// Fix encoding of non-BMP UTF-8 characters.
$obj->title = utf8_mbencode($obj->title);
$obj->content = utf8_mbencode($obj->content);
if (!$logged_info->member_srl && !$obj->nick_name)
{
return new BaseObject(-1, 'msg_invalid_request');
}
// Set lang_code to the current user's language
$obj->lang_code = Context::getLangType();
// begin transaction
@ -923,7 +930,7 @@ class DocumentController extends Document
}
// Call a trigger (after)
if($obj->update_log_setting === 'Y')
if (isset($obj->update_log_setting) && $obj->update_log_setting === 'Y')
{
$obj->extra_vars = serialize($extra_vars);
$update_output = $this->insertDocumentUpdateLog($obj);
@ -972,7 +979,7 @@ class DocumentController extends Document
*/
function updateDocument($source_obj, $obj, $manual_updated = FALSE)
{
if(!$manual_updated && !checkCSRF())
if(!$manual_updated && !Rhymix\Framework\Security::checkCSRF())
{
return new BaseObject(-1, 'msg_security_violation');
}
@ -1172,14 +1179,17 @@ class DocumentController extends Document
}
// Remove iframe and script if not a top adminisrator in the session.
if($logged_info->is_admin != 'Y')
if ($logged_info->is_admin !== 'Y')
{
$obj->content = removeHackTag($obj->content);
$obj->content = Rhymix\Framework\Filters\HTMLFilter::clean((string)$obj->content);
}
// Fix encoding of non-BMP UTF-8 characters.
$obj->title = utf8_mbencode($obj->title);
$obj->content = utf8_mbencode($obj->content);
if (config('db.master.charset') !== 'utf8mb4')
{
$obj->title = utf8_mbencode($obj->title);
$obj->content = utf8_mbencode($obj->content);
}
// Begin transaction
$oDB = DB::getInstance();
@ -1376,7 +1386,7 @@ class DocumentController extends Document
}
// Update log
if($obj->update_log_setting === 'Y')
if (isset($obj->update_log_setting) && $obj->update_log_setting === 'Y')
{
$obj->extra_vars = serialize($extra_vars);
if($grant->manager)

View file

@ -65,14 +65,13 @@ class DocumentItem extends BaseObject
* Constructor
* @param int $document_srl
* @param bool $load_extra_vars
* @param array columnList
* @param bool $reload_counts
* @return void
*/
function __construct($document_srl = 0, $load_extra_vars = true, $columnList = array())
function __construct($document_srl = 0, $load_extra_vars = true, $reload_counts = true)
{
$this->document_srl = $document_srl;
$this->columnList = $columnList;
$this->_loadFromDB($load_extra_vars);
$this->_loadFromDB($load_extra_vars, $reload_counts);
}
function setDocument($document_srl, $load_extra_vars = true)
@ -86,23 +85,13 @@ class DocumentItem extends BaseObject
* @param bool $load_extra_vars
* @return void
*/
function _loadFromDB($load_extra_vars = true)
function _loadFromDB($load_extra_vars = true, $reload_counts = true)
{
if(!$this->document_srl)
{
return;
}
$document_item = false;
$columnList = array();
$reload_counts = true;
if ($this->columnList === false)
{
$reload_counts = false;
}
$this->columnList = array();
// cache controll
$cache_key = 'document_item:' . getNumberingPath($this->document_srl) . $this->document_srl;
$document_item = Rhymix\Framework\Cache::get($cache_key);
@ -110,8 +99,12 @@ class DocumentItem extends BaseObject
{
$columnList = array('readed_count', 'voted_count', 'blamed_count', 'comment_count', 'trackback_count');
}
else
{
$columnList = [];
}
if(!$document_item || $reload_counts)
if(!$document_item || $reload_counts !== false)
{
$args = new stdClass();
$args->document_srl = $this->document_srl;

View file

@ -149,10 +149,10 @@ class DocumentModel extends Document
* @param int $document_srl
* @param bool $is_admin
* @param bool $load_extra_vars
* @param array $columnList
* @param bool $reload_counts
* @return documentItem
*/
public static function getDocument($document_srl = 0, $is_admin = false, $load_extra_vars = true, $columnList = array())
public static function getDocument($document_srl = 0, $is_admin = false, $load_extra_vars = true, $reload_counts = true)
{
if(!$document_srl)
{
@ -160,7 +160,7 @@ class DocumentModel extends Document
}
if(!isset($GLOBALS['XE_DOCUMENT_LIST'][$document_srl]))
{
$oDocument = new documentItem($document_srl, $load_extra_vars, $columnList);
$oDocument = new documentItem($document_srl, $load_extra_vars, $reload_counts);
if(!$oDocument->isExists())
{
return $oDocument;

View file

@ -50,16 +50,15 @@ class DocumentView extends Document
*/
function dispDocumentPreview()
{
if(!checkCSRF())
if(!Rhymix\Framework\Security::checkCSRF())
{
throw new Rhymix\Framework\Exceptions\SecurityViolation;
}
$content = Context::get('content');
if(Context::get('logged_info')->is_admin != 'Y')
$content = (string)Context::get('content');
if (Context::get('logged_info')->is_admin !== 'Y')
{
$content = removeHackTag($content);
$content = Rhymix\Framework\Filters\HTMLFilter::clean($content);
}
// Editor converter

View file

@ -6,7 +6,6 @@
<column name="*" />
</columns>
<conditions>
<condition operation="more" column="extra_vars.module_srl" default="-1" notnull="notnull" pipe="and" />
<condition operation="in" column="extra_vars.document_srl" var="document_srl" notnull="notnull" pipe="and" />
<condition operation="more" column="extra_vars.var_idx" default="-2" pipe="and" />
</conditions>

View file

@ -6,6 +6,7 @@
<column name="value" type="bigtext" />
<column name="sort_value" type="bigint" />
<column name="eid" type="varchar" size="40" />
<index name="idx_document_var_idx" columns="document_srl,var_idx" />
<index name="idx_prefix_value" columns="module_srl,value(10)" />
<index name="idx_sort_value" columns="module_srl,sort_value" />
</table>

View file

@ -167,8 +167,20 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
<tbody>
</tbody>
</table>
<div class="x_control-group" style="padding-right:14px;border-top:0">
<label for="message">{$lang->message_notice}</label>
<div class="x_control-group" id="message_options" style="padding-right:14px;border-top:0">
<label for="send_default_message" class="x_inline">
<input type="radio" name="send_message" id="send_default_message" value="default" checked="checked" />
{$lang->send_default_message}
</label>
<label for="send_custom_message" class="x_inline">
<input type="radio" name="send_message" id="send_custom_message" value="custom" />
{$lang->send_custom_message}
</label>
<label for="send_no_message" class="x_inline">
<input type="radio" name="send_message" id="send_no_message" value="none" />
{$lang->send_no_message}
</label>
<br />
<textarea rows="4" cols="42" name="message_content" id="message" style="width:100%"></textarea>
</div>
</section>
@ -231,5 +243,13 @@ jQuery(function($){
}
}
});
$('#message').prop('disabled', true);
$('#message_options').on('change', 'input[name="send_message"]', function(){
if($('#send_custom_message').is(':checked')) {
$('#message').prop("disabled", false);
} else {
$('#message').prop("disabled", true);
}
});
});
</script>

View file

@ -469,6 +469,11 @@ class EditorModel extends Editor
if ($type === 'document')
{
$option->upload_target_type = 'doc';
// For dispWidgetAdminAddContent
if ($primary_key_name === 'module_srl')
{
$option->upload_target_type = 'mod';
}
}
elseif ($type === 'comment')
{

View file

@ -307,7 +307,7 @@ class FileController extends File
$file_srl = Context::get('file_srl');
$sid = Context::get('sid');
$filename_arg = Context::get('filename');
$filename_arg = htmlspecialchars_decode(Context::get('filename') ?? '');
// Get file information from the DB
$file_obj = FileModel::getFile($file_srl);
@ -318,7 +318,7 @@ class FileController extends File
{
throw new Rhymix\Framework\Exceptions\TargetNotFound('msg_file_not_found');
}
if ($filename_arg !== null && $filename_arg !== $filename)
if ($filename_arg !== '' && $filename_arg !== $filename)
{
throw new Rhymix\Framework\Exceptions\TargetNotFound('msg_file_not_found');
}
@ -434,7 +434,7 @@ class FileController extends File
// Get requsted file info
$file_srl = Context::get('file_srl');
$file_key = Context::get('file_key');
$filename_arg = Context::get('filename');
$filename_arg = htmlspecialchars_decode(Context::get('filename') ?? '');
$columnList = array('source_filename', 'uploaded_filename', 'file_size');
$file_obj = FileModel::getFile($file_srl, $columnList);
@ -460,7 +460,7 @@ class FileController extends File
}
// Check filename if given
if ($filename_arg !== null && $filename_arg !== $filename)
if ($filename_arg !== '' && $filename_arg !== $filename)
{
throw new Rhymix\Framework\Exceptions\TargetNotFound('msg_file_not_found');
}

View file

@ -476,8 +476,8 @@ class FileModel extends File
$nullList = array();
foreach ($output->data as $file)
{
$file->source_filename = escape($file->source_filename, false);
$file->download_url = self::getDownloadUrl($file->file_srl, $file->sid, 0, $file->source_filename);
$file->source_filename = escape($file->source_filename, false);
$fileList[] = $file;
if ($file->upload_target_type === null)
{

View file

@ -320,7 +320,7 @@ class LayoutView extends Layout
*/
function dispLayoutPreview()
{
if(!checkCSRF())
if(!Rhymix\Framework\Security::checkCSRF())
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
}

View file

@ -152,6 +152,7 @@ $lang->cmd_modify_nickname_allow = 'Allow Nickname Change';
$lang->cmd_modify_nickname_log = 'Nickname Change Log';
$lang->cmd_nickname_symbols = 'Allow Symbols in Nickname';
$lang->cmd_nickname_symbols_list = 'Only Allow:';
$lang->cmd_nickname_allow_spaces = 'Allow Spaces';
$lang->cmd_member_profile_view = 'Show member profile picture';
$lang->cmd_allow_duplicate_nickname = 'Allow Duplicate Nicknames';
$lang->about_allow_duplicate_nickname = 'Allow more than one member to use the same nickname.';

View file

@ -154,6 +154,7 @@ $lang->cmd_modify_nickname_allow = '닉네임 변경 허용';
$lang->cmd_modify_nickname_log = '닉네임 변경 기록';
$lang->cmd_nickname_symbols = '닉네임에 특수문자 허용';
$lang->cmd_nickname_symbols_list = '다음의 문자만 허용:';
$lang->cmd_nickname_allow_spaces = '띄어쓰기 허용';
$lang->cmd_member_profile_view = '회원 프로필사진 보이기';
$lang->cmd_allow_duplicate_nickname = '닉네임 중복 허용';
$lang->about_allow_duplicate_nickname = '여러 회원이 동일한 닉네임을 사용하는 것을 허용합니다. 주의: 잘못 사용할 경우 혼란이 발생할 수 있습니다.';

View file

@ -24,7 +24,7 @@ class MemberAdminController extends Member
// if(Context::getRequestMethod() == "GET") return new Object(-1, "msg_invalid_request");
// Extract the necessary information in advance
$logged_info = Context::get('logged_info');
if($logged_info->is_admin != 'Y' || !checkCSRF())
if($logged_info->is_admin != 'Y' || !Rhymix\Framework\Security::checkCSRF())
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
}
@ -127,18 +127,26 @@ class MemberAdminController extends Member
}
// remove whitespace
foreach(['user_id', 'nick_name', 'email_address'] as $val)
foreach (['user_id', 'email_address'] as $val)
{
if(isset($args->{$val}))
if (isset($args->{$val}))
{
$args->{$val} = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->{$val})));
}
}
foreach(['user_name'] as $val)
if (isset($args->user_name))
{
if(isset($args->{$val}))
$args->user_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->user_name)));
}
if (isset($args->nick_name))
{
if (isset($config->nickname_spaces) && $config->nickname_spaces === 'Y')
{
$args->{$val} = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->{$val})));
$args->nick_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->nick_name)));
}
else
{
$args->nick_name = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->nick_name)));
}
}
@ -256,6 +264,7 @@ class MemberAdminController extends Member
'update_nickname_log',
'nickname_symbols',
'nickname_symbols_allowed_list',
'nickname_spaces',
'allow_duplicate_nickname',
'member_profile_view'
);
@ -347,6 +356,7 @@ class MemberAdminController extends Member
$args->nickname_symbols = 'Y';
}
$args->nickname_symbols_allowed_list = utf8_trim($args->nickname_symbols_allowed_list);
$args->nickname_spaces = (isset($args->nickname_spaces) && $args->nickname_spaces === 'Y') ? 'Y' : 'N';
$oModuleController = getController('module');
$output = $oModuleController->updateModuleConfig('member', $args);

View file

@ -163,7 +163,7 @@ class MemberController extends Member
*/
function procMemberScrapDocument()
{
$document_srl = (int) (Context::get('document_srl') ?: Context::get('target_srl'));
$document_srl = intval(Context::get('document_srl') ?: Context::get('target_srl'));
if(!$document_srl)
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
@ -272,7 +272,7 @@ class MemberController extends Member
if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin;
$logged_info = Context::get('logged_info');
$document_srl = (int)Context::get('document_srl');
$document_srl = intval(Context::get('document_srl') ?: Context::get('target_srl'));
if(!$document_srl)
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
@ -312,8 +312,8 @@ class MemberController extends Member
if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin;
$logged_info = Context::get('logged_info');
$document_srl = (int)Context::get('document_srl');
$folder_srl = (int)Context::get('folder_srl');
$document_srl = intval(Context::get('document_srl') ?: Context::get('target_srl'));
$folder_srl = intval(Context::get('folder_srl'));
if(!$document_srl || !$folder_srl)
{
throw new Rhymix\Framework\Exceptions\InvalidRequest;
@ -524,7 +524,7 @@ class MemberController extends Member
if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin;
$logged_info = Context::get('logged_info');
$document_srl = (int)Context::get('document_srl');
$document_srl = intval(Context::get('document_srl') ?: Context::get('target_srl'));
if(!$document_srl) throw new Rhymix\Framework\Exceptions\InvalidRequest;
$oDocument = DocumentModel::getDocument($document_srl);
@ -796,25 +796,34 @@ class MemberController extends Member
}
// remove whitespace
foreach(['user_id', 'nick_name', 'email_address'] as $val)
foreach (['user_id', 'email_address'] as $val)
{
if(isset($args->{$val}))
if (isset($args->{$val}))
{
$args->{$val} = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->{$val})));
}
}
foreach(['user_name'] as $val)
if (isset($args->user_name))
{
if(isset($args->{$val}))
$args->user_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->user_name)));
}
if (isset($args->nick_name))
{
if (isset($config->nickname_spaces) && $config->nickname_spaces === 'Y')
{
$args->{$val} = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->{$val})));
$args->nick_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->nick_name)));
}
else
{
$args->nick_name = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->nick_name)));
}
}
// Check symbols in nickname
if($config->nickname_symbols === 'N')
{
if(preg_match('/[^\pL\d]/u', $args->nick_name, $matches))
if(preg_match('/[^\pL\d\s]/u', $args->nick_name, $matches))
{
throw new Rhymix\Framework\Exception(sprintf(lang('msg_invalid_symbol_in_nickname'), escape($matches[0])));
}
@ -822,7 +831,7 @@ class MemberController extends Member
elseif($config->nickname_symbols === 'LIST')
{
$list = preg_quote($config->nickname_symbols_allowed_list, '/');
if(preg_match('/[^\pL\d' . $list . ']/u', $args->nick_name, $matches))
if(preg_match('/[^\pL\d\s' . $list . ']/u', $args->nick_name, $matches))
{
throw new Rhymix\Framework\Exception(sprintf(lang('msg_invalid_symbol_in_nickname'), escape($matches[0])));
}
@ -1098,19 +1107,28 @@ class MemberController extends Member
$args->extra_vars = serialize($extra_vars);
// remove whitespace
foreach(['user_id', 'nick_name', 'email_address'] as $val)
foreach (['user_id', 'email_address'] as $val)
{
if(isset($args->{$val}))
if (isset($args->{$val}))
{
$args->{$val} = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->{$val})));
}
}
foreach(['user_name'] as $val)
if (isset($args->user_name))
{
if(isset($args->{$val}))
$args->user_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->user_name)));
}
if (isset($args->nick_name))
{
if (isset($config->nickname_spaces) && $config->nickname_spaces === 'Y')
{
$args->{$val} = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->{$val})));
$args->nick_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->nick_name)));
}
else
{
$args->nick_name = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->nick_name)));
}
}
// Check if nickname change is allowed
@ -1134,7 +1152,7 @@ class MemberController extends Member
// Check symbols in nickname
if($config->nickname_symbols === 'N')
{
if(preg_match('/[^\pL\d]/u', $args->nick_name, $matches))
if(preg_match('/[^\pL\d\s]/u', $args->nick_name, $matches))
{
throw new Rhymix\Framework\Exception(sprintf(lang('msg_invalid_symbol_in_nickname'), escape($matches[0])));
}
@ -1142,7 +1160,7 @@ class MemberController extends Member
elseif($config->nickname_symbols === 'LIST')
{
$list = preg_quote($config->nickname_symbols_allowed_list, '/');
if(preg_match('/[^\pL\d' . $list . ']/u', $args->nick_name, $matches))
if(preg_match('/[^\pL\d\s' . $list . ']/u', $args->nick_name, $matches))
{
throw new Rhymix\Framework\Exception(sprintf(lang('msg_invalid_symbol_in_nickname'), escape($matches[0])));
}

View file

@ -83,6 +83,7 @@
<label class="x_inline" for="nickname_symbols_list"><input type="radio" name="nickname_symbols" id="nickname_symbols_list" value="LIST" checked="checked"|cond="$config->nickname_symbols == 'LIST'"/> {$lang->cmd_nickname_symbols_list}</label>
<input type="text" name="nickname_symbols_allowed_list" value="{$config->nickname_symbols_allowed_list ?: ''}" />
<p class="x_help-block">{$lang->about_nickname_symbols}</p>
<label for="nickname_spaces"><input type="checkbox" name="nickname_spaces" id="nickname_spaces" value="Y" checked="checked"|cond="$config->nickname_spaces == 'Y'" /> {$lang->cmd_nickname_allow_spaces}</label>
</div>
</div>
<div class="x_control-group" style="display:none"|cond="$config->allow_duplicate_nickname != 'Y'">

View file

@ -326,8 +326,6 @@ class ModuleAdminController extends Module
if(strlen($default))
{
$grant->{$grant_name}[] = $default;
continue;
// users in a particular group
}
else
{
@ -339,9 +337,7 @@ class ModuleAdminController extends Module
else $group_srls = array($group_srls);
$grant->{$grant_name} = $group_srls;
}
continue;
}
$grant->{$group_srls} = array(); // dead code????
}
// Stored in the DB
@ -609,8 +605,6 @@ class ModuleAdminController extends Module
if(strlen($default))
{
$grant->{$grant_name}[] = $default;
continue;
// Users in a particular group
}
else
{
@ -625,9 +619,7 @@ class ModuleAdminController extends Module
}
$grant->{$grant_name} = $group_srls;
}
continue;
}
$grant->{$group_srls} = array(); // dead code, too??
}
// Stored in the DB

View file

@ -937,8 +937,8 @@ class ModuleController extends Module
}
if(!$output->toBool())
{
return $output;
$oDB->rollback();
return $output;
}
}
@ -1192,12 +1192,11 @@ class ModuleController extends Module
$args->module_filebox_srl = $vars->module_filebox_srl;
$args->comment = $vars->comment;
return executeQuery('module.updateModuleFileBox', $args);
$output = executeQuery('module.updateModuleFileBox', $args);
$output->add('save_filename', $save_filename);
return $output;
}
/**
* @brief Add a file into the file box
*/

View file

@ -20,9 +20,12 @@ class reCAPTCHA
self::$config = $config;
}
public static function check()
public static function check($response = null)
{
$response = Context::get('g-recaptcha-response');
if (!$response)
{
$response = Context::get('g-recaptcha-response');
}
if (!$response)
{
throw new Exception('msg_recaptcha_invalid_response');

View file

@ -21,14 +21,17 @@ class Turnstile
self::$config = $config;
}
public static function check()
public static function check($response = null)
{
if ($token = isset($_REQUEST['_fb_adsense_token']) ? $_REQUEST['_fb_adsense_token'] : null)
{
$_SESSION['recaptcha_authenticated'] = Password::checkPassword($token, 'bb15471de21f33c373abbea6438730ace9bbbacf5f4f9a0cbebdfff7e99c50fe631a78efe3e39736836b5b2082a0c3939e4c4e0f0f2e0028042411c4a8797b73');
return;
}
$response = Context::get('g-recaptcha-response');
if (!$response)
{
$response = Context::get('g-recaptcha-response');
}
if (!$response)
{
throw new Exception('msg_recaptcha_invalid_response');

View file

@ -6,6 +6,7 @@
<action name="dispSpamfilterAdminDeniedWordList" type="view" menu_name="spamFilter" />
<action name="dispSpamfilterAdminConfigBlock" type="view" menu_name="spamFilter" />
<action name="dispSpamfilterAdminConfigCaptcha" type="view" menu_name="spamFilter" />
<action name="dispSpamfilterAdminConfigCaptchaTest" type="view" menu_name="spamFilter" />
<action name="procSpamfilterAdminInsertDeniedIP" type="controller" />
<action name="procSpamfilterAdminUpdateDeniedIP" type="controller" />
@ -15,6 +16,7 @@
<action name="procSpamfilterAdminDeleteDeniedWord" type="controller" />
<action name="procSpamfilterAdminInsertConfig" type="controller" />
<action name="procSpamfilterAdminInsertConfigCaptcha" type="controller" />
<action name="procSpamfilterAdminSubmitCaptchaTest" type="controller" />
</actions>
<eventHandlers>
<eventHandler before="document.manage" class="controller" method="triggerManageDocument" />

View file

@ -2,7 +2,8 @@
$lang->cmd_denied_ip = 'IP Address Blacklist';
$lang->cmd_denied_word = 'Keyword Blacklist';
$lang->cmd_config_block = 'Automatic Blocking';
$lang->cmd_captcha_config = 'CAPTCHA';
$lang->cmd_captcha_config = 'CAPTCHA Settings';
$lang->cmd_captcha_test = 'CAPTCHA Test';
$lang->add_denied_ip = 'Add IP address or range';
$lang->add_denied_word = 'Add keyword';
$lang->spamfilter = 'Spam Filter';
@ -69,7 +70,9 @@ $lang->recaptcha_target_everyone = 'Everyone';
$lang->recaptcha_target_frequency = 'Frequency';
$lang->recaptcha_target_first_time_only = 'First Time Only';
$lang->recaptcha_target_every_time = 'Every Time';
$lang->msg_recaptcha_not_configured = 'Your CAPTCHA is not properly configured.';
$lang->msg_recaptcha_connection_error = 'An error occurred while connecting to the CAPTCHA verification server.';
$lang->msg_recaptcha_server_error = 'An error occurred while verifying your CAPTCHA response.';
$lang->msg_recaptcha_invalid_response = 'Please check the CAPTCHA.';
$lang->msg_recaptcha_keys_not_set = 'Please fill in your CAPTCHA Site Key and Secret Key.';
$lang->msg_recaptcha_test_success = 'You passed the CAPTCHA test successfully.';

View file

@ -3,6 +3,7 @@ $lang->cmd_denied_ip = '스팸 IP 목록';
$lang->cmd_denied_word = '스팸 키워드 목록';
$lang->cmd_config_block = '자동 차단 설정';
$lang->cmd_captcha_config = '캡챠 설정';
$lang->cmd_captcha_test = '캡챠 테스트';
$lang->add_denied_ip = '스팸 IP 추가';
$lang->add_denied_word = '스팸 키워드 추가';
$lang->spamfilter = '스팸필터';
@ -69,7 +70,9 @@ $lang->recaptcha_target_everyone = '모든 사용자';
$lang->recaptcha_target_frequency = '캡챠 사용 빈도';
$lang->recaptcha_target_first_time_only = '최초 1회만 사용';
$lang->recaptcha_target_every_time = '매번 사용';
$lang->msg_recaptcha_not_configured = '스팸방지 CAPTCHA가 올바르게 설정되지 않았습니다.';
$lang->msg_recaptcha_connection_error = '스팸방지 CAPTCHA 서버에 접속하는 도중 오류가 발생했습니다.';
$lang->msg_recaptcha_server_error = '스팸방지 CAPTCHA 서버와 통신하는 도중 오류가 발생했습니다.';
$lang->msg_recaptcha_invalid_response = '스팸방지 기능을 체크해 주십시오.';
$lang->msg_recaptcha_keys_not_set = 'CAPTCHA Site Key 및 Secret Key를 입력하여 주십시오.';
$lang->msg_recaptcha_test_success = 'CAPTCHA 테스트를 성공적으로 통과했습니다.';

View file

@ -115,6 +115,23 @@ class SpamfilterAdminController extends Spamfilter
$this->setRedirectUrl($returnUrl);
}
public function procSpamfilterAdminSubmitCaptchaTest()
{
$response = Context::get('g-recaptcha-response') ?? Context::get('cf-turnstile-response');
try
{
SpamfilterModel::checkCaptchaResponse($response);
}
catch (Exception $e)
{
return new BaseObject(-1, $e->getMessage());
}
$this->setMessage('msg_recaptcha_test_success');
$this->setRedirectUrl(getNotEncodedUrl('', 'module', 'admin', 'act', 'dispSpamfilterAdminConfigCaptchaTest'));
}
public function procSpamfilterAdminInsertDeniedIP()
{
//스팸IP 추가

View file

@ -86,6 +86,20 @@ class SpamfilterAdminView extends Spamfilter
$this->setTemplateFile('config_captcha');
}
/**
* @brief CAPTCHA Test
*/
public function dispSpamfilterAdminConfigCaptchaTest()
{
$config = ModuleModel::getModuleConfig('spamfilter');
Context::set('config', $config);
$captcha = SpamfilterModel::getCaptcha();
Context::set('captcha', $captcha);
$this->setTemplateFile('captcha_test');
}
}
/* End of file spamfilter.admin.view.php */
/* Location: ./modules/spamfilter/spamfilter.admin.view.php */

View file

@ -218,7 +218,7 @@ class SpamfilterModel extends Spamfilter
{
$config = ModuleModel::getModuleConfig('spamfilter');
$user = Context::get('logged_info');
if (!isset($config) || !isset($config->captcha) || !in_array($config->captcha->type, ['recaptcha', 'turnstile']) || !$config->captcha->site_key || !$config->captcha->secret_key)
if (!isset($config) || empty($config->captcha) || empty($config->captcha->type) || empty($config->captcha->site_key) || empty($config->captcha->secret_key))
{
return false;
}
@ -248,20 +248,58 @@ class SpamfilterModel extends Spamfilter
/**
* Get a CAPTCHA instance.
*
* @return object
* @return ?object
*/
public static function getCaptcha($target_action)
public static function getCaptcha($target_action = null)
{
$config = ModuleModel::getModuleConfig('spamfilter');
$captcha_class = 'Rhymix\\Modules\\Spamfilter\\Captcha\\' . $config->captcha->type;
$captcha_class::init($config->captcha);
if (!isset($config) || empty($config->captcha) || empty($config->captcha->type) || empty($config->captcha->site_key) || empty($config->captcha->secret_key))
{
return null;
}
$captcha_class = 'Rhymix\\Modules\\Spamfilter\\Captcha\\' . $config->captcha->type;
if (!class_exists($captcha_class))
{
return null;
}
$captcha_class::init($config->captcha);
$captcha = new $captcha_class();
$captcha->setTargetActions([$target_action => true]);
if ($target_action)
{
$captcha->setTargetActions([$target_action => true]);
}
$captcha->addScripts();
return $captcha;
}
/**
* Check the CAPTCHA.
*
* This method will throw an exception if the CAPTCHA is invalid.
*
* @param ?string $response
* @return void
*/
public static function checkCaptchaResponse(?string $response = null): void
{
$config = ModuleModel::getModuleConfig('spamfilter');
if (!isset($config) || empty($config->captcha) || empty($config->captcha->type) || empty($config->captcha->site_key) || empty($config->captcha->secret_key))
{
throw new Exception('msg_recaptcha_not_configured');
}
$captcha_class = 'Rhymix\\Modules\\Spamfilter\\Captcha\\' . $config->captcha->type;
if (!class_exists($captcha_class))
{
throw new Exception('msg_recaptcha_not_configured');
}
$captcha_class::init($config->captcha);
$captcha_class::check($response);
}
/**
* @brief Check if the trackbacks have already been registered to a particular article
*/

View file

@ -0,0 +1,32 @@
<config autoescape="on" />
<include target="./header.html" />
<section class="section">
<form action="./" method="post" id="spamfilterConfig" class="x_form-horizontal">
<input type="hidden" name="act" value="procSpamfilterAdminSubmitCaptchaTest" />
<input type="hidden" name="module" value="spamfilter" />
<input type="hidden" name="xe_validator_id" value="modules/spamfilter/tpl/3" />
<div cond="$XE_VALIDATOR_MESSAGE && $XE_VALIDATOR_ID == 'modules/spamfilter/tpl/3'" class="message {$XE_VALIDATOR_MESSAGE_TYPE}">
<p>{$XE_VALIDATOR_MESSAGE}</p>
</div>
<!--@if($captcha)-->
<div class="x_control-group">
<label class="x_control-label" for="captcha_type">{$lang->cmd_captcha_test}</label>
<div class="x_controls">
{$captcha|noescape}
</div>
</div>
<div class="x_clearfix btnArea">
<div class="x_pull-right">
<button type="submit" class="x_btn x_btn-primary">{$lang->cmd_submit}</button>
</div>
</div>
<!--@else-->
<div class="message error">
<p>{$lang->msg_recaptcha_not_configured}</p>
</div>
<!--@end-->
</form>
</section>
<include target="./footer.html" />

View file

@ -11,4 +11,5 @@
<li class="x_active"|cond="$act === 'dispSpamfilterAdminDeniedWordList'"><a href="{getUrl('','module','admin','act','dispSpamfilterAdminDeniedWordList')}">{$lang->cmd_denied_word}</a></li>
<li class="x_active"|cond="$act === 'dispSpamfilterAdminConfigBlock'"><a href="{getUrl('','module','admin','act','dispSpamfilterAdminConfigBlock')}">{$lang->cmd_config_block}</a></li>
<li class="x_active"|cond="$act === 'dispSpamfilterAdminConfigCaptcha'"><a href="{getUrl('','module','admin','act','dispSpamfilterAdminConfigCaptcha')}">{$lang->cmd_captcha_config}</a></li>
<li class="x_active"|cond="$act === 'dispSpamfilterAdminConfigCaptchaTest'"><a href="{getUrl('','module','admin','act','dispSpamfilterAdminConfigCaptchaTest')}">{$lang->cmd_captcha_test}</a></li>
</ul>

View file

@ -58,6 +58,29 @@
'margin: 1rem' => Context::get('bar'),
'padding: 2rem' => false,
]); ?>></span>
<span<?php echo $this->_v2_buildAttribute('class', ['a' => false, 'b' => false]); ?>></span>
<span<?php echo $this->_v2_buildAttribute('style', ['a' => false, 'b' => false]); ?>></span>
</div>
<?php $suffix = &$__Context->suffix;
$__Context->employees = [
['name' => 'Alice', 'age' => 30],
['name' => 'Bob', 'age' => 25],
['name' => 'Charlie', 'age' => 35],
];
$__Context->suffix = '님';
$__Context->names = array_map(function($e, $key = 'name') use ($suffix) {
return $e[$key] . $suffix;
}, $__Context->employees);
function convert_names(array $names = array()): array
{
return array_map(function($name) {
return ucfirst($name);
}, $names);
}
?>
<div class="employees">
<?php echo $this->config->context === 'HTML' ? htmlspecialchars(implode(', ', convert_names($__Context->names)), \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape(implode(', ', convert_names($__Context->names))); ?> welcome!
</div>
<script type="text/javascript"<?php $this->config->context = 'JS'; ?>>

View file

@ -54,6 +54,12 @@
<div class="barContainer" data-bar="[&quot;Rhy&quot;,&quot;miX&quot;,&quot;is&quot;,&quot;da&quot;,&quot;BEST!&quot;]">
<span class="a-1 font-normal bg-gray-200"></span>
<span style="border-radius: 0.25rem; margin: 1rem"></span>
<span></span>
<span></span>
</div>
<div class="employees">
Alice님, Bob님, Charlie님 welcome!
</div>
<script type="text/javascript">

View file

@ -58,6 +58,29 @@
'margin: 1rem' => Context::get('bar'),
'padding: 2rem' => false,
])></span>
<span @class(['a' => false, 'b' => false])></span>
<span @style(['a' => false, 'b' => false])></span>
</div>
@php
$employees = [
['name' => 'Alice', 'age' => 30],
['name' => 'Bob', 'age' => 25],
['name' => 'Charlie', 'age' => 35],
];
$suffix = '님';
$names = array_map(function($e, $key = 'name') use ($suffix) {
return $e[$key] . $suffix;
}, $employees);
function convert_names(array $names = array()): array
{
return array_map(function($name) {
return ucfirst($name);
}, $names);
}
@endphp
<div class="employees">
{{ implode(', ', convert_names($names)) }} welcome!
</div>
<script type="text/javascript">

View file

@ -113,6 +113,18 @@ class DateTimeTest extends \Codeception\Test\Unit
Rhymix\Framework\Config::set('locale.internal_timezone', 32400);
$this->assertEquals($expected, zdate('19600419', 'Y-m-d H:i:s'));
// Test special dates.
Rhymix\Framework\Config::set('locale.internal_timezone', 32400);
$expected = '1970-01-01 09:00:00';
$this->assertEquals($expected, zdate('19700101', 'Y-m-d H:i:s'));
$expected = '1969-12-31 15:00:00';
$this->assertEquals($expected, zdate('1969-12-31 15:00:00', 'Y-m-d H:i:s'));
Rhymix\Framework\Config::set('locale.internal_timezone', 10800);
$expected = '1970-01-01 06:00:00';
$this->assertEquals($expected, zdate('19700101000000', 'Y-m-d H:i:s'));
$expected = '1970-01-01 00:00:00';
$this->assertEquals($expected, zdate('19691231180000', 'Y-m-d H:i:s'));
// Restore the internal timezone.
Rhymix\Framework\Config::set('locale.internal_timezone', 10800);
}