From f9119c8ba39e7618109b510d521e0a0fe4c678d2 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 26 Apr 2026 14:47:52 +0900 Subject: [PATCH 01/36] Fix incorrect detection of ChatGPT-generated PNG as dangerous SVG #2703 --- .../framework/filters/FileContentFilter.php | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/common/framework/filters/FileContentFilter.php b/common/framework/filters/FileContentFilter.php index 6bfa5cf53..d53525118 100644 --- a/common/framework/filters/FileContentFilter.php +++ b/common/framework/filters/FileContentFilter.php @@ -51,28 +51,39 @@ class FileContentFilter } // Check other image files. - if (in_array($ext, array('jpg', 'jpeg', 'png', 'gif')) && $mime_type !== false && $mime_type !== 'image') + if (preg_match('/^(jpe?g|png|gif|webp)$/', $ext)) { - fclose($fp); - return false; + if ($mime_type !== false && $mime_type !== 'image') + { + fclose($fp); + return false; + } + $image_info = @getimagesize($file); + if ($image_info) + { + $skip_xml = true; + } } // Check audio and video files. - if (preg_match('/(wm[va]|mpe?g|avi|flv|mp[1-4]|as[fx]|wav|midi?|moo?v|qt|r[am]{1,2}|m4v)$/', $file) && $mime_type !== false && $mime_type !== 'audio' && $mime_type !== 'video') + if (preg_match('/^(wm[va]|mpe?g|avi|flv|mp[1-4]|as[fx]|wav|midi?|moo?v|qt|r[am]{1,2}|m4v)$/', $ext)) { - fclose($fp); - return false; + if ($mime_type !== false && $mime_type !== 'audio' && $mime_type !== 'video') + { + fclose($fp); + return false; + } } // Check XML files. - if (($ext === 'xml' || $is_xml) && !self::_checkXML($fp, 0, $filesize)) + if (($ext === 'xml' || ($is_xml && !$skip_xml)) && !self::_checkXML($fp, 0, $filesize)) { fclose($fp); return false; } // Check HTML files. - if (($ext === 'html' || $ext === 'shtml' || $ext === 'xhtml' || $ext === 'phtml' || ($is_xml && !$skip_xml)) && !self::_checkHTML($fp, 0, $filesize)) + if ((preg_match('/html$/', $ext) || ($is_xml && !$skip_xml)) && !self::_checkHTML($fp, 0, $filesize)) { fclose($fp); return false; From 4090d6f5d7eb26da9969641b61ba95471215efd9 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 26 Apr 2026 14:55:57 +0900 Subject: [PATCH 02/36] Hide friend-related options and error messages if friend feature is disabled #2702 --- modules/communication/communication.controller.php | 9 ++++++++- modules/communication/communication.view.php | 7 +++++++ modules/communication/skins/default/messages.html | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/communication/communication.controller.php b/modules/communication/communication.controller.php index 72ff49fa9..fe9c1e369 100644 --- a/modules/communication/communication.controller.php +++ b/modules/communication/communication.controller.php @@ -117,7 +117,14 @@ class CommunicationController extends communication { if(!$oCommunicationModel->isFriend($receiver_member_info->member_srl)) { - throw new Rhymix\Framework\Exception('msg_allow_message_to_friend'); + if ($config->enable_friend === 'Y') + { + throw new Rhymix\Framework\Exception('msg_allow_message_to_friend'); + } + else + { + throw new Rhymix\Framework\Exception('msg_disallow_message'); + } } } else if($receiver_member_info->allow_message == 'N') diff --git a/modules/communication/communication.view.php b/modules/communication/communication.view.php index 22b90a54c..11bbca94b 100644 --- a/modules/communication/communication.view.php +++ b/modules/communication/communication.view.php @@ -127,6 +127,13 @@ class CommunicationView extends communication $page = max(1, intval(Context::get('page'))); $output = $oCommunicationModel->getMessages($message_type, $columnList, $search_target, $search_keyword, $page); + $allow_message_type = lang('allow_message_type')->getArrayCopy(); + if ($this->config->allow_friend === 'N') + { + unset($allow_message_type['F']); + } + Context::set('allow_message_type', $allow_message_type); + // set a template file Context::set('total_count', $output->total_count); Context::set('total_page', $output->total_page); diff --git a/modules/communication/skins/default/messages.html b/modules/communication/skins/default/messages.html index b7ab60b33..149d783b9 100644 --- a/modules/communication/skins/default/messages.html +++ b/modules/communication/skins/default/messages.html @@ -15,7 +15,7 @@ From 715daa9a69efee4d6fefcf5b409d4ce195d3bdd7 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 26 Apr 2026 18:13:59 +0900 Subject: [PATCH 03/36] Disallow setting allow_message to "F" if friend feature is disabled #2702 --- modules/communication/communication.controller.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/communication/communication.controller.php b/modules/communication/communication.controller.php index fe9c1e369..aaefcf201 100644 --- a/modules/communication/communication.controller.php +++ b/modules/communication/communication.controller.php @@ -27,6 +27,7 @@ class CommunicationController extends communication } $args = new stdClass(); + $args->member_srl = $this->user->member_srl; $args->allow_message = Context::get('allow_message'); if(!in_array($args->allow_message, array('Y', 'N', 'F'))) @@ -34,7 +35,11 @@ class CommunicationController extends communication $args->allow_message = 'Y'; } - $args->member_srl = $this->user->member_srl; + $config = CommunicationModel::getConfig(); + if ($config->enable_friend !== 'Y' && $args->allow_message === 'F') + { + $args->allow_message = 'Y'; + } $output = executeQuery('communication.updateAllowMessage', $args); if(!$output->toBool()) From c66daf6507cfa9238f0778f35e5c9bd979fb343a Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 26 Apr 2026 18:18:40 +0900 Subject: [PATCH 04/36] Be more strict about skipping embedded SVG checks #2703 --- common/framework/filters/FileContentFilter.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/common/framework/filters/FileContentFilter.php b/common/framework/filters/FileContentFilter.php index d53525118..cc07924c2 100644 --- a/common/framework/filters/FileContentFilter.php +++ b/common/framework/filters/FileContentFilter.php @@ -58,10 +58,12 @@ class FileContentFilter fclose($fp); return false; } - $image_info = @getimagesize($file); - if ($image_info) + if ($mime_type === 'image' && $is_xml && $image_info = @getimagesize($file)) { - $skip_xml = true; + if ($image_info[0] && $image_info[1] && !empty($image_info[2])) + { + $skip_xml = true; + } } } From 045010670b8d0d552d4a7a52e2acf1805a63e0f9 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 26 Apr 2026 18:30:51 +0900 Subject: [PATCH 05/36] Add triggers before and after category insert/update/delete --- modules/document/document.controller.php | 102 ++++++++++++++++------- 1 file changed, 71 insertions(+), 31 deletions(-) diff --git a/modules/document/document.controller.php b/modules/document/document.controller.php index ab9b80ece..5f0697cbc 100644 --- a/modules/document/document.controller.php +++ b/modules/document/document.controller.php @@ -2546,12 +2546,24 @@ class DocumentController extends Document $obj->list_order = $obj->category_srl = getNextSequence(); } + $output = ModuleHandler::triggerCall('document.insertCategory', 'before', $obj); + if (!$output->toBool()) + { + return $output; + } + $output = executeQuery('document.insertCategory', $obj); - if($output->toBool()) + if ($output->toBool()) { $output->add('category_srl', $obj->category_srl); $this->makeCategoryFile($obj->module_srl); } + else + { + return $output; + } + + ModuleHandler::triggerCall('document.insertCategory', 'after', $obj); return $output; } @@ -2595,7 +2607,10 @@ class DocumentController extends Document $args->category_srl = $category_srl; $args->{$mode} = $document_count; $output = executeQuery('document.updateCategoryCount', $args); - if($output->toBool()) $this->makeCategoryFile($module_srl); + if ($output->toBool()) + { + $this->makeCategoryFile($module_srl); + } return $output; } @@ -2607,8 +2622,23 @@ class DocumentController extends Document */ function updateCategory($obj) { + $output = ModuleHandler::triggerCall('document.updateCategory', 'before', $obj); + if (!$output->toBool()) + { + return $output; + } + $output = executeQuery('document.updateCategory', $obj); - if($output->toBool()) $this->makeCategoryFile($obj->module_srl); + if ($output->toBool()) + { + $this->makeCategoryFile($obj->module_srl); + } + else + { + return $output; + } + + ModuleHandler::triggerCall('document.updateCategory', 'after', $obj); return $output; } @@ -2622,43 +2652,53 @@ class DocumentController extends Document $args = new stdClass(); $args->category_srl = $category_srl; $category_info = DocumentModel::getCategory($category_srl); - // Display an error that the category cannot be deleted if it has a child + + // Check if the category has any children. $output = executeQuery('document.getChildCategoryCount', $args); - if(!$output->toBool()) return $output; - if($output->data->count>0) return new BaseObject(-1, 'msg_cannot_delete_for_child'); - // Delete a category information - $output = executeQuery('document.deleteCategory', $args); - if(!$output->toBool()) return $output; - - $this->makeCategoryFile($category_info->module_srl); - - // remove cache - $page = 0; - while(true) + if (!$output->toBool()) { - $args = new stdClass(); - $args->category_srl = $category_srl; - $args->list_count = 100; - $args->page = ++$page; - $output = executeQuery('document.getDocumentList', $args, array('document_srl')); - - if($output->data == array()) - { - break; - } - - foreach($output->data as $val) - { - self::clearDocumentCache($val->document_srl); - } + return $output; + } + if ($output->data->count > 0) + { + return new BaseObject(-1, 'msg_cannot_delete_for_child'); } - // Update category_srl of the documents in the same category to 0 + // Call trigger (before) + $output = ModuleHandler::triggerCall('document.deleteCategory', 'before', $args); + if (!$output->toBool()) + { + return $output; + } + + // Delete the category. + $output = executeQuery('document.deleteCategory', $args); + if ($output->toBool()) + { + $this->makeCategoryFile($category_info->module_srl); + } + else + { + return $output; + } + + // Documents in the deleted category will be moved to category 0. $args = new stdClass(); $args->target_category_srl = 0; $args->source_category_srl = $category_srl; $output = executeQuery('document.updateDocumentCategory', $args); + if ($output->toBool()) + { + Rhymix\Framework\Cache::clearGroup('document_item'); + $GLOBALS['XE_DOCUMENT_LIST'] = []; + } + else + { + return $output; + } + // Call trigger (after) + ModuleHandler::triggerCall('document.deleteCategory', 'after', $args); return $output; } From e724236681a3a7ee3b355333200a3ad21f92b4d1 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 29 Apr 2026 17:19:21 +0900 Subject: [PATCH 06/36] Set default thumbnail target to "attachment only" --- modules/admin/controllers/systemconfig/Advanced.php | 4 ++-- modules/admin/tpl/config_advanced.html | 10 +++++----- modules/document/document.model.php | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/admin/controllers/systemconfig/Advanced.php b/modules/admin/controllers/systemconfig/Advanced.php index 8622d1599..0f497fb80 100644 --- a/modules/admin/controllers/systemconfig/Advanced.php +++ b/modules/admin/controllers/systemconfig/Advanced.php @@ -76,7 +76,7 @@ class Advanced extends Base // Thumbnail settings $oDocumentModel = getModel('document'); $config = $oDocumentModel->getDocumentConfig(); - Context::set('thumbnail_target', $config->thumbnail_target ?: 'all'); + Context::set('thumbnail_target', $config->thumbnail_target ?: 'attachment'); Context::set('thumbnail_type', $config->thumbnail_type ?: 'fill'); Context::set('thumbnail_quality', $config->thumbnail_quality ?: 75); if ($config->thumbnail_type === 'none') @@ -190,7 +190,7 @@ class Advanced extends Base // Thumbnail settings $oDocumentModel = getModel('document'); $document_config = $oDocumentModel->getDocumentConfig(); - $document_config->thumbnail_target = $vars->thumbnail_target ?: 'all'; + $document_config->thumbnail_target = $vars->thumbnail_target ?: 'attachment'; $document_config->thumbnail_type = $vars->thumbnail_type ?: 'fill'; $document_config->thumbnail_quality = intval($vars->thumbnail_quality) ?: 75; $oModuleController = getController('module'); diff --git a/modules/admin/tpl/config_advanced.html b/modules/admin/tpl/config_advanced.html index 7c4540f59..154ec1eb3 100644 --- a/modules/admin/tpl/config_advanced.html +++ b/modules/admin/tpl/config_advanced.html @@ -126,14 +126,14 @@
- +
- + diff --git a/modules/layout/tpl/sub_tab.html b/modules/layout/tpl/sub_tab.html index 7247e2778..d3d20a449 100644 --- a/modules/layout/tpl/sub_tab.html +++ b/modules/layout/tpl/sub_tab.html @@ -1,6 +1,6 @@
{$lang->instance_layout} | diff --git a/modules/module/module.admin.view.php b/modules/module/module.admin.view.php index 7cc50fa0e..a4f7d7876 100644 --- a/modules/module/module.admin.view.php +++ b/modules/module/module.admin.view.php @@ -44,7 +44,7 @@ class ModuleAdminView extends Module // get easyinstall need update $packageSrl = $oAutoinstallModel->getPackageSrlByPath($val->path); $package = $oAutoinstallModel->getInstalledPackages($packageSrl); - $module_list[$key]->need_autoinstall_update = $package[$packageSrl]->need_update; + $module_list[$key]->need_autoinstall_update = $package[$packageSrl]->need_update ?? null; // get easyinstall update url if($module_list[$key]->need_autoinstall_update == 'Y') diff --git a/modules/module/tpl/adminFileBox.html b/modules/module/tpl/adminFileBox.html index 12cc5b351..234e7b13c 100644 --- a/modules/module/tpl/adminFileBox.html +++ b/modules/module/tpl/adminFileBox.html @@ -68,7 +68,7 @@
@@ -78,7 +78,7 @@
@@ -103,15 +103,6 @@
-
- -
- - -
-

{$lang->about_use_sso}

-
-
diff --git a/tests/unit/framework/SessionTest.php b/tests/unit/framework/SessionTest.php index e0675dfc6..af8a6dcd8 100644 --- a/tests/unit/framework/SessionTest.php +++ b/tests/unit/framework/SessionTest.php @@ -98,11 +98,6 @@ class SessionTest extends \Codeception\Test\Unit $this->assertTrue(Rhymix\Framework\Session::isStarted()); } - public function testCheckSSO() - { - $this->assertNull(Rhymix\Framework\Session::checkSSO(new stdClass)); - } - public function testRefresh() { $_SERVER['REQUEST_METHOD'] = 'GET'; From 0fdbf72e323191d78ad8e44ff550cf0f1920b499 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 30 Apr 2026 17:35:01 +0900 Subject: [PATCH 14/36] Reset default thumbnail target to "attachment", and add warning about using external images --- modules/admin/lang/en.php | 3 ++- modules/admin/lang/ko.php | 3 ++- modules/admin/tpl/config_advanced.html | 7 ++++--- modules/document/document.model.php | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/admin/lang/en.php b/modules/admin/lang/en.php index 19e7231a0..346aab137 100644 --- a/modules/admin/lang/en.php +++ b/modules/admin/lang/en.php @@ -338,8 +338,9 @@ $lang->mobile_viewport = 'Mobile viewport Setting'; $lang->about_mobile_viewport = 'The settings entered above will be output in a <meta name="viewport"> tag for mobile visitors.'; $lang->restore_default_viewport = 'Restore Default'; $lang->thumbnail_target = 'Extract Thumbnail From'; -$lang->thumbnail_target_all = 'All images'; +$lang->thumbnail_target_content = 'All embedded images, including external images'; $lang->thumbnail_target_attachment = 'Attached images only'; +$lang->about_thumbnail_target = 'Using external images to generate thumbnails may cause various issues with respect to page load speed, copyright, and security. Please be careful.'; $lang->thumbnail_type = 'Thumbnail Type'; $lang->input_header_script = 'Header Script'; $lang->detail_input_header_script = 'Content added here will be printed at the top of every page, except the admin module.'; diff --git a/modules/admin/lang/ko.php b/modules/admin/lang/ko.php index 5c0f39257..d03a4e08c 100644 --- a/modules/admin/lang/ko.php +++ b/modules/admin/lang/ko.php @@ -334,8 +334,9 @@ $lang->mobile_viewport = '모바일 viewport 설정'; $lang->about_mobile_viewport = '모바일 접속시 여기 입력한 내용이 <meta name="viewport"> 태그로 출력됩니다.'; $lang->restore_default_viewport = '기본값 복원'; $lang->thumbnail_target = '썸네일 생성 대상'; -$lang->thumbnail_target_all = '모든 이미지 (외부 이미지 포함)'; +$lang->thumbnail_target_content = '모든 이미지 (외부 이미지 포함)'; $lang->thumbnail_target_attachment = '첨부된 이미지'; +$lang->about_thumbnail_target = '외부 이미지를 사용하여 썸네일을 생성하는 경우 페이지 로딩 속도, 저작권, 보안 등 다양한 문제가 발생할 수 있으니 주의하시기 바랍니다.'; $lang->thumbnail_type = '썸네일 생성 방식'; $lang->input_header_script = '상단(헤더) 스크립트'; $lang->detail_input_header_script = '모든 페이지의 최상단에 코드를 삽입합니다. 관리자 화면에는 적용되지 않습니다.'; diff --git a/modules/admin/tpl/config_advanced.html b/modules/admin/tpl/config_advanced.html index 154ec1eb3..c1c158476 100644 --- a/modules/admin/tpl/config_advanced.html +++ b/modules/admin/tpl/config_advanced.html @@ -130,14 +130,15 @@ {$lang->thumbnail_target_attachment} -
diff --git a/modules/document/document.model.php b/modules/document/document.model.php index 087e8fec7..d036807b4 100644 --- a/modules/document/document.model.php +++ b/modules/document/document.model.php @@ -1037,7 +1037,7 @@ class DocumentModel extends Document { self::$_config = ModuleModel::getModuleConfig('document') ?: new stdClass; } - if (!isset(self::$_config->thumbnail_target)) + if (!isset(self::$_config->thumbnail_target) || self::$_config->thumbnail_target === 'all') { self::$_config->thumbnail_target = 'attachment'; } From f438a91cd677df6813ad9afec3140b1b145f50d0 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 30 Apr 2026 22:44:14 +0900 Subject: [PATCH 15/36] Treat samesite attribute as a string, not integer --- common/framework/Session.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/framework/Session.php b/common/framework/Session.php index ec4b5275b..2a77a7fb9 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -79,7 +79,7 @@ class Session ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); ini_set('session.use_strict_mode', 1); - ini_set('session.cookie_samesite', $samesite ? 1 : 0); + ini_set('session.cookie_samesite', $samesite); session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly); session_name($session_name = Config::get('session.name') ?: session_name()); @@ -948,7 +948,7 @@ class Session $path = Config::get('session.path') ?: ini_get('session.cookie_path'); $secure = (\RX_SSL && config('session.use_ssl')) ? true : false; $httponly = Config::get('session.httponly') ?? true; - $samesite = config('session.samesite'); + $samesite = config('session.samesite') ?: ''; return array($lifetime, $refresh, $domain, $path, $secure, $httponly, $samesite); } From 7f1a61fb83ab8bb6641223f19b77a6b95dc8053d Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 30 Apr 2026 23:00:55 +0900 Subject: [PATCH 16/36] Clean up session refresh handling (dedicated timer, don't refresh in non-GET request, etc.) --- classes/module/ModuleObject.class.php | 2 +- common/framework/Session.php | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index d4ec19e79..df397e020 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -211,7 +211,7 @@ class ModuleObject extends BaseObject } // Special treatment for admin actions - if(preg_match('/^disp[A-Z][a-z0-9\_]+Admin/', $this->act)) + if(preg_match('/^disp(?:Admin[A-Z]|[A-Z][a-z0-9\_]+Admin)/', $this->act)) { // Set admin layout if(config('view.manager_layout') === 'admin') diff --git a/common/framework/Session.php b/common/framework/Session.php index 2a77a7fb9..1891a0e8b 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -113,7 +113,11 @@ class Session } // Check if the session needs to be refreshed. - if (!$must_create && !isset($_SESSION['RHYMIX']['domains'][$alt_domain]['started']) || $_SESSION['RHYMIX']['domains'][$alt_domain]['started'] < time() - $refresh_interval) + if (!$must_create && (!isset($_SESSION['RHYMIX']['last_refresh']) || $_SESSION['RHYMIX']['last_refresh'] < time() - $refresh_interval)) + { + $must_refresh = true; + } + if (!$must_create && isset($_SESSION['RHYMIX']['next_refresh']) && $_SESSION['RHYMIX']['next_refresh'] === true) { $must_refresh = true; } @@ -127,8 +131,9 @@ class Session } // If this is not a GET request, do not refresh now. - if (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'GET') + if ($must_refresh && (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'GET')) { + $_SESSION['RHYMIX']['next_refresh'] = true; $must_refresh = false; } @@ -250,6 +255,8 @@ class Session $_SESSION['RHYMIX'] = array(); $_SESSION['RHYMIX']['login'] = false; $_SESSION['RHYMIX']['last_login'] = false; + $_SESSION['RHYMIX']['last_refresh'] = time(); + $_SESSION['RHYMIX']['next_refresh'] = false; $_SESSION['RHYMIX']['autologin_key'] = false; $_SESSION['RHYMIX']['ipaddress'] = $_SESSION['ipaddress'] = \RX_CLIENT_IP; $_SESSION['RHYMIX']['useragent'] = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; @@ -319,7 +326,10 @@ class Session ); // Update the domain initialization timestamp. - $_SESSION['RHYMIX']['domains'][$alt_domain]['started'] = time(); + if (!isset($_SESSION['RHYMIX']['domains'][$alt_domain]['started'])) + { + $_SESSION['RHYMIX']['domains'][$alt_domain]['started'] = time(); + } if (!isset($_SESSION['RHYMIX']['domains'][$alt_domain]['trusted'])) { $_SESSION['RHYMIX']['domains'][$alt_domain]['trusted'] = 0; @@ -328,6 +338,8 @@ class Session // Refresh the main session cookie and the autologin key. if ($refresh_cookie) { + $_SESSION['RHYMIX']['last_refresh'] = time(); + $_SESSION['RHYMIX']['next_refresh'] = false; self::destroyCookiesFromConflictingDomains(array(session_name())); //Cookie::set(session_name(), session_id(), $options); session_regenerate_id(true); From 019950c8a8b135f0c6336705b4b7187bbcf46937 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 30 Apr 2026 23:13:05 +0900 Subject: [PATCH 17/36] Prevent unnecessary double cookie refresh when logging in as admin --- classes/module/ModuleObject.class.php | 4 ++-- common/framework/Session.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index df397e020..c0861660d 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -226,9 +226,9 @@ class ModuleObject extends BaseObject } // Refresh session - if (!isset($_SESSION['RHYMIX']['admin_accessed'])) + if (!isset($_SESSION['RHYMIX']['admin_accessed']) && !headers_sent()) { - if (!headers_sent()) + if (!isset($_SESSION['RHYMIX']['last_refresh']) || $_SESSION['RHYMIX']['last_refresh'] < time() - 10) { $_SESSION['RHYMIX']['admin_accessed'] = \RX_TIME; Rhymix\Framework\Session::refresh(true); diff --git a/common/framework/Session.php b/common/framework/Session.php index 1891a0e8b..4cf729897 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -446,7 +446,7 @@ class Session if ($refresh) { self::checkLoginStatusCookie(); - return self::refresh(true); + return $_SESSION['RHYMIX']['next_refresh'] = true; } else { From bde08b1480d1495f41d66c86b8d61f7a05bcd607 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 4 May 2026 13:03:25 +0900 Subject: [PATCH 18/36] Only check path recursively if its realpath is different --- modules/page/page.admin.controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/page/page.admin.controller.php b/modules/page/page.admin.controller.php index 15245ae47..12966a0ec 100644 --- a/modules/page/page.admin.controller.php +++ b/modules/page/page.admin.controller.php @@ -411,7 +411,7 @@ class PageAdminController extends Page if (!preg_match('!^https?://!i', $path) && file_exists($path)) { $realpath = realpath($path); - if (!self::_isAllowedExternalPath($realpath)) + if ($realpath !== $path && !self::_isAllowedExternalPath($realpath)) { return false; } From 1033cc33a7f14356f339fc12377bcf7f5661998c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 7 May 2026 17:10:14 +0900 Subject: [PATCH 19/36] Fix undefined variable error in PHP 8 --- modules/layout/layout.view.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/layout/layout.view.php b/modules/layout/layout.view.php index b83713a16..5fbb4936e 100644 --- a/modules/layout/layout.view.php +++ b/modules/layout/layout.view.php @@ -301,6 +301,9 @@ class LayoutView extends Layout Context::set('skin_type', null); Context::set('skin_vars', null); + // Set dummy variable + Context::set('layout_info', Context::get('layout_info') ?: new stdClass()); + // Proc module $oModule = $oModuleHandler->procModule(); if(!$oModule->toBool()) From abda55c926109073243aac14556b6fcfcc668a4d Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 7 May 2026 17:17:35 +0900 Subject: [PATCH 20/36] Remove dispLayoutPreview --- modules/layout/conf/module.xml | 1 - modules/layout/layout.view.php | 70 --------------------------- modules/layout/tpl/js/adminEdit.js | 13 ----- modules/layout/tpl/js/layout_admin.js | 10 ---- modules/layout/tpl/layout_edit.html | 1 - 5 files changed, 95 deletions(-) diff --git a/modules/layout/conf/module.xml b/modules/layout/conf/module.xml index b5278c856..a01b9eb0f 100644 --- a/modules/layout/conf/module.xml +++ b/modules/layout/conf/module.xml @@ -2,7 +2,6 @@ - diff --git a/modules/layout/layout.view.php b/modules/layout/layout.view.php index 5fbb4936e..c68cf527c 100644 --- a/modules/layout/layout.view.php +++ b/modules/layout/layout.view.php @@ -317,76 +317,6 @@ class LayoutView extends Layout return $handler->toDoc($oModule); } - /** - * Preview a layout - * @return void|Object (void : success, Object : fail) - */ - function dispLayoutPreview() - { - if(!Rhymix\Framework\Security::checkCSRF()) - { - throw new Rhymix\Framework\Exceptions\InvalidRequest; - } - - // admin check - // this act is admin view but in normal view because do not load admin css/js files - $logged_info = Context::get('logged_info'); - if($logged_info->is_admin != 'Y') throw new Rhymix\Framework\Exceptions\InvalidRequest; - - $layout_srl = Context::get('layout_srl'); - $code = Context::get('code'); - - $code_css = Context::get('code_css'); - if(!$layout_srl || !$code) throw new Rhymix\Framework\Exceptions\InvalidRequest; - // Get the layout information - $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($layout_srl); - if(!$layout_info) throw new Rhymix\Framework\Exceptions\InvalidRequest; - // Separately handle the layout if its type is faceoff - if($layout_info && $layout_info->type == 'faceoff') $oLayoutModel->doActivateFaceOff($layout_info); - // Apply CSS directly - Context::addHtmlHeader(""); - // Set names and values of extra_vars to $layout_info - if($layout_info->extra_var_count) - { - foreach($layout_info->extra_var as $var_id => $val) - { - $layout_info->{$var_id} = $val->value; - } - } - // menu in layout information becomes an argument for Context:: set - if($layout_info->menu_count) - { - foreach($layout_info->menu as $menu_id => $menu) - { - $menu->php_file = FileHandler::getRealPath($menu->php_file); - if(FileHandler::exists($menu->php_file)) include($menu->php_file); - - Context::set($menu_id, $menu); - } - } - - Context::set('layout_info', $layout_info); - Context::set('content', lang('layout_preview_content')); - // Temporary save the codes - $edited_layout_file = RX_BASEDIR . 'files/cache/layout/tmp.tpl'; - FileHandler::writeFile($edited_layout_file, $code); - - // Compile - $oTemplate = TemplateHandler::getInstance(); - - $layout_path = $layout_info->path; - $layout_file = 'layout'; - - $layout_tpl = $oTemplate->compile($layout_path, $layout_file, $edited_layout_file); - Context::set('layout','none'); - // Convert widgets and others - Context::set('layout_tpl', $layout_tpl); - // Delete Temporary Files - FileHandler::removeFile($edited_layout_file); - $this->setTemplateFile('layout_preview'); - } - private function getRealLayoutFile($layoutSrl) { $oLayoutModel = getModel('layout'); diff --git a/modules/layout/tpl/js/adminEdit.js b/modules/layout/tpl/js/adminEdit.js index b8defafb7..bcb1971e1 100644 --- a/modules/layout/tpl/js/adminEdit.js +++ b/modules/layout/tpl/js/adminEdit.js @@ -1,16 +1,3 @@ -function doPreviewLayoutCode() -{ - var $form = jQuery('#fo_layout'), $act = $form.find('input[name=act]'); - var og_act = $act.val(); - - $form.attr('target', '_LayoutPreview'); - $act.val('dispLayoutPreview'); - $form.submit(); - - $form.removeAttr('target'); - $act.val(og_act); -} - $(function() { $('.reset_layout').on('click', function(e) { var msg = $(this).data('confirmationMsg'); diff --git a/modules/layout/tpl/js/layout_admin.js b/modules/layout/tpl/js/layout_admin.js index f2e4c7cf8..7a2099b54 100644 --- a/modules/layout/tpl/js/layout_admin.js +++ b/modules/layout/tpl/js/layout_admin.js @@ -70,16 +70,6 @@ function addLayoutCopyInputbox() (function($){ -/* preview layout */ -function doPreviewLayoutCode(layout_srl) { - var fo = $('#fo_layout'); - var act = fo.find('input[name=act]:first').val(); - fo.attr('target', '_LayoutPreview').find('input[name=act]').val('dispLayoutAdminPreview'); - fo.submit(); - //.removeAttr('target').find('input[name=act]').val(act); -} -window.doPreviewLayoutCode = doPreviewLayoutCode; - /* restore layout code */ function doResetLayoutCode(layout_srl) { procFilter($('#fo_layout')[0], reset_layout_code); diff --git a/modules/layout/tpl/layout_edit.html b/modules/layout/tpl/layout_edit.html index 15a51f97a..5b70bc930 100644 --- a/modules/layout/tpl/layout_edit.html +++ b/modules/layout/tpl/layout_edit.html @@ -102,7 +102,6 @@ -
From 93c8fa3d7d0783c8c0001600c0583db71dc5b5c5 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 7 May 2026 20:20:27 +0900 Subject: [PATCH 21/36] Fix RVE-2026-11 XSS in Photoswipe addon --- addons/photoswipe/rx_photoswipe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/photoswipe/rx_photoswipe.js b/addons/photoswipe/rx_photoswipe.js index d21fbabce..fc6f30d0d 100644 --- a/addons/photoswipe/rx_photoswipe.js +++ b/addons/photoswipe/rx_photoswipe.js @@ -182,7 +182,7 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { captionEl.children[0].innerText = ''; return false; } - captionEl.children[0].innerHTML = item.title; + captionEl.children[0].innerText = item.title; return true; }, From c2e38044a43038042f1ba6478727a435d4df1b31 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 7 May 2026 20:29:06 +0900 Subject: [PATCH 22/36] Fix RVE-2026-12 unauthorized file deletion --- modules/file/file.controller.php | 45 +++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/modules/file/file.controller.php b/modules/file/file.controller.php index ab41a0003..b6870098c 100644 --- a/modules/file/file.controller.php +++ b/modules/file/file.controller.php @@ -607,9 +607,7 @@ class FileController extends File { // Basic variable setting(upload_target_srl and module_srl set) $editor_sequence = Context::get('editor_sequence'); - $file_srl = Context::get('file_srl'); - $file_srls = Context::get('file_srls'); - if($file_srls) $file_srl = $file_srls; + $file_srls = explode(',', Context::get('file_srls') ?: Context::get('file_srl')); // Exit a session if there is neither upload permission nor information if (!$_SESSION['upload_info'][$editor_sequence]->enabled) @@ -623,26 +621,43 @@ class FileController extends File } $module_srl = $_SESSION['upload_info'][$editor_sequence]->module_srl ?? 0; - $srls = explode(',',$file_srl); - if(!count($srls)) return; - - for($i=0;$ifile_srl = $srl; + $args->file_srl = $file_srl; $output = executeQuery('file.getFile', $args); - if(!$output->toBool()) continue; - + if (!$output->toBool()) + { + continue; + } $file_info = $output->data; - if(!$file_info || $file_info->upload_target_srl != $upload_target_srl) continue; + if (!$file_info || $file_info->upload_target_srl != $upload_target_srl) + { + continue; + } //if($module_srl && $file_info->module_srl != $module_srl) continue; - if(!FileModel::isDeletable($file_info)) continue; - $output = $this->deleteFile($file_srl); + if (!FileModel::isDeletable($file_info)) + { + continue; + } + + $valid_file_srls[] = $file_srl; } + if (!count($valid_file_srls)) + { + return; + } + + $output = $this->deleteFile($valid_file_srls); + // Add upload status (getFileList) try { From 83e2c981cea5bc42c6cad97a3c4ecd874853a0f5 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 7 May 2026 21:31:55 +0900 Subject: [PATCH 23/36] Fix conflicting pre_conversion types setting in file module config #2707 --- modules/file/file.admin.controller.php | 15 ++------------- modules/file/file.model.php | 5 ++++- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/modules/file/file.admin.controller.php b/modules/file/file.admin.controller.php index 52ccb1611..46d1b0cee 100644 --- a/modules/file/file.admin.controller.php +++ b/modules/file/file.admin.controller.php @@ -302,19 +302,8 @@ class FileAdminController extends File $download_grant = Context::get('download_grant'); $config->download_grant = is_array($download_grant) ? array_values($download_grant) : array($download_grant); - // Create pre-conversion whitelist - $config->pre_conversion_types = []; - foreach ($config->image_autoconv ?? [] as $source_type => $target_type) - { - if ($target_type && $target_type !== true) - { - $config->pre_conversion_types[] = $source_type; - if ($source_type === 'jpg') - { - $config->pre_conversion_types[] = 'jpeg'; - } - } - } + // Unset pre-conversion type setting #2707 + unset($config->pre_conversion_types); // Update $oModuleController = getController('module'); diff --git a/modules/file/file.model.php b/modules/file/file.model.php index ba69f6c6e..5edd954b7 100644 --- a/modules/file/file.model.php +++ b/modules/file/file.model.php @@ -360,7 +360,10 @@ class FileModel extends File $module_config = ModuleModel::getModulePartConfig('file', $module_srl); foreach((array)$module_config as $key => $value) { - $config->$key = $value; + if ($value !== null) + { + $config->$key = $value; + } } } From ee324645683e673a81dc3f271c828508d55f6166 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 7 May 2026 21:38:57 +0900 Subject: [PATCH 24/36] Improve defense against non-array cached value #2705 --- modules/admin/models/AdminMenu.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/admin/models/AdminMenu.php b/modules/admin/models/AdminMenu.php index c722f4f37..03b87a7af 100644 --- a/modules/admin/models/AdminMenu.php +++ b/modules/admin/models/AdminMenu.php @@ -66,7 +66,7 @@ class AdminMenu $lang = \Rhymix\Framework\Cache::get('admin_menu_langs:' . Context::getLangType()); } - if ($lang === null) + if ($lang === null || !is_array($lang)) { $lang = []; $installed_module_list = ModuleModel::getModulesXmlInfo(); From 67a77ff4c8a2584b229b09f9f326c22d9b99b298 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 7 May 2026 22:52:25 +0900 Subject: [PATCH 25/36] Add missing return --- modules/board/board.view.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/board/board.view.php b/modules/board/board.view.php index d10a62853..12cbc0c6a 100644 --- a/modules/board/board.view.php +++ b/modules/board/board.view.php @@ -141,7 +141,7 @@ class BoardView extends Board */ if(!$this->grant->access || !$this->grant->list) { - $this->dispBoardMessage($this->user->isMember() ? 'msg_not_permitted' : 'msg_not_logged'); + return $this->dispBoardMessage($this->user->isMember() ? 'msg_not_permitted' : 'msg_not_logged'); } /** From 71b11c769dd86ccc85807ba7b6e80115f915d79b Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 7 May 2026 23:07:36 +0900 Subject: [PATCH 26/36] Fix incorrect error location when R\F\Exception is thrown from inside an event handler --- classes/module/ModuleHandler.class.php | 2 ++ classes/module/ModuleObject.class.php | 8 ++++++++ common/framework/Exception.php | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 1d8acd7fe..53d3bca1d 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -1355,6 +1355,7 @@ class ModuleHandler extends Handler catch (Rhymix\Framework\Exception $e) { $output = new BaseObject(-2, $e->getMessage()); + $output->add('rx_error_location', $e->getUserFileAndLine()); } if ($trigger_name !== 'common.flushDebugInfo') @@ -1391,6 +1392,7 @@ class ModuleHandler extends Handler catch (Rhymix\Framework\Exception $e) { $output = new BaseObject(-2, $e->getMessage()); + $output->add('rx_error_location', $e->getUserFileAndLine()); } if ($trigger_name !== 'common.writeSlowlog') diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index 687c07bad..97a1b8049 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -805,6 +805,10 @@ class ModuleObject extends BaseObject { $this->setError($triggerOutput->getError()); $this->setMessage($triggerOutput->getMessage()); + if ($triggerOutput->get('rx_error_location')) + { + $this->add('rx_error_location', $triggerOutput->get('rx_error_location')); + } return FALSE; } @@ -846,6 +850,10 @@ class ModuleObject extends BaseObject { $this->setError($triggerOutput->getError()); $this->setMessage($triggerOutput->getMessage()); + if ($triggerOutput->get('rx_error_location')) + { + $this->add('rx_error_location', $triggerOutput->get('rx_error_location')); + } return false; } diff --git a/common/framework/Exception.php b/common/framework/Exception.php index 6cd07d412..89b1a6739 100644 --- a/common/framework/Exception.php +++ b/common/framework/Exception.php @@ -19,6 +19,11 @@ class Exception extends \Exception public function getUserFileAndLine(): string { $regexp = '!^' . preg_quote(\RX_BASEDIR, '!') . '(?:classes|common)/!'; + if (!preg_match($regexp, $this->getFile())) + { + return $this->getFile() . ':' . $this->getLine(); + } + $trace = $this->getTrace(); foreach ($trace as $frame) { From 589b9167c985509d8e602135bdf15054e81050b6 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 7 May 2026 23:21:18 +0900 Subject: [PATCH 27/36] Remove link to temporary documents in admin list --- modules/document/tpl/document_list.html | 31 +++++++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/modules/document/tpl/document_list.html b/modules/document/tpl/document_list.html index 4e73b31ba..68f598071 100644 --- a/modules/document/tpl/document_list.html +++ b/modules/document/tpl/document_list.html @@ -54,15 +54,36 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
- {escape($oDocument->getTitleText(), false)}{$lang->no_title_document} - - {$module_list[$oDocument->get('module_srl')]->browser_title} + {@ $document_link = $oDocument->getPermanentUrl()} + {@ $module_link = getUrl('', 'mid', $module_list[$oDocument->get('module_srl')]->mid)} - {escape($oDocument->getTitleText(), false)}{$lang->no_title_document} - - {$module_list[$oDocument->get('module_srl')]->browser_title} + {@ $document_link = ModuleModel::getDomainByModuleSrl($oDocument->get('module_srl')) . getUrl('', 'mid', $module_list[$oDocument->get('module_srl')]->mid, 'document_srl', $oDocument->document_srl)} + {@ $module_link = ModuleModel::getDomainByModuleSrl($oDocument->get('module_srl')) . getUrl('', 'mid', $module_list[$oDocument->get('module_srl')]->mid)} - {escape($oDocument->getTitleText(), false)}{$lang->no_title_document} + {@ $document_link = $oDocument->getPermanentUrl()} + {@ $module_link = null} + + + + {escape($oDocument->getTitleText(), false)} + + {$lang->no_title_document} + + + + + + {escape($oDocument->getTitleText(), false)} + + {$lang->no_title_document} + + + + + - {$module_list[$oDocument->get('module_srl')]->browser_title} + {$oDocument->getNickName()} From 38d1a3a5856ab64ebd0923a22dba0ed260ee7b12 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 7 May 2026 23:22:50 +0900 Subject: [PATCH 28/36] Always display "allow duplicate nickname" option --- modules/member/tpl/default_config.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/member/tpl/default_config.html b/modules/member/tpl/default_config.html index 0155e3140..f7db367ac 100644 --- a/modules/member/tpl/default_config.html +++ b/modules/member/tpl/default_config.html @@ -86,7 +86,7 @@ -
-

{$layout->title}

+

{$layout->title}

{$layout->description}

-

+

{$lang->msg_avail_easy_update} {$lang->msg_do_you_like_update}

-

+

{htmlspecialchars($name, ENT_COMPAT | ENT_HTML401, 'UTF-8', false)}: {$value}

- +
@@ -111,7 +111,7 @@ - +
  • {$lang->last_page} »
  • diff --git a/modules/widget/tpl/downloaded_widget_list.html b/modules/widget/tpl/downloaded_widget_list.html index 047258383..3de6c18dc 100644 --- a/modules/widget/tpl/downloaded_widget_list.html +++ b/modules/widget/tpl/downloaded_widget_list.html @@ -29,7 +29,7 @@ Rhymix Core - {$widget->version} + {$widget->version} From f00780ef4721f6942f6676801bb204a53525094e Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 8 May 2026 21:01:12 +0900 Subject: [PATCH 31/36] Fix lang code exposure after selecting module in document manage popup --- modules/document/tpl/checked_list.html | 2 +- modules/module/module.view.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/document/tpl/checked_list.html b/modules/document/tpl/checked_list.html index 9cfd75554..020549b4b 100644 --- a/modules/document/tpl/checked_list.html +++ b/modules/document/tpl/checked_list.html @@ -28,7 +28,7 @@ diff --git a/modules/module/module.view.php b/modules/module/module.view.php index b93143cc2..08d1151fc 100644 --- a/modules/module/module.view.php +++ b/modules/module/module.view.php @@ -64,10 +64,10 @@ class ModuleView extends Module $obj = new stdClass; $obj->module_srl = $val->module_srl; - $obj->browser_title = $val->browser_title; + $obj->browser_title = Context::replaceUserLang($val->browser_title); $mid_list[$val->module]->list[$val->category ?: 0][$val->mid] = $obj; - $mid_list[$val->module]->title = ModuleModel::getModuleInfoXml($val->module)->title; + $mid_list[$val->module]->title = Context::replaceUserLang(ModuleModel::getModuleInfoXml($val->module)->title); } Context::set('mid_list', $mid_list); From e276eb83773da8fd52368f4d9c55ce167e6d1d4a Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 8 May 2026 21:12:23 +0900 Subject: [PATCH 32/36] Hide friend option in signup form and member modify page if friend is disabled in communication module #2702 --- modules/member/member.view.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/modules/member/member.view.php b/modules/member/member.view.php index 9523b6cc6..64a1f023e 100644 --- a/modules/member/member.view.php +++ b/modules/member/member.view.php @@ -394,6 +394,15 @@ class MemberView extends Member // Set a copy of the agreement for compatibility with old skins $member_config->agreement = $member_config->agreements[1]->content ?? ''; + // Check whether friends are enabled in the communication module. + $comm_config = CommunicationModel::getConfig(); + if ($comm_config->enable_friend !== 'Y') + { + $allow_message_type = lang('member.allow_message_type'); + unset($allow_message_type['F']); + $GLOBALS['lang']->set('member.allow_message_type', $allow_message_type); + } + // Set a template file self::setMemberPageBrowserTitle(lang('cmd_signup')); $this->setTemplateFile('signup_form'); @@ -517,6 +526,15 @@ class MemberView extends Member $this->addExtraFormValidatorMessage(); + // Check whether friends are enabled in the communication module. + $comm_config = CommunicationModel::getConfig(); + if ($comm_config->enable_friend !== 'Y') + { + $allow_message_type = lang('member.allow_message_type'); + unset($allow_message_type['F']); + $GLOBALS['lang']->set('member.allow_message_type', $allow_message_type); + } + // Set a template file self::setMemberPageBrowserTitle(lang('cmd_modify_member_info')); $this->setTemplateFile('modify_info'); From 3566a01a0eb8f60296c68324e4731bd71886d60e Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 11 May 2026 15:26:56 +0900 Subject: [PATCH 33/36] Fix inconsistency between getDocumentList() and getDocumentPage() when searching #2699 --- modules/document/document.model.php | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/modules/document/document.model.php b/modules/document/document.model.php index acf200d3d..920e51083 100644 --- a/modules/document/document.model.php +++ b/modules/document/document.model.php @@ -147,7 +147,7 @@ class DocumentModel extends Document /** * Get a document. - * + * * @param int $document_srl * @param bool $is_admin * @param bool $load_extra_vars @@ -179,7 +179,7 @@ class DocumentModel extends Document /** * Create a blank document. - * + * * @param int $module_srl * @return DocumentItem */ @@ -690,37 +690,37 @@ class DocumentModel extends Document $opt->isExtraVars = $sort_check->isExtraVars; $opt->isExtraVarsSortAsNumber = $sort_check->isExtraVarsSortAsNumber; + $args = new stdClass(); self::_setSearchOption($opt, $args, $query_id, $use_division); - if($sort_check->isExtraVars || !$opt->list_count) + if ($sort_check->isExtraVars || !$opt->list_count) { return 1; } else { - if($sort_check->sort_index === 'list_order' || $sort_check->sort_index === 'update_order') + $args->sort_index = preg_replace('/^documents\./', '', $args->sort_index ?? 'list_order'); + if ($args->sort_index === 'list_order' || $args->sort_index === 'update_order') { - if($args->order_type === 'desc') + if ($args->order_type === 'desc') { - $args->{'rev_' . $sort_check->sort_index} = $oDocument->get($sort_check->sort_index); + $args->{'rev_' . $args->sort_index} = $oDocument->get($args->sort_index); } else { - $args->{$sort_check->sort_index} = $oDocument->get($sort_check->sort_index); + $args->{$args->sort_index} = $oDocument->get($args->sort_index); } } - elseif($sort_check->sort_index === 'regdate') + elseif ($args->sort_index === 'regdate') { - - if($args->order_type === 'asc') + if ($args->order_type === 'asc') { - $args->{'rev_' . $sort_check->sort_index} = $oDocument->get($sort_check->sort_index); + $args->{'rev_' . $args->sort_index} = $oDocument->get($args->sort_index); } else { - $args->{$sort_check->sort_index} = $oDocument->get($sort_check->sort_index); + $args->{$args->sort_index} = $oDocument->get($args->sort_index); } - } else { @@ -728,7 +728,6 @@ class DocumentModel extends Document } } - // Guhanhu total number of the article search page $output = executeQuery($query_id . 'Page', $args); $count = $output->data->count; $page = (int)(($count-1)/$opt->list_count)+1; From 20c57fc56365aae35188c346e2e6b498e97fb0f5 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 11 May 2026 15:46:35 +0900 Subject: [PATCH 34/36] Fix missing query conditions #2699 --- modules/document/queries/getDocumentListWithExtraVarsPage.xml | 2 ++ modules/document/queries/getDocumentListWithinCommentPage.xml | 2 ++ modules/document/queries/getDocumentListWithinTagPage.xml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/modules/document/queries/getDocumentListWithExtraVarsPage.xml b/modules/document/queries/getDocumentListWithExtraVarsPage.xml index 7a6d6a31f..2216cb5ef 100644 --- a/modules/document/queries/getDocumentListWithExtraVarsPage.xml +++ b/modules/document/queries/getDocumentListWithExtraVarsPage.xml @@ -21,6 +21,8 @@ + + diff --git a/modules/document/queries/getDocumentListWithinCommentPage.xml b/modules/document/queries/getDocumentListWithinCommentPage.xml index 070b9d276..b5d76a258 100644 --- a/modules/document/queries/getDocumentListWithinCommentPage.xml +++ b/modules/document/queries/getDocumentListWithinCommentPage.xml @@ -25,6 +25,8 @@ + + diff --git a/modules/document/queries/getDocumentListWithinTagPage.xml b/modules/document/queries/getDocumentListWithinTagPage.xml index 2c96808fa..0b2dac7e6 100644 --- a/modules/document/queries/getDocumentListWithinTagPage.xml +++ b/modules/document/queries/getDocumentListWithinTagPage.xml @@ -19,6 +19,8 @@ + + From 180aa6f43428b968559dbca5ea57614864b49f2b Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 11 May 2026 16:56:48 +0900 Subject: [PATCH 35/36] Add IP exceptions to login failure counting config --- modules/member/lang/en.php | 2 ++ modules/member/lang/ko.php | 6 ++++-- modules/member/member.admin.controller.php | 9 ++++++++ modules/member/member.controller.php | 25 +++++++++++----------- modules/member/member.model.php | 1 + modules/member/tpl/login_config.html | 7 ++++++ 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/modules/member/lang/en.php b/modules/member/lang/en.php index 24eceb38c..842043983 100644 --- a/modules/member/lang/en.php +++ b/modules/member/lang/en.php @@ -316,6 +316,7 @@ $lang->change_password_date = 'Password renewal cycle'; $lang->about_change_password_date = 'If you set a value to this, you will be notified to change your password periodically. (If set to 0, disabled)'; $lang->msg_change_password_date = 'You have not changed the password during %s days. For personal information protection, you need to change the password.'; $lang->about_login_trial_limit = 'Limit the number of login attempts in a short time from the same IP address.'; +$lang->about_login_failure_except_ip = 'You can specify IP addresses or ranges that are exempt from the login attempt limit.
    Enter one IP address or range per line.'; $lang->msg_kr_address = 'Search for the name of eup, myeon or dong of your address.'; $lang->msg_kr_address_etc = 'Enter the rest of your address.'; $lang->cmd_search_again = 'Search again'; @@ -381,6 +382,7 @@ $lang->group = 'Group'; $lang->retrieve_password = 'Retrieve password'; $lang->excess_ip_access_count = 'There were too much login attempts from your device in a short time. You can not log in for %s.'; $lang->enable_login_fail_report = 'Login failure'; +$lang->login_failure_except_ip = 'Login failure except IP'; $lang->login_fail_report = 'Login failure report.'; $lang->login_fail_report_contents = '

    There is recorded login failures.

    %1$s

    * This notification is shown once.
    * This message contains login failure records, before a ID login success.
    Sending: %2$s

    '; $lang->all_group = 'Entire Group'; diff --git a/modules/member/lang/ko.php b/modules/member/lang/ko.php index dee729b3b..40b396bdb 100644 --- a/modules/member/lang/ko.php +++ b/modules/member/lang/ko.php @@ -319,7 +319,8 @@ $lang->msg_invalid_symbol_in_nickname = '닉네임에 사용할 수 없는 특 $lang->change_password_date = '비밀번호 갱신주기'; $lang->about_change_password_date = '일정 기간이 지나면 비밀번호를 변경하도록 유도하는 기능입니다. 사용하지 않으려면 0을 입력하십시오.'; $lang->msg_change_password_date = '%s일 동안 비밀번호를 변경하지 않았습니다. 개인정보 보호를 위하여 비밀번호를 변경해야 합니다.'; -$lang->about_login_trial_limit = '짧은 시간 동안 하나의 아이피(IP)에서 시도할 수 있는 로그인 횟수에 제한을 둡니다.'; +$lang->about_login_trial_limit = '짧은 시간 동안 하나의 IP에서 시도할 수 있는 로그인 횟수에 제한을 둡니다.'; +$lang->about_login_failure_except_ip = '로그인 횟수 제한에서 예외로 할 IP 주소 또는 대역을 지정할 수 있습니다.
    IP 주소 또는 대역을 한 줄에 하나씩 입력하세요.'; $lang->msg_kr_address = '읍, 면, 동 이름으로 검색하세요.'; $lang->msg_kr_address_etc = '나머지 주소(번지)를 입력하세요.'; $lang->cmd_search_again = '다시 검색'; @@ -384,7 +385,8 @@ $lang->msg_success_modify_email_address = '이메일 주소가 정상적으로 $lang->group = '그룹'; $lang->retrieve_password = '비밀번호 찾기'; $lang->excess_ip_access_count = '로그인 가능 횟수를 초과했습니다. %s 간 로그인할 수 없습니다.'; -$lang->enable_login_fail_report = '계정 무한 대입 방지 사용'; +$lang->enable_login_fail_report = '계정 무한 대입 방지'; +$lang->login_failure_except_ip = '로그인 횟수 예외 IP'; $lang->login_fail_report = '로그인 실패 기록 보고 입니다.'; $lang->login_fail_report_contents = '

    로그인 실패 기록을 알려드립니다.

    %1$s

    * 비밀번호를 틀리는 등의 일이 없었는데 이 메시지를 보신다면, 계정 관리에 유의 바랍니다.
    * 이 메시지는 로그인이 성공한 순간 누적 로그인 실패 기록이 많을 경우, 로그인 성공 이전 실패 기록을 모아서 발송합니다.
    발송 시각: %2$s

    '; $lang->all_group = '그룹전체'; diff --git a/modules/member/member.admin.controller.php b/modules/member/member.admin.controller.php index 17859439f..081f3c63b 100644 --- a/modules/member/member.admin.controller.php +++ b/modules/member/member.admin.controller.php @@ -596,6 +596,7 @@ class MemberAdminController extends Member 'enable_login_fail_report', 'max_error_count', 'max_error_count_time', + 'login_failure_except_ip', 'login_invalidate_other_sessions', 'after_login_url', 'after_logout_url' @@ -640,6 +641,14 @@ class MemberAdminController extends Member $args->change_password_date = 0; } + if($args->login_failure_except_ip) + { + $args->login_failure_except_ip = array_map('trim', explode("\n", $args->login_failure_except_ip)); + $args->login_failure_except_ip = array_filter($args->login_failure_except_ip, function($val) { + return $val !== ''; + }); + } + if(!trim(strip_tags($args->after_login_url))) { $args->after_login_url = NULL; diff --git a/modules/member/member.controller.php b/modules/member/member.controller.php index 433adf649..356cf4cec 100644 --- a/modules/member/member.controller.php +++ b/modules/member/member.controller.php @@ -2642,20 +2642,21 @@ class MemberController extends Member $args = new stdClass; $args->ipaddress = \RX_CLIENT_IP; $output = executeQuery('member.getLoginCountByIp', $args); - $errorCount = $output->data->count; - if($errorCount >= $config->max_error_count) + if ($output->data->count >= $config->max_error_count) { - $last_update = strtotime($output->data->last_update); - $term = intval($_SERVER['REQUEST_TIME']-$last_update); - if($term < $config->max_error_count_time) + $last_update = ztime($output->data->last_update); + $term = intval(\RX_TIME - $last_update); + if ($term < $config->max_error_count_time) { - $term = $config->max_error_count_time - $term; - if($term < 60) $term = intval($term).lang('unit_sec'); - elseif(60 <= $term && $term < 3600) $term = intval($term/60).lang('unit_min'); - elseif(3600 <= $term && $term < 86400) $term = intval($term/3600).lang('unit_hour'); - else $term = intval($term/86400).lang('unit_day'); - - return new BaseObject(-1, sprintf(lang('excess_ip_access_count'), $term)); + if (!$config->login_failure_except_ip || !Rhymix\Framework\Filters\IpFilter::inRanges(\RX_CLIENT_IP, $config->login_failure_except_ip)) + { + $term = $config->max_error_count_time - $term; + if($term < 60) $term = intval($term).lang('unit_sec'); + elseif(60 <= $term && $term < 3600) $term = intval($term/60).lang('unit_min'); + elseif(3600 <= $term && $term < 86400) $term = intval($term/3600).lang('unit_hour'); + else $term = intval($term/86400).lang('unit_day'); + return new BaseObject(-1, sprintf(lang('excess_ip_access_count'), $term)); + } } else { diff --git a/modules/member/member.model.php b/modules/member/member.model.php index 2cdda377e..0a51d18ab 100644 --- a/modules/member/member.model.php +++ b/modules/member/member.model.php @@ -121,6 +121,7 @@ class MemberModel extends Member $config->enable_login_fail_report = $config->enable_login_fail_report ?? 'Y'; $config->max_error_count = $config->max_error_count ?? 10; $config->max_error_count_time = $config->max_error_count_time ?? 300; + $config->login_failure_except_ip = $config->login_failure_except_ip ?? []; $config->login_invalidate_other_sessions = $config->login_invalidate_other_sessions ?? 'N'; $config->after_login_url = $config->after_login_url ?? null; $config->after_logout_url = $config->after_logout_url ?? null; diff --git a/modules/member/tpl/login_config.html b/modules/member/tpl/login_config.html index e83bb4b68..236672a14 100644 --- a/modules/member/tpl/login_config.html +++ b/modules/member/tpl/login_config.html @@ -43,6 +43,13 @@

    {$lang->about_login_trial_limit}

    +
    + +
    + +

    {$lang->about_login_failure_except_ip}

    +
    +
    From b7b1a6e5fc01e0dd3f7d92171edd831e44c0ea9f Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Tue, 12 May 2026 11:43:00 +0900 Subject: [PATCH 36/36] Version 2.1.33 --- common/constants.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constants.php b/common/constants.php index ee9fab27d..d4f00f1e9 100644 --- a/common/constants.php +++ b/common/constants.php @@ -3,7 +3,7 @@ /** * RX_VERSION is the version number of the Rhymix CMS. */ -define('RX_VERSION', '2.1.32'); +define('RX_VERSION', '2.1.33'); /** * RX_MICROTIME is the startup time of the current script, in microseconds since the Unix epoch.