From f343dff71344a2476ecbb49f404905ca327a92c4 Mon Sep 17 00:00:00 2001 From: Lastorder-DC <18280396+Lastorder-DC@users.noreply.github.com> Date: Thu, 5 Feb 2026 18:24:58 +0900 Subject: [PATCH 01/47] fanbinit 0205 --- modules/board/board.view.php | 8 +++++++- modules/comment/comment.controller.php | 5 +++++ modules/document/document.controller.php | 5 +++++ modules/document/document.model.php | 5 +++++ modules/document/queries/getDocumentCount.xml | 4 ++-- .../document/queries/getDocumentCountByGroupStatus.xml | 4 ++-- modules/document/queries/getDocumentList.xml | 4 ++-- modules/document/queries/getDocumentListExtraSort.xml | 4 ++-- modules/document/queries/getDocumentListPage.xml | 4 ++-- modules/document/queries/getDocumentListUseIndex.xml | 4 ++-- modules/document/queries/getDocumentListWithExtraVars.xml | 4 ++-- .../queries/getDocumentListWithinExtraVarsExtraSort.xml | 4 ++-- modules/document/queries/getDocumentListWithinMember.xml | 4 ++-- modules/document/queries/getTrashList.xml | 4 ++-- modules/spamfilter/spamfilter.model.php | 2 +- modules/spamfilter/tpl/denied_word_list.html | 6 ++++-- 16 files changed, 47 insertions(+), 24 deletions(-) diff --git a/modules/board/board.view.php b/modules/board/board.view.php index eb40cd2b8..1da409661 100644 --- a/modules/board/board.view.php +++ b/modules/board/board.view.php @@ -547,7 +547,13 @@ class BoardView extends Board $args->page = intval(Context::get('page')) ?: null; $args->list_count = $this->list_count; $args->page_count = $this->page_count; - if(Context::get('v_mode') == 'recommended') $args->s_voted_count = Rhymix\Modules\Yeokbox\Models\Config::getVoteCount(); + if(Context::get('v_mode') == 'recommended') { + $args->s_voted_count = Rhymix\Modules\Yeokbox\Models\Config::getVoteCount(); + } + if(Context::get('v_mode') == 'super_recommended') { + $args->s_voted_count = Rhymix\Modules\Yeokbox\Models\Config::getSuperVoteCount(); + $args->s_readed_count = Rhymix\Modules\Yeokbox\Models\Config::getReadCount(); + } if (isset($this->module_info->include_days) && $this->module_info->include_days > 0) { $args->start_regdate = date('YmdHis', time() - ($this->module_info->include_days * 86400)); diff --git a/modules/comment/comment.controller.php b/modules/comment/comment.controller.php index 31a42fba7..e1dc1bc8c 100644 --- a/modules/comment/comment.controller.php +++ b/modules/comment/comment.controller.php @@ -58,6 +58,11 @@ class CommentController extends Comment } } } + $yeokka_member_srl = Rhymix\Modules\Yeokbox\Models\Config::getConfig()->yeokka_member_srl; + if($logged_info->member_srl != $yeokka_member_srl && $oComment->getRegdateTime() < (time() - (86400 * 7))) + { + throw new Rhymix\Framework\Exception('작성 이후 7일 이상이 경과한 댓글은 추천할 수 없습니다.'); + } $point = 1; $allow_same_ip = ($comment_config->allow_vote_from_same_ip ?? 'N') === 'Y'; diff --git a/modules/document/document.controller.php b/modules/document/document.controller.php index 0265a56ec..3ddcfc128 100644 --- a/modules/document/document.controller.php +++ b/modules/document/document.controller.php @@ -70,6 +70,11 @@ class DocumentController extends Document } } } + $yeokka_member_srl = Rhymix\Modules\Yeokbox\Models\Config::getConfig()->yeokka_member_srl; + if($logged_info->member_srl != $yeokka_member_srl && $oDocument->getRegdateTime() < (time() - (86400 * 7))) + { + throw new Rhymix\Framework\Exception('작성 이후 7일 이상이 경과한 글은 추천할 수 없습니다.'); + } $point = 1; $allow_same_ip = ($document_config->allow_vote_from_same_ip ?? 'N') === 'Y'; diff --git a/modules/document/document.model.php b/modules/document/document.model.php index 446a52fd3..9fdab4e09 100644 --- a/modules/document/document.model.php +++ b/modules/document/document.model.php @@ -261,6 +261,7 @@ class DocumentModel extends Document else { self::_setSearchOption($obj, $args, $query_id, $use_division); + debugPrint($args); $output = executeQueryArray($query_id, $args, $args->columnList); } @@ -1430,6 +1431,10 @@ class DocumentModel extends Document { $args->s_voted_count = intval($searchOpt->s_voted_count); } + if (isset($searchOpt->s_readed_count) && $searchOpt->s_readed_count > 0) + { + $args->s_readed_count = intval($searchOpt->s_readed_count); + } // get directly module_srl by mid if(isset($searchOpt->mid) && $searchOpt->mid) diff --git a/modules/document/queries/getDocumentCount.xml b/modules/document/queries/getDocumentCount.xml index 4b064dcc2..94dd60a54 100644 --- a/modules/document/queries/getDocumentCount.xml +++ b/modules/document/queries/getDocumentCount.xml @@ -22,8 +22,8 @@ - - + + diff --git a/modules/document/queries/getDocumentCountByGroupStatus.xml b/modules/document/queries/getDocumentCountByGroupStatus.xml index ec922d296..7398fda36 100644 --- a/modules/document/queries/getDocumentCountByGroupStatus.xml +++ b/modules/document/queries/getDocumentCountByGroupStatus.xml @@ -23,8 +23,8 @@ - - + + diff --git a/modules/document/queries/getDocumentList.xml b/modules/document/queries/getDocumentList.xml index fa6ffd6b3..c11e6c57a 100644 --- a/modules/document/queries/getDocumentList.xml +++ b/modules/document/queries/getDocumentList.xml @@ -26,8 +26,8 @@ - - + + diff --git a/modules/document/queries/getDocumentListExtraSort.xml b/modules/document/queries/getDocumentListExtraSort.xml index 10b86b9e0..6964c8101 100644 --- a/modules/document/queries/getDocumentListExtraSort.xml +++ b/modules/document/queries/getDocumentListExtraSort.xml @@ -32,8 +32,8 @@ - - + + diff --git a/modules/document/queries/getDocumentListPage.xml b/modules/document/queries/getDocumentListPage.xml index c6aed3abb..a1322cc9e 100644 --- a/modules/document/queries/getDocumentListPage.xml +++ b/modules/document/queries/getDocumentListPage.xml @@ -26,8 +26,8 @@ - - + + diff --git a/modules/document/queries/getDocumentListUseIndex.xml b/modules/document/queries/getDocumentListUseIndex.xml index 68a5537bb..77b7015b3 100644 --- a/modules/document/queries/getDocumentListUseIndex.xml +++ b/modules/document/queries/getDocumentListUseIndex.xml @@ -29,8 +29,8 @@ - - + + diff --git a/modules/document/queries/getDocumentListWithExtraVars.xml b/modules/document/queries/getDocumentListWithExtraVars.xml index 25e5f7e36..3034f3932 100644 --- a/modules/document/queries/getDocumentListWithExtraVars.xml +++ b/modules/document/queries/getDocumentListWithExtraVars.xml @@ -35,8 +35,8 @@ - - + + diff --git a/modules/document/queries/getDocumentListWithinExtraVarsExtraSort.xml b/modules/document/queries/getDocumentListWithinExtraVarsExtraSort.xml index 78ac1cc24..888ae9ea2 100644 --- a/modules/document/queries/getDocumentListWithinExtraVarsExtraSort.xml +++ b/modules/document/queries/getDocumentListWithinExtraVarsExtraSort.xml @@ -32,8 +32,8 @@ - - + + diff --git a/modules/document/queries/getDocumentListWithinMember.xml b/modules/document/queries/getDocumentListWithinMember.xml index 08dfd76dd..555fbe5cd 100644 --- a/modules/document/queries/getDocumentListWithinMember.xml +++ b/modules/document/queries/getDocumentListWithinMember.xml @@ -20,8 +20,8 @@ - - + + diff --git a/modules/document/queries/getTrashList.xml b/modules/document/queries/getTrashList.xml index fe13af161..52cb97bbe 100644 --- a/modules/document/queries/getTrashList.xml +++ b/modules/document/queries/getTrashList.xml @@ -31,8 +31,8 @@ - - + + diff --git a/modules/spamfilter/spamfilter.model.php b/modules/spamfilter/spamfilter.model.php index 379cf776c..d19ca740e 100644 --- a/modules/spamfilter/spamfilter.model.php +++ b/modules/spamfilter/spamfilter.model.php @@ -141,7 +141,7 @@ class SpamfilterModel extends Spamfilter if (strpos($custom_message, '%s') !== false) { - $custom_message = sprintf($custom_message, escape($hit, false)); + $custom_message = sprintf($custom_message, substr(sha1($word), 0, 7)); } return new BaseObject(-1, $custom_message); diff --git a/modules/spamfilter/tpl/denied_word_list.html b/modules/spamfilter/tpl/denied_word_list.html index eb51e9bdd..d66aa5eec 100644 --- a/modules/spamfilter/tpl/denied_word_list.html +++ b/modules/spamfilter/tpl/denied_word_list.html @@ -12,8 +12,9 @@ {$lang->word} + 오류코드 {$lang->description} - {$lang->cmd_spamfilter_except_member} + {$lang->cmd_spamfilter_filter_html} {$lang->latest_hit} {$lang->hit} @@ -24,8 +25,9 @@ {$word_info->word} {$lang->cmd_spamfilter_is_regexp} + {substr(sha1($word_info->word), 0, 7)} {$word_info->description} - {$word_info->except_member} + {$word_info->filter_html} {zdate($word_info->latest_hit,'Y-m-d H:i')}- {$word_info->hit} From 0518b04fff4d04793ca043df7e09b6ba30e00258 Mon Sep 17 00:00:00 2001 From: Lastorder-DC <18280396+Lastorder-DC@users.noreply.github.com> Date: Thu, 5 Feb 2026 18:26:12 +0900 Subject: [PATCH 02/47] remove debug --- modules/document/document.model.php | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/document/document.model.php b/modules/document/document.model.php index 9fdab4e09..2697a1e7d 100644 --- a/modules/document/document.model.php +++ b/modules/document/document.model.php @@ -261,7 +261,6 @@ class DocumentModel extends Document else { self::_setSearchOption($obj, $args, $query_id, $use_division); - debugPrint($args); $output = executeQueryArray($query_id, $args, $args->columnList); } From 9d1738e21dcec2680a9dee3c01f2afa1e60e737e Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 5 Feb 2026 22:13:50 +0900 Subject: [PATCH 03/47] Add trigger before auto-login #2665 #2666 --- common/framework/Session.php | 2 +- modules/member/member.controller.php | 54 +++++++++++++++++++++------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/common/framework/Session.php b/common/framework/Session.php index 52027e80e..1a6e7242d 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -380,7 +380,7 @@ class Session self::$_autologin_key = self::_getAutologinKey(); if (!$member_srl && self::$_autologin_key) { - $member_srl = \MemberController::getInstance()->doAutologin(self::$_autologin_key); + $member_srl = \MemberController::doAutoLogin(self::$_autologin_key); if ($member_srl && self::isValid($member_srl)) { self::login($member_srl, false); diff --git a/modules/member/member.controller.php b/modules/member/member.controller.php index f5f972cdf..6a9e8b401 100644 --- a/modules/member/member.controller.php +++ b/modules/member/member.controller.php @@ -2387,7 +2387,7 @@ class MemberController extends Member * @param string $autologin_key * @return int|false */ - function doAutologin($autologin_key = null) + public static function doAutoLogin($autologin_key = null) { // Validate the key. if (strlen($autologin_key) == 48) @@ -2430,6 +2430,28 @@ class MemberController extends Member return false; } + // Get the member info and check it. + $member_info = MemberModel::getMemberInfo($output->data->member_srl); + if (!$member_info) + { + return false; + } + if (!empty($member_info->denied) && $member_info->denied === 'Y') + { + return false; + } + if (!empty($member_info->limit_date) && substr($member_info->limit_date, 0, 8) >= date('Ymd')) + { + return false; + } + + // Call a trigger before auto-login. + $trigger_output = ModuleHandler::triggerCall('member.doAutoLogin', 'before', $member_info); + if (!$trigger_output->toBool()) + { + return false; + } + // If the current security key matches, generate a new key. // If the previous key matches, don't update until the client has the current key. // Resending the current key in this case will be handled by the Session class. @@ -2462,13 +2484,10 @@ class MemberController extends Member } // Update the last login time. - executeQuery('member.updateLastLogin', (object)['member_srl' => $output->data->member_srl]); - self::clearMemberCache($output->data->member_srl); + self::updateLastLogin($output->data->member_srl); - // Call a trigger after validate security key (after) - $trigger_obj = new stdClass(); - $trigger_obj->member_srl = $output->data->member_srl; - ModuleHandler::triggerCall('member.doAutoLogin', 'after', $trigger_obj); + // Call a trigger after validate security key. + ModuleHandler::triggerCall('member.doAutoLogin', 'after', $member_info); // Return the member_srl. return intval($output->data->member_srl); @@ -2640,11 +2659,6 @@ class MemberController extends Member } } - // Update the latest login time - $args->member_srl = $member_info->member_srl; - $output = executeQuery('member.updateLastLogin', $args); - self::clearMemberCache($args->member_srl); - // Check if there is recoding table. $oDB = DB::getInstance(); if($oDB->isTableExists('member_count_history') && $config->enable_login_fail_report != 'N') @@ -2702,6 +2716,7 @@ class MemberController extends Member // Log in! Rhymix\Framework\Session::login($member_info->member_srl); + self::updateLastLogin($member_info->member_srl); $this->setSessionInfo(); // Log out all other sessions if so configured. @@ -2716,10 +2731,23 @@ class MemberController extends Member return $output; } + /** + * Update the last login timestamp of a member. + * + * @param int $member_srl + * @return object + */ + public static function updateLastLogin(int $member_srl) + { + $output = executeQuery('member.updateLastLogin', ['member_srl' => $member_srl]); + self::clearMemberCache($member_srl); + return $output; + } + /** * Update or create session information */ - function setSessionInfo() + public function setSessionInfo() { // If your information came through the current session information to extract information from the users $member_info = Rhymix\Framework\Session::getMemberInfo(true); From d9a6c577fde190b8b64674d8a137b7380073bb0b Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 5 Feb 2026 22:26:24 +0900 Subject: [PATCH 04/47] Restore available fields in password reset email #2663 --- modules/member/member.controller.php | 16 ++++++++++++++++ .../skins/default/find_member_account_mail.html | 8 +++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/modules/member/member.controller.php b/modules/member/member.controller.php index 6a9e8b401..0bdfceb13 100644 --- a/modules/member/member.controller.php +++ b/modules/member/member.controller.php @@ -1841,6 +1841,7 @@ class MemberController extends Member } Context::set('auth_args', $args); + // Prepare member information to be included in the email #2594 #2663 $memberInfo = array(); if (in_array('user_id', $member_config->identifiers)) { @@ -1850,6 +1851,21 @@ class MemberController extends Member { $memberInfo[$lang->email_address] = $member_info->email_address; } + if (in_array('phone_number', $member_config->identifiers)) + { + $phone_number = $member_info->phone_number; + if($member_config->phone_number_hide_country !== 'Y') + { + $phone_number = Rhymix\Framework\i18n::formatPhoneNumber($phone_number, $member_info->phone_country); + } + elseif($member_config->phone_number_default_country === 'KOR' && ($member_info->phone_country === 'KOR' || $member_info->phone_country == '82')) + { + $phone_number = Rhymix\Framework\Korea::formatPhoneNumber($phone_number); + } + $memberInfo[$lang->phone_number] = $phone_number; + } + $memberInfo[$lang->user_name] = $member_info->user_name; + $memberInfo[$lang->nick_name] = $member_info->nick_name; Context::set('memberInfo', $memberInfo); if(!$member_config->skin) $member_config->skin = "default"; diff --git a/modules/member/skins/default/find_member_account_mail.html b/modules/member/skins/default/find_member_account_mail.html index 36ace04da..151368fa4 100644 --- a/modules/member/skins/default/find_member_account_mail.html +++ b/modules/member/skins/default/find_member_account_mail.html @@ -2,7 +2,13 @@
  • {$lang->site} : {getUrl()}
  • -
  • {$name} : {$value}
  • + +
  • {$lang->user_id} : {$memberInfo[$lang->user_id]}
  • + +
  • {$lang->email_address} : {$memberInfo[$lang->email_address]}
  • + +
  • {$lang->phone_number} : {$memberInfo[$lang->phone_number]}
  • +
  • {$lang->password} : {$auth_args->new_password}

From d824bc9da378f0d8425bf9f615dae2790c53938c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 5 Feb 2026 22:36:31 +0900 Subject: [PATCH 05/47] Clean up method of loading default sender info #2661 --- .../admin/controllers/systemconfig/Notification.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/admin/controllers/systemconfig/Notification.php b/modules/admin/controllers/systemconfig/Notification.php index 0068d6a5a..79a8eaec2 100644 --- a/modules/admin/controllers/systemconfig/Notification.php +++ b/modules/admin/controllers/systemconfig/Notification.php @@ -20,17 +20,16 @@ class Notification extends Base public function dispAdminConfigNotification() { // Load advanced mailer module (for lang). - $oAdvancedMailerAdminView = \Advanced_mailerAdminView::getInstance(); + $oAdvancedMailerController = \Advanced_mailerController::getInstance(); // Load advanced mailer config. - $advanced_mailer_config = $oAdvancedMailerAdminView->getConfig(); + $advanced_mailer_config = $oAdvancedMailerController->getConfig(); Context::set('advanced_mailer_config', $advanced_mailer_config); // Load member config. - $member_config = ModuleModel::getModuleConfig('member'); - Context::set('member_config', $member_config); - Context::set('webmaster_name', !empty($member_config->webmaster_name) ? $member_config->webmaster_name : 'webmaster'); - Context::set('webmaster_email', $member_config->webmaster_email ?? ''); + $default_identity = $oAdvancedMailerController->getDefaultEmailIdentity(); + Context::set('webmaster_name', $default_identity[1]); + Context::set('webmaster_email', $default_identity[0]); // Load module config. $module_config = ModuleModel::getModuleConfig('module'); @@ -89,7 +88,7 @@ class Notification extends Base $vars = Context::getRequestVars(); // Load advanced mailer module (for lang). - $oAdvancedMailerAdminView = \Advanced_mailerAdminView::getInstance(); + $oAdvancedMailerController = \Advanced_mailerController::getInstance(); // Validate the mail sender's information. if (!$vars->mail_default_name) From 8920cb74912868fa0d2aa6b5266ffcd6b87338d5 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 5 Feb 2026 22:39:33 +0900 Subject: [PATCH 06/47] Fix incorrect path conversion on Windows #2667 --- modules/module/module.model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/module/module.model.php b/modules/module/module.model.php index 3c4182a46..02c1a8e72 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -1012,7 +1012,7 @@ class ModuleModel extends Module $skin_list[$skin_name] = $skin_info; } - $tmpPath = strtr($path, array('/' => ' ')); + $tmpPath = strtr($path, array('/' => ' ', '\\' => ' ')); $tmpPath = trim($tmpPath); $module = array_last(explode(' ', $tmpPath)); From 26c59c251cac78c5420065042994297ba6a342ff Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 5 Feb 2026 23:05:19 +0900 Subject: [PATCH 07/47] Fix incorrect conversion of JS template variable containing path #2657 --- common/framework/Template.php | 2 +- tests/unit/framework/parsers/TemplateParserV2Test.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/common/framework/Template.php b/common/framework/Template.php index 387c3ba6a..bd7d9165d 100644 --- a/common/framework/Template.php +++ b/common/framework/Template.php @@ -528,7 +528,7 @@ class Template */ public function isRelativePath(string $path): bool { - return !preg_match('#^((?:https?|file|data):|[\/\{<])#i', $path); + return !preg_match('#^((?:https?|file|data):|[\/\{\\\\<$\#@]|&\#x1B;)#i', $path); } /** diff --git a/tests/unit/framework/parsers/TemplateParserV2Test.php b/tests/unit/framework/parsers/TemplateParserV2Test.php index 9754800cf..0257ae05f 100644 --- a/tests/unit/framework/parsers/TemplateParserV2Test.php +++ b/tests/unit/framework/parsers/TemplateParserV2Test.php @@ -676,6 +676,16 @@ class TemplateParserV2Test extends \Codeception\Test\Unit $source = '

url(img/foo.jpg); }

'; $target = '

url(img/foo.jpg); }

'; $this->assertEquals($target, $this->_parse($source)); + + // No conversion if it's a template variable + $source = ''; + $target = ''; + $this->assertEquals($target, $this->_parse($source)); + + // No conversion if it's a JS template variable + $source = '@verbatim let src = `` @endverbatim'; + $target = ' let src = `` '; + $this->assertEquals($target, $this->_parse($source)); } public function testBlockConditions() From 5a8c669c421c82becfd66eb06773c59ad6ac4874 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 5 Feb 2026 23:16:38 +0900 Subject: [PATCH 08/47] Fix duplicated layout info when layout.html (or layout.blade.php) does not exist #2132 #2670 --- modules/layout/layout.model.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/layout/layout.model.php b/modules/layout/layout.model.php index 402f8b348..b12e80112 100644 --- a/modules/layout/layout.model.php +++ b/modules/layout/layout.model.php @@ -167,7 +167,7 @@ class LayoutModel extends Layout if($layout) { - if(count($instanceList) < 1 && $downloadedList[$layout]) + if(count($instanceList) < 1 && isset($downloadedList[$layout])) { $insertArgs = new stdClass(); $insertArgs->layout_srl = getNextSequence(); @@ -383,7 +383,7 @@ class LayoutModel extends Layout // Get information of the layout $layout_info = self::getLayoutInfo($layout, null, $layout_type); - if(!$layout_info) + if (!$layout_info || !self::isExistsLayoutFile($layout, $layout_type)) { continue; } From 2f8c4ca77da5fed48fee64b90994f25d1a264309 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 6 Feb 2026 18:06:16 +0900 Subject: [PATCH 09/47] Enable customizing the number of comments/replies required to prevent editing or deleting a post --- modules/admin/tpl/js/admin.js | 11 +++-- modules/board/board.admin.controller.php | 16 ++++++- modules/board/board.controller.php | 20 +++++---- modules/board/board.view.php | 18 +++++--- modules/board/lang/en.php | 19 ++++---- modules/board/lang/ko.php | 17 +++++--- modules/board/lang/zh-CN.php | 4 +- modules/board/tpl/board_insert.html | 55 ++++++++++++++++++------ 8 files changed, 110 insertions(+), 50 deletions(-) diff --git a/modules/admin/tpl/js/admin.js b/modules/admin/tpl/js/admin.js index 4d3d52c45..6ee132312 100644 --- a/modules/admin/tpl/js/admin.js +++ b/modules/admin/tpl/js/admin.js @@ -347,9 +347,14 @@ jQuery(function($){ $.fn.checkToggle = function(){ function check(){ setTimeout(function(){ - $(':checked').parent('label').addClass('checked'); - $(':not(":checked")').parent('label').removeClass('checked'); - },0); + $(':radio, :checkbox').each(function() { + if ($(this).is(':checked')) { + $(this).parent('label').addClass('checked'); + } else { + $(this).parent('label').removeClass('checked'); + } + }); + }, 0); } this.change(check); check(); diff --git a/modules/board/board.admin.controller.php b/modules/board/board.admin.controller.php index d1f0c329c..b6c35ca40 100644 --- a/modules/board/board.admin.controller.php +++ b/modules/board/board.admin.controller.php @@ -58,7 +58,21 @@ class BoardAdminController extends Board { if($args->use_anonymous != 'Y') $args->use_anonymous = 'N'; if($args->anonymous_except_admin != 'Y') $args->anonymous_except_admin = 'N'; if($args->consultation != 'Y') $args->consultation = 'N'; - if($args->protect_content != 'Y') $args->protect_content = 'N'; + + // Protection settings + if ($args->protect_content != 'Y') $args->protect_content = 'N'; + if ($args->protect_document_regdate === 'Y') + { + $args->protect_document_regdate = intval($args->protect_document_regdate_limit ?? 0) ?: null; + } + if ($args->protect_comment_regdate === 'Y') + { + $args->protect_comment_regdate = intval($args->protect_comment_regdate_limit ?? 0) ?: null; + } + unset($args->protect_document_regdate_limit); + unset($args->protect_comment_regdate_limit); + + // Admin protection settings if($this->user->isAdmin()) { if($args->protect_admin_content_update != 'Y') $args->protect_admin_content_update = 'N'; diff --git a/modules/board/board.controller.php b/modules/board/board.controller.php index 8bed7c029..12f873ef0 100644 --- a/modules/board/board.controller.php +++ b/modules/board/board.controller.php @@ -171,9 +171,10 @@ class BoardController extends Board // Protect document by comment if($this->module_info->protect_content == 'Y' || $this->module_info->protect_update_content == 'Y') { - if($oDocument->get('comment_count') > 0 && !$this->grant->manager) + $comment_limit = $this->module_info->protect_update_content_limit ?? 1; + if($oDocument->get('comment_count') >= $comment_limit && !$this->grant->manager) { - throw new Rhymix\Framework\Exception('msg_protect_update_content'); + throw new Rhymix\Framework\Exception(sprintf(lang('msg_protect_update_content'), $comment_limit)); } } @@ -358,9 +359,10 @@ class BoardController extends Board // check protect content if($this->module_info->protect_content == 'Y' || $this->module_info->protect_delete_content == 'Y') { - if($oDocument->get('comment_count') > 0 && $this->grant->manager == false) + $comment_limit = $this->module_info->protect_delete_content_limit ?? 1; + if($oDocument->get('comment_count') >= $comment_limit && $this->grant->manager == false) { - throw new Rhymix\Framework\Exception('msg_protect_delete_content'); + throw new Rhymix\Framework\Exception(sprintf(lang('msg_protect_delete_content'), $comment_limit)); } } @@ -538,10 +540,11 @@ class BoardController extends Board $comment = CommentModel::getComment($obj->comment_srl); if($this->module_info->protect_update_comment === 'Y' && $this->grant->manager == false) { + $childs_limit = $this->module_info->protect_update_comment_limit ?? 1; $childs = CommentModel::getChildComments($obj->comment_srl); - if(count($childs) > 0) + if(count($childs) >= $childs_limit) { - throw new Rhymix\Framework\Exception('msg_board_update_protect_comment'); + throw new Rhymix\Framework\Exception(sprintf(lang('msg_board_update_protect_comment'), $childs_limit)); } } } @@ -656,10 +659,11 @@ class BoardController extends Board $childs = null; if($this->module_info->protect_delete_comment === 'Y' && $this->grant->manager == false) { + $childs_limit = $this->module_info->protect_delete_comment_limit ?? 1; $childs = CommentModel::getChildComments($comment_srl); - if(count($childs) > 0) + if(count($childs) >= $childs_limit) { - throw new Rhymix\Framework\Exception('msg_board_delete_protect_comment'); + throw new Rhymix\Framework\Exception(sprintf(lang('msg_board_delete_protect_comment'), $childs_limit)); } } diff --git a/modules/board/board.view.php b/modules/board/board.view.php index e6cd6ee2d..e9f19b1e9 100644 --- a/modules/board/board.view.php +++ b/modules/board/board.view.php @@ -878,9 +878,10 @@ class BoardView extends Board } if ($this->module_info->protect_content === 'Y' || $this->module_info->protect_update_content === 'Y') { - if($oDocument->get('comment_count') > 0 && $this->grant->manager == false) + $comment_limit = $this->module_info->protect_update_content_limit ?? 1; + if($oDocument->get('comment_count') >= $comment_limit && $this->grant->manager == false) { - throw new Rhymix\Framework\Exception('msg_protect_update_content'); + throw new Rhymix\Framework\Exception(sprintf(lang('msg_protect_update_content'), $comment_limit)); } } @@ -1103,9 +1104,10 @@ class BoardView extends Board if($this->module_info->protect_content == "Y" || $this->module_info->protect_delete_content == 'Y') { + $comment_limit = $this->module_info->protect_delete_content_limit ?? 1; if($oDocument->get('comment_count')>0 && $this->grant->manager == false) { - throw new Rhymix\Framework\Exception('msg_protect_delete_content'); + throw new Rhymix\Framework\Exception(sprintf(lang('msg_protect_delete_content'), $comment_limit)); } } @@ -1299,10 +1301,11 @@ class BoardView extends Board } if($this->module_info->protect_update_comment === 'Y' && $this->grant->manager == false) { + $childs_limit = $this->module_info->protect_update_comment_limit ?? 1; $childs = CommentModel::getChildComments($comment_srl); - if(count($childs) > 0) + if(count($childs) >= $childs_limit) { - throw new Rhymix\Framework\Exception('msg_board_update_protect_comment'); + throw new Rhymix\Framework\Exception(sprintf(lang('msg_board_update_protect_comment'), $childs_limit)); } } @@ -1387,10 +1390,11 @@ class BoardView extends Board if($this->module_info->protect_delete_comment === 'Y' && $this->grant->manager == false) { + $childs_limit = $this->module_info->protect_delete_comment_limit ?? 1; $childs = CommentModel::getChildComments($comment_srl); - if(count($childs) > 0) + if(count($childs) >= $childs_limit) { - throw new Rhymix\Framework\Exception('msg_board_delete_protect_comment'); + throw new Rhymix\Framework\Exception(sprintf(lang('msg_board_delete_protect_comment'), $childs_limit)); } } diff --git a/modules/board/lang/en.php b/modules/board/lang/en.php index 9c9b4f9b0..88f07c98b 100644 --- a/modules/board/lang/en.php +++ b/modules/board/lang/en.php @@ -44,7 +44,6 @@ $lang->about_secret = 'Users will be able to write secret posts or comments.'; $lang->about_admin_mail = 'Send an e-mail when a new post or comment is submitted. Separate multiple recipients with a comma.'; $lang->about_list_config = 'If using list-style skin, you may arrange items to display. However, this feature might not be availble for non-official skins. If you double-click target items and display items, then you can add / remove them'; $lang->about_use_status = 'Please select status that can be selected when you write a post.'; -$lang->about_protect_comment = 'Prevent updating or deleting a comment if it has children.'; $lang->about_update_log = 'Store a log of every version of a post every time it is updated.'; $lang->skip_bottom_list_for_olddoc = 'Do not calculate the bottom list exactly when viewing an old post.'; $lang->skip_bottom_list_for_robot = 'Do not calculate the bottom list exactly when a robot is visiting.'; @@ -59,7 +58,7 @@ $lang->hide_category = 'Hide categories'; $lang->about_hide_category = 'Temporarily disable categories. Existing categories will not be deleted.
Unless you select the option below, users may not be able to write anymore.'; $lang->allow_no_category = 'Do not require category'; $lang->about_allow_no_category = 'Allow posting without selecting a category.
Admins are never required to select a category.'; -$lang->protect_content = 'Protect Content'; +$lang->protect_content = 'Protect Post'; $lang->protect_comment = 'Protect Comment'; $lang->protect_admin_content = 'Protect Admin Content'; $lang->protect_regdate = 'Update/Delete Time Limit'; @@ -73,15 +72,19 @@ $lang->about_inline_data_url_limit = 'Restrict data: URLs that can be used to ev $lang->update_order_on_comment = 'Update Post on New Comment'; $lang->about_update_order_on_comment = 'When a new comment is posted, update the update timestamp of the parent post. This is needed for forums.'; $lang->about_filter_specialchars = 'Prevent use of excessive Unicode accents, RLO characters, and other symbols that hinder readability.'; -$lang->about_protect_regdate = 'Prevent updating or deleting a post or comment after a certain amount of time has passed. (Unit: day)'; -$lang->about_protect_content = 'Prevent updating a post if there are comments on it.'; +$lang->about_protect_content_update = 'Prevent updating a post if it has %d or more comments.'; +$lang->about_protect_content_delete = 'Prevent deleting a post if it has %d or more comments.'; +$lang->about_protect_comment_update = 'Prevent updating a comment if it has %d or more replies.'; +$lang->about_protect_comment_delete = 'Prevent deleting a comment if it has %d or more replies.'; +$lang->about_protect_document_regdate = 'Prevent updating or deleting a post after %d days.'; +$lang->about_protect_comment_regdate = 'Prevent updating or deleting a comment after %d days.'; $lang->about_protect_admin_content = 'Prevent updating or deleting a post or comment written by the administrator, even by a user who is permitted to manage the board.'; -$lang->msg_protect_delete_content = 'You cannot delete a post with comments on it.'; -$lang->msg_protect_update_content = 'You cannot update a post with comments on it.'; +$lang->msg_protect_update_content = 'You cannot update a post with %d or more comments on it.'; +$lang->msg_protect_delete_content = 'You cannot delete a post with %d or more comments on it.'; +$lang->msg_board_update_protect_comment = 'You cannot update a comment with %d or more replies on it.'; +$lang->msg_board_delete_protect_comment = 'You cannot delete a comment with %d or more replies on it.'; $lang->msg_admin_document_no_modify = 'You cannot edit the administrator\'s post.'; $lang->msg_admin_comment_no_modify = 'You cannot edit the administrator\'s comment.'; -$lang->msg_board_delete_protect_comment = 'You cannot delete a comment when there are replies.'; -$lang->msg_board_update_protect_comment = 'You cannot update a comment when there are replies.'; $lang->msg_protect_regdate_document = 'You cannot update or delete a post after %d days.'; $lang->msg_protect_regdate_comment = 'You cannot update or delete a comment after %d days.'; $lang->msg_dont_have_update_log = 'This post has no update log.'; diff --git a/modules/board/lang/ko.php b/modules/board/lang/ko.php index 0820f57ed..f53cf5db6 100644 --- a/modules/board/lang/ko.php +++ b/modules/board/lang/ko.php @@ -44,7 +44,6 @@ $lang->about_secret = '게시판 및 댓글의 비밀글 기능을 사용할 수 $lang->about_admin_mail = '새 글이나 댓글이 등록되면 지정된 메일 주소로 알림을 받습니다. 여러 메일 주소를 지정하려면 쉼표(,)로 구분하세요.'; $lang->about_list_config = '게시판의 목록형식 사용시 원하는 항목들로 배치를 할 수 있습니다. 단 스킨에서 지원하지 않는 경우 불가능합니다. 대상항목/ 표시항목의 항목을 더블클릭하면 추가/ 제거가 됩니다.'; $lang->about_use_status = '글 작성 시 선택할 수 있는 상태를 지정해주세요.'; -$lang->about_protect_comment = '댓글의 댓글이 있을경우 해당댓글을 삭제 및 수정을 할 수 없도록 합니다.'; $lang->about_update_log = '게시글을 수정할 경우 수정한 내역을 저장하도록 합니다.'; $lang->skip_bottom_list_for_olddoc = '오래된 게시물 열람시 하단 목록을 정확하게 계산하지 않음'; $lang->skip_bottom_list_for_robot = '로봇 방문시 하단 목록을 정확하게 계산하지 않음'; @@ -73,15 +72,19 @@ $lang->about_inline_data_url_limit = 'data: URL을 사용하여 첨부 제한을 $lang->update_order_on_comment = '댓글 작성시 글 수정 시각 갱신'; $lang->about_update_order_on_comment = '댓글이 작성되면 해당 글의 수정 시각을 갱신합니다. 포럼형 게시판, 최근 댓글 표시 기능 등에 필요합니다.'; $lang->about_filter_specialchars = '가독성에 악영향을 주는 과도한 유니코드 악센트 기호의 조합, RLO 문자 등의 사용을 금지합니다.'; -$lang->about_protect_regdate = '글이나 댓글을 작성한 후 일정 기간이 지나면 수정 또는 삭제할 수 없도록 합니다. (단위 : day)'; -$lang->about_protect_content = '댓글이 달린 글은 수정 또는 삭제할 수 없도록 합니다.'; +$lang->about_protect_content_update = '댓글이 %d개 이상 달린 글은 수정할 수 없도록 합니다.'; +$lang->about_protect_content_delete = '댓글이 %d개 이상 달린 글은 삭제할 수 없도록 합니다.'; +$lang->about_protect_comment_update = '대댓글이 %d개 이상 달린 댓글은 수정할 수 없도록 합니다.'; +$lang->about_protect_comment_delete = '대댓글이 %d개 이상 달린 댓글은 삭제할 수 없도록 합니다.'; +$lang->about_protect_document_regdate = '작성 후 %d일 이상 지난 글은 수정 또는 삭제할 수 없도록 합니다.'; +$lang->about_protect_comment_regdate = '작성 후 %d일 이상 지난 댓글은 수정 또는 삭제할 수 없도록 합니다.'; $lang->about_protect_admin_content = '최고관리자가 작성한 글이나 댓글은 게시판 관리 권한이 있는 회원이라도 수정 또는 삭제할 수 없도록 합니다.'; -$lang->msg_protect_delete_content = '댓글이 달린 글은 삭제할 수 없습니다.'; -$lang->msg_protect_update_content = '댓글이 달린 글은 수정할 수 없습니다.'; +$lang->msg_protect_update_content = '댓글이 %d개 이상 달린 글은 수정할 수 없습니다.'; +$lang->msg_protect_delete_content = '댓글이 %d개 이상 달린 글은 삭제할 수 없습니다.'; +$lang->msg_board_update_protect_comment = '대댓글이 %d개 이상 달린 댓글은 수정할 수 없습니다.'; +$lang->msg_board_delete_protect_comment = '대댓글이 %d개 이상 달린 댓글은 삭제할 수 없습니다.'; $lang->msg_admin_document_no_modify = '최고관리자의 게시물을 수정할 권한이 없습니다.'; $lang->msg_admin_comment_no_modify = '최고관리자의 댓글을 수정할 권한이 없습니다.'; -$lang->msg_board_delete_protect_comment = '대댓글이 달린 댓글은 삭제할 수 없습니다.'; -$lang->msg_board_update_protect_comment = '대댓글이 달린 댓글은 수정할 수 없습니다.'; $lang->msg_protect_regdate_document = '%d일 이상 지난 글은 수정 또는 삭제할 수 없습니다.'; $lang->msg_protect_regdate_comment = '%d일 이상 지난 댓글은 수정 또는 삭제할 수 없습니다.'; $lang->msg_dont_have_update_log = '업데이트 로그가 기록되어 있지 않은 게시글입니다.'; diff --git a/modules/board/lang/zh-CN.php b/modules/board/lang/zh-CN.php index 1f9196da7..c399112cb 100644 --- a/modules/board/lang/zh-CN.php +++ b/modules/board/lang/zh-CN.php @@ -72,8 +72,8 @@ $lang->about_filter_specialchars = '禁止使用影响阅读的内容的符号 $lang->about_non_login_vote = '非会员也可以推荐。'; $lang->about_protect_regdate = '发布帖子或回复过一定时间的话无法修改或删除。(单位:天)'; $lang->about_protect_content = '无法删除或修改有回复的帖子。'; -$lang->msg_protect_delete_content = '无法删除有回复的帖子。'; -$lang->msg_protect_update_content = '无法修改有回复的帖子。'; +$lang->msg_protect_delete_content = '评论数超过 %d 条的帖子无法删除。'; +$lang->msg_protect_update_content = '评论数超过 %d 条的帖子无法编辑。'; $lang->msg_admin_document_no_modify = '无法修改管理员的帖子。'; $lang->msg_admin_comment_no_modify = '无法修改管理员的回复。'; $lang->msg_board_delete_protect_comment = '回复里有他人回复的时候无法删除。'; diff --git a/modules/board/tpl/board_insert.html b/modules/board/tpl/board_insert.html index 0478aa728..97b429794 100644 --- a/modules/board/tpl/board_insert.html +++ b/modules/board/tpl/board_insert.html @@ -362,17 +362,52 @@
- - -

{$lang->about_protect_content}

+ +
- - -

{$lang->about_protect_comment}

+ + +
+
+
+ +
+ +
@@ -385,14 +420,6 @@ -
- -
- {$lang->document} : - {$lang->comment} : -

{$lang->about_protect_regdate}

-
-
From 867014d0f4724dd34dc89d27184b4804073b4eff Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 6 Feb 2026 18:13:40 +0900 Subject: [PATCH 10/47] Don't filter by extra var lang when sorting by numeric value --- modules/document/document.model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/document/document.model.php b/modules/document/document.model.php index ee7165775..c12daa936 100644 --- a/modules/document/document.model.php +++ b/modules/document/document.model.php @@ -1558,13 +1558,13 @@ class DocumentModel extends Document if($searchOpt->isExtraVars) { $args->sort_eid = $args->sort_index; - $args->sort_lang = Context::getLangType(); if ($searchOpt->isExtraVarsSortAsNumber ?? false) { $args->sort_index = 'extra_sort.sort_value'; } else { + $args->sort_lang = Context::getLangType(); $args->sort_index = 'extra_sort.value'; } } From 0e013367a0ab6b7d0435395952d5abfbbeff9da5 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 6 Feb 2026 21:48:23 +0900 Subject: [PATCH 11/47] Fix misspelled variables and incorrect config references --- modules/board/board.controller.php | 12 ++++++------ modules/board/board.view.php | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/modules/board/board.controller.php b/modules/board/board.controller.php index 12f873ef0..36875a087 100644 --- a/modules/board/board.controller.php +++ b/modules/board/board.controller.php @@ -597,11 +597,11 @@ class BoardController extends Board { if($this->module_info->protect_comment_regdate > 0 && $this->grant->manager == false) { - if($comment->get('regdate') < date('YmdHis', strtotime('-'.$this->module_info->protect_document_regdate.' day'))) + if($comment->get('regdate') < date('YmdHis', strtotime('-'.$this->module_info->protect_comment_regdate.' day'))) { $format = lang('msg_protect_regdate_comment'); - $massage = sprintf($format, $this->module_info->protect_document_regdate); - throw new Rhymix\Framework\Exception($massage); + $message = sprintf($format, $this->module_info->protect_comment_regdate); + throw new Rhymix\Framework\Exception($message); } } // check the grant @@ -678,11 +678,11 @@ class BoardController extends Board if($this->module_info->protect_comment_regdate > 0 && $this->grant->manager == false) { - if($comment->get('regdate') < date('YmdHis', strtotime('-'.$this->module_info->protect_document_regdate.' day'))) + if($comment->get('regdate') < date('YmdHis', strtotime('-'.$this->module_info->protect_comment_regdate.' day'))) { $format = lang('msg_protect_regdate_comment'); - $massage = sprintf($format, $this->module_info->protect_document_regdate); - throw new Rhymix\Framework\Exception($massage); + $message = sprintf($format, $this->module_info->protect_comment_regdate); + throw new Rhymix\Framework\Exception($message); } } // generate comment controller object diff --git a/modules/board/board.view.php b/modules/board/board.view.php index e9f19b1e9..117ff0219 100644 --- a/modules/board/board.view.php +++ b/modules/board/board.view.php @@ -872,8 +872,8 @@ class BoardView extends Board if($oDocument->get('regdate') < date('YmdHis', strtotime('-'.$this->module_info->protect_document_regdate.' day'))) { $format = lang('msg_protect_regdate_document'); - $massage = sprintf($format, $this->module_info->protect_document_regdate); - throw new Rhymix\Framework\Exception($massage); + $message = sprintf($format, $this->module_info->protect_document_regdate); + throw new Rhymix\Framework\Exception($message); } } if ($this->module_info->protect_content === 'Y' || $this->module_info->protect_update_content === 'Y') @@ -1096,18 +1096,18 @@ class BoardView extends Board { if($oDocument->get('regdate') < date('YmdHis', strtotime('-'.$this->module_info->protect_document_regdate.' day'))) { - $format = lang('msg_protect_regdate_document'); - $massage = sprintf($format, $this->module_info->protect_document_regdate); - throw new Rhymix\Framework\Exception($massage); + $format = lang('msg_protect_regdate_document'); + $message = sprintf($format, $this->module_info->protect_document_regdate); + throw new Rhymix\Framework\Exception($message); } } if($this->module_info->protect_content == "Y" || $this->module_info->protect_delete_content == 'Y') { $comment_limit = $this->module_info->protect_delete_content_limit ?? 1; - if($oDocument->get('comment_count')>0 && $this->grant->manager == false) + if($oDocument->get('comment_count') >= $comment_limit && $this->grant->manager == false) { - throw new Rhymix\Framework\Exception(sprintf(lang('msg_protect_delete_content'), $comment_limit)); + return $this->dispBoardMessage(sprintf(lang('msg_protect_delete_content'), $comment_limit)); } } @@ -1292,11 +1292,11 @@ class BoardView extends Board if($this->module_info->protect_comment_regdate > 0 && $this->grant->manager == false) { - if($oComment->get('regdate') < date('YmdHis', strtotime('-'.$this->module_info->protect_document_regdate.' day'))) + if($oComment->get('regdate') < date('YmdHis', strtotime('-'.$this->module_info->protect_comment_regdate.' day'))) { - $format = lang('msg_protect_regdate_comment'); - $massage = sprintf($format, $this->module_info->protect_document_regdate); - throw new Rhymix\Framework\Exception($massage); + $format = lang('msg_protect_regdate_comment'); + $message = sprintf($format, $this->module_info->protect_comment_regdate); + throw new Rhymix\Framework\Exception($message); } } if($this->module_info->protect_update_comment === 'Y' && $this->grant->manager == false) @@ -1380,11 +1380,11 @@ class BoardView extends Board if($this->module_info->protect_comment_regdate > 0 && $this->grant->manager == false) { - if($oComment->get('regdate') < date('YmdHis', strtotime('-'.$this->module_info->protect_document_regdate.' day'))) + if($oComment->get('regdate') < date('YmdHis', strtotime('-'.$this->module_info->protect_comment_regdate.' day'))) { $format = lang('msg_protect_regdate_comment'); - $massage = sprintf($format, $this->module_info->protect_document_regdate); - throw new Rhymix\Framework\Exception($massage); + $message = sprintf($format, $this->module_info->protect_comment_regdate); + throw new Rhymix\Framework\Exception($message); } } From 4339d01a75a546635cadcd79d566b3a159e398e9 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 7 Feb 2026 21:10:34 +0900 Subject: [PATCH 12/47] Update Daum/Kakao postcode API URL #2672 --- modules/krzip/lang/en.php | 2 +- modules/krzip/lang/ko.php | 2 +- modules/krzip/tpl/js/daumapi.js | 2 +- modules/krzip/tpl/template.daumapi.html | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/krzip/lang/en.php b/modules/krzip/lang/en.php index 0fc24fd4f..6a13a8046 100644 --- a/modules/krzip/lang/en.php +++ b/modules/krzip/lang/en.php @@ -1,7 +1,7 @@ krzip = 'Korean Postal Code'; $lang->cmd_krzip_api_type = 'Select API Provider'; -$lang->cmd_krzip_daumapi = 'Daum-Kakao API'; +$lang->cmd_krzip_daumapi = 'Kakao API'; $lang->cmd_krzip_epostapi = 'Korea Post API'; $lang->cmd_krzip_postcodify = 'Postcodify'; $lang->cmd_krzip_regkey = 'Registration Key'; diff --git a/modules/krzip/lang/ko.php b/modules/krzip/lang/ko.php index c7293c3cf..d1c3fd193 100644 --- a/modules/krzip/lang/ko.php +++ b/modules/krzip/lang/ko.php @@ -1,7 +1,7 @@ krzip = '한국 우편번호'; $lang->cmd_krzip_api_type = 'API 선택'; -$lang->cmd_krzip_daumapi = '다음 우편번호'; +$lang->cmd_krzip_daumapi = '카카오 우편번호'; $lang->cmd_krzip_epostapi = '우체국 우편번호'; $lang->cmd_krzip_postcodify = 'Postcodify'; $lang->cmd_krzip_regkey = '등록키'; diff --git a/modules/krzip/tpl/js/daumapi.js b/modules/krzip/tpl/js/daumapi.js index 49db03274..a8170e5e6 100644 --- a/modules/krzip/tpl/js/daumapi.js +++ b/modules/krzip/tpl/js/daumapi.js @@ -24,7 +24,7 @@ guide : $this.find(".krzip-guide") }; - var krzip = new daum.Postcode({ + var krzip = new kakao.Postcode({ oncomplete: function (response) { var fullAddr = "", extraAddr = ""; diff --git a/modules/krzip/tpl/template.daumapi.html b/modules/krzip/tpl/template.daumapi.html index 1270cbd00..95322eef5 100644 --- a/modules/krzip/tpl/template.daumapi.html +++ b/modules/krzip/tpl/template.daumapi.html @@ -1,8 +1,8 @@ - + + + - - From b9a512c007b79490260b1cb3e520681582c5c185 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 8 Feb 2026 10:56:28 +0900 Subject: [PATCH 13/47] Fix add IP to spamfilter menu not working --- modules/comment/comment.model.php | 2 +- modules/document/document.model.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/comment/comment.model.php b/modules/comment/comment.model.php index 3ac2dd1e1..9ee84cbe0 100644 --- a/modules/comment/comment.model.php +++ b/modules/comment/comment.model.php @@ -113,7 +113,7 @@ class CommentModel extends Comment $url = getUrl('', 'module', 'admin', 'act', 'dispCommentAdminList', 'search_target', 'ipaddress', 'search_keyword', $oComment->getIpAddress()); $oCommentController->addCommentPopupMenu($url, 'cmd_search_by_ipaddress', '', 'TraceByIpaddress'); - $url = sprintf("var params = new Array(); params['ipaddress_list']='%s'; exec_xml('spamfilter', 'procSpamfilterAdminInsertDeniedIP', params, completeCallModuleAction)", $oComment->getIpAddress()); + $url = sprintf("var params = new Array(); params['ipaddress_list']='%s'; exec_json('spamfilter.procSpamfilterAdminInsertDeniedIP', params)", $oComment->getIpAddress()); $oCommentController->addCommentPopupMenu($url, 'cmd_add_ip_to_spamfilter', '', 'javascript'); } } diff --git a/modules/document/document.model.php b/modules/document/document.model.php index c12daa936..6a3137c03 100644 --- a/modules/document/document.model.php +++ b/modules/document/document.model.php @@ -600,7 +600,7 @@ class DocumentModel extends Document $url = getUrl('','module','admin','act','dispDocumentAdminList','search_target','ipaddress','search_keyword',$oDocument->getIpAddress()); $oDocumentController->addDocumentPopupMenu($url,'cmd_search_by_ipaddress',$icon_path,'TraceByIpaddress'); - $url = sprintf("var params = new Array(); params['ipaddress_list']='%s'; exec_xml('spamfilter', 'procSpamfilterAdminInsertDeniedIP', params, completeCallModuleAction)", $oDocument->getIpAddress()); + $url = sprintf("var params = new Array(); params['ipaddress_list']='%s'; exec_json('spamfilter.procSpamfilterAdminInsertDeniedIP', params)", $oDocument->getIpAddress()); $oDocumentController->addDocumentPopupMenu($url,'cmd_add_ip_to_spamfilter','','javascript'); } } From ee5418d9d5b5ac5f47dde1d94f422c1e85a268a0 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 8 Feb 2026 11:02:50 +0900 Subject: [PATCH 14/47] Clean up message module config JS --- .../admin/controllers/maintenance/Cleanup.php | 1 + modules/message/tpl/filter/insert_config.xml | 7 ----- modules/message/tpl/js/config.js | 30 +++++-------------- 3 files changed, 9 insertions(+), 29 deletions(-) delete mode 100644 modules/message/tpl/filter/insert_config.xml diff --git a/modules/admin/controllers/maintenance/Cleanup.php b/modules/admin/controllers/maintenance/Cleanup.php index 1ea397361..a8906dbe4 100644 --- a/modules/admin/controllers/maintenance/Cleanup.php +++ b/modules/admin/controllers/maintenance/Cleanup.php @@ -441,6 +441,7 @@ class Cleanup extends Base 'modules/install/tpl/js/install_admin.js' => 'deleted:xe', 'modules/integration_search/skins/default/trackback.html' => 'deleted', 'modules/member/skins/default/filter/find_member_account_by_question.xml' => 'deleted:xe', + 'modules/message/tpl/filter/' => 'deleted:xe', 'modules/module/schemas/site_admin.xml' => 'deleted', 'modules/module/tpl/css/module_admin.less' => 'deleted', 'modules/page/page.wap.php' => 'deleted:xe', diff --git a/modules/message/tpl/filter/insert_config.xml b/modules/message/tpl/filter/insert_config.xml deleted file mode 100644 index 5307f8b3b..000000000 --- a/modules/message/tpl/filter/insert_config.xml +++ /dev/null @@ -1,7 +0,0 @@ - -
- - - - - diff --git a/modules/message/tpl/js/config.js b/modules/message/tpl/js/config.js index aa304acd2..97004198d 100644 --- a/modules/message/tpl/js/config.js +++ b/modules/message/tpl/js/config.js @@ -5,41 +5,27 @@ function doGetSkinColorset(skin, type) { 'skin' : skin, 'type': type, }; - var response_tags = [ 'error', 'message', 'tpl' ]; - function on_complete(ret) { - var $container = jQuery('#colorset'); - - if(type == 'M'){ - $container = jQuery('#mcolorset'); - } - + Rhymix.ajax('message.getMessageAdminColorset', params).then(function(ret) { + var $container = type == 'M' ? $('#mcolorset') : $('#colorset'); var old_h = $container.is(':visible') ? $container.outerHeight() : 0; - if(ret.tpl == ''){ + if (ret.tpl == '') { $container.hide(); - }else{ + } else { $container.show(); - var $colorset = jQuery('#message_colorset'); - - if(type == 'M'){ - $colorset = jQuery('#message_mcolorset'); - } - + var $colorset = (type == 'M') ? $('#message_mcolorset') : $('#message_colorset'); $colorset.html(ret.tpl); } var new_h = $container.is(':visible') ? $container.outerHeight() : 0; - try { fixAdminLayoutFooter(new_h - old_h) } catch (e) {}; - } - - exec_xml('message', 'getMessageAdminColorset', params, on_complete, response_tags); + }); } -jQuery(function($){ +$(function() { doGetSkinColorset($('#skin').val()); doGetSkinColorset($('#mskin').val(), 'M'); -}); \ No newline at end of file +}); From 59f95fe09936b4dc5f8c8b2048185c3ecb1cb9dc Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 8 Feb 2026 11:08:37 +0900 Subject: [PATCH 15/47] Remove outdated filter files in admin module --- modules/admin/controllers/maintenance/Cleanup.php | 1 + modules/admin/tpl/filter/install_ftp_info.xml | 12 ------------ modules/admin/tpl/filter/install_ftp_path.xml | 9 --------- modules/admin/tpl/filter/update_env_config.xml | 9 --------- modules/admin/tpl/filter/update_lang_select.xml | 7 ------- 5 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 modules/admin/tpl/filter/install_ftp_info.xml delete mode 100644 modules/admin/tpl/filter/install_ftp_path.xml delete mode 100644 modules/admin/tpl/filter/update_env_config.xml delete mode 100644 modules/admin/tpl/filter/update_lang_select.xml diff --git a/modules/admin/controllers/maintenance/Cleanup.php b/modules/admin/controllers/maintenance/Cleanup.php index a8906dbe4..b60de76d2 100644 --- a/modules/admin/controllers/maintenance/Cleanup.php +++ b/modules/admin/controllers/maintenance/Cleanup.php @@ -369,6 +369,7 @@ class Cleanup extends Base 'modules/admin/tpl/css/admin_en.css' => 'deleted:xe', 'modules/admin/tpl/css/admin_jp.css' => 'deleted:xe', 'modules/admin/tpl/css/admin_ko.css' => 'deleted:xe', + 'modules/admin/tpl/filter/' => 'deleted:xe', 'modules/autoinstall/ruleset/' => 'deleted:xe', 'modules/autoinstall/tpl/filter/uninstall_package.xml' => 'deleted:xe', 'modules/board/board.wap.php' => 'deleted:xe', diff --git a/modules/admin/tpl/filter/install_ftp_info.xml b/modules/admin/tpl/filter/install_ftp_info.xml deleted file mode 100644 index e33c6e2fd..000000000 --- a/modules/admin/tpl/filter/install_ftp_info.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/modules/admin/tpl/filter/install_ftp_path.xml b/modules/admin/tpl/filter/install_ftp_path.xml deleted file mode 100644 index 9c7775069..000000000 --- a/modules/admin/tpl/filter/install_ftp_path.xml +++ /dev/null @@ -1,9 +0,0 @@ - -
- - - - - - -
diff --git a/modules/admin/tpl/filter/update_env_config.xml b/modules/admin/tpl/filter/update_env_config.xml deleted file mode 100644 index a95045995..000000000 --- a/modules/admin/tpl/filter/update_env_config.xml +++ /dev/null @@ -1,9 +0,0 @@ - -
- - - - - - -
diff --git a/modules/admin/tpl/filter/update_lang_select.xml b/modules/admin/tpl/filter/update_lang_select.xml deleted file mode 100644 index 7a8453f94..000000000 --- a/modules/admin/tpl/filter/update_lang_select.xml +++ /dev/null @@ -1,7 +0,0 @@ - -
- - - - - From ad1617b17cafe390ba4a084da18a7b0438dee00e Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 9 Feb 2026 21:40:56 +0900 Subject: [PATCH 16/47] Show clickable list of layout instances in "installed layout" page --- modules/layout/layout.admin.view.php | 68 ++++++++----------- modules/layout/tpl/installed_layout_list.html | 40 +++++++++-- modules/layout/tpl/sub_tab.html | 4 +- 3 files changed, 65 insertions(+), 47 deletions(-) diff --git a/modules/layout/layout.admin.view.php b/modules/layout/layout.admin.view.php index 26f532086..3551d828c 100644 --- a/modules/layout/layout.admin.view.php +++ b/modules/layout/layout.admin.view.php @@ -22,50 +22,44 @@ class LayoutAdminView extends Layout */ function dispLayoutAdminInstalledList() { - $type = Context::get('type'); - $type = ($type != 'M') ? 'P' : 'M'; + $type = Context::get('type') === 'M' ? 'M' : 'P'; - // Set a layout list - $oLayoutModel = getModel('layout'); - $layout_list = $oLayoutModel->getDownloadedLayoutList($type, true); - if(!is_array($layout_list)) + // Get installed layout list + $layout_list = LayoutModel::getDownloadedLayoutList($type, true); + if (!is_array($layout_list)) { - $layout_list = array(); + $layout_list = []; } - if($type == 'P') + // Get instance list + $columns = ['layout_srl', 'layout', 'module_srl', 'title', 'regdate']; + $instances = LayoutModel::getLayoutInstanceList(0, $type, null, $columns); + $instance_list = []; + foreach ($instances as $instance) { - // get Theme layout - $oAdminModel = getAdminModel('admin'); - $themeList = $oAdminModel->getThemeList(); - $themeLayoutList = array(); - foreach($themeList as $themeInfo) - { - if(strpos($themeInfo->layout_info->name, '.') === false) continue; - $themeLayoutList[] = $oLayoutModel->getLayoutInfo($themeInfo->layout_info->name, null, 'P'); - } - $layout_list = array_merge($layout_list, $themeLayoutList); - $layout_list[] = $oLayoutModel->getLayoutInfo('faceoff', null, 'P'); + $instance_list[$instance->layout][] = $instance; } + Context::set('instance_list', $instance_list); - $pcLayoutCount = $oLayoutModel->getInstalledLayoutCount('P'); - $mobileLayoutCount = $oLayoutModel->getInstalledLayoutCount('M'); + // Get installed layout count by type + $pcLayoutCount = LayoutModel::getInstalledLayoutCount('P'); + $mobileLayoutCount = LayoutModel::getInstalledLayoutCount('M'); Context::set('pcLayoutCount', $pcLayoutCount); Context::set('mobileLayoutCount', $mobileLayoutCount); - $this->setTemplateFile('installed_layout_list'); + // Security? $security = new Security($layout_list); $layout_list = $security->encodeHTML('..', '..author..'); - - //Security $security = new Security(); - $security->encodeHTML('layout_list..layout','layout_list..title'); - - foreach($layout_list as $no => $layout_info) + $security->encodeHTML('layout_list..layout','layout_list..title','instance_list..'); + foreach ($layout_list as $no => $layout_info) { $layout_list[$no]->description = nl2br(trim($layout_info->description)); } Context::set('layout_list', $layout_list); + + // Set template file + $this->setTemplateFile('installed_layout_list'); } /** @@ -74,19 +68,17 @@ class LayoutAdminView extends Layout */ function dispLayoutAdminAllInstanceList() { - $type = Context::get('type'); + $type = Context::get('type') === 'M' ? 'M' : 'P'; - if(!in_array($type, array('P', 'M'))) $type = 'P'; - - $oLayoutModel = getModel('layout'); - - $pcLayoutCount = $oLayoutModel->getInstalledLayoutCount('P'); - $mobileLayoutCount = $oLayoutModel->getInstalledLayoutCount('M'); + // Get installed layout count + $pcLayoutCount = LayoutModel::getInstalledLayoutCount('P'); + $mobileLayoutCount = LayoutModel::getInstalledLayoutCount('M'); Context::set('pcLayoutCount', $pcLayoutCount); Context::set('mobileLayoutCount', $mobileLayoutCount); + // Get layout instance list $columnList = array('layout_srl', 'layout', 'module_srl', 'title', 'regdate'); - $_layout_list = $oLayoutModel->getLayoutInstanceList(0, $type, null, $columnList); + $_layout_list = LayoutModel::getLayoutInstanceList(0, $type, null, $columnList); $layout_list = array(); foreach($_layout_list as $item) @@ -94,7 +86,7 @@ class LayoutAdminView extends Layout if(!$layout_list[$item->layout]) { $layout_list[$item->layout] = array(); - $layout_info = $oLayoutModel->getLayoutInfo($item->layout, null, $type); + $layout_info = LayoutModel::getLayoutInfo($item->layout, null, $type); if ($layout_info) { $layout_list[$item->layout]['title'] = $layout_info->title; @@ -108,10 +100,10 @@ class LayoutAdminView extends Layout Context::set('layout_list', $layout_list); - $this->setTemplateFile('layout_all_instance_list'); - $security = new Security(); $security->encodeHTML('layout_list..'); + + $this->setTemplateFile('layout_all_instance_list'); } /** diff --git a/modules/layout/tpl/installed_layout_list.html b/modules/layout/tpl/installed_layout_list.html index 343e51ee7..693b556d9 100644 --- a/modules/layout/tpl/installed_layout_list.html +++ b/modules/layout/tpl/installed_layout_list.html @@ -17,10 +17,11 @@ - - + + + -

{$layout->title}

+

{$layout->title}

{$layout->description}

{$lang->msg_avail_easy_update} {$lang->msg_do_you_like_update} @@ -40,9 +41,10 @@ {$layout->path} - {$lang->cmd_delete} - - + + {$lang->cmd_delete} + +

{$layout->layout}

@@ -52,7 +54,31 @@ - - {$layout->path} - + + + + + + +   + + {$instance->title} + + + + + {$lang->cmd_edit} + + 편집 + + {$lang->cmd_copy} + + + + + + + diff --git a/modules/layout/tpl/sub_tab.html b/modules/layout/tpl/sub_tab.html index 60e696410..7247e2778 100644 --- a/modules/layout/tpl/sub_tab.html +++ b/modules/layout/tpl/sub_tab.html @@ -1,6 +1,6 @@

{$lang->instance_layout} | From 1199095e7f1a94f61b85e06b57fbebcff2614cac Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Tue, 10 Feb 2026 12:23:00 +0900 Subject: [PATCH 17/47] Version 2.1.30 --- common/constants.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constants.php b/common/constants.php index 0ecf769c8..133150cf8 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.29'); +define('RX_VERSION', '2.1.30'); /** * RX_MICROTIME is the startup time of the current script, in microseconds since the Unix epoch. From 5834a3c18ae72e6878e0ae7444811e195cdc272c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 11 Feb 2026 20:54:00 +0900 Subject: [PATCH 18/47] Fix fatal error in some environments when relative URL is passed to encodeIdna() or decodeIdna() #2675 --- common/framework/URL.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/common/framework/URL.php b/common/framework/URL.php index 6752eb99d..23e7f8f07 100644 --- a/common/framework/URL.php +++ b/common/framework/URL.php @@ -207,9 +207,13 @@ class URL */ public static function encodeIdna(string $url): string { - if (preg_match('@[:/#]@', $url)) + if (preg_match('@[:/#]@', $url) && !str_starts_with($url, '/')) { $domain = parse_url($url, \PHP_URL_HOST); + if (!$domain) + { + return $url; + } $position = strpos($url, $domain); if ($position === false) { @@ -243,9 +247,13 @@ class URL */ public static function decodeIdna(string $url): string { - if (preg_match('@[:/#]@', $url)) + if (preg_match('@[:/#]@', $url) && !str_starts_with($url, '/')) { $domain = parse_url($url, \PHP_URL_HOST); + if (!$domain) + { + return $url; + } $position = strpos($url, $domain); if ($position === false) { From c5d453a2df7b34fb4a706dbb2d5b001a31023571 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 11 Feb 2026 21:02:50 +0900 Subject: [PATCH 19/47] =?UTF-8?q?#2675=20=EB=B3=B4=EC=99=84=20=EB=B0=8F=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/framework/URL.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/framework/URL.php b/common/framework/URL.php index 23e7f8f07..14739f9a5 100644 --- a/common/framework/URL.php +++ b/common/framework/URL.php @@ -207,7 +207,7 @@ class URL */ public static function encodeIdna(string $url): string { - if (preg_match('@[:/#]@', $url) && !str_starts_with($url, '/')) + if (!preg_match('@^/(?!/)@', $url) && preg_match('@[:/#]@', $url)) { $domain = parse_url($url, \PHP_URL_HOST); if (!$domain) @@ -247,7 +247,7 @@ class URL */ public static function decodeIdna(string $url): string { - if (preg_match('@[:/#]@', $url) && !str_starts_with($url, '/')) + if (!preg_match('@^/(?!/)@', $url) && preg_match('@[:/#]@', $url)) { $domain = parse_url($url, \PHP_URL_HOST); if (!$domain) From 752b51c78f2443734d73da9d35b69b8775304189 Mon Sep 17 00:00:00 2001 From: Lastorder-DC <18280396+Lastorder-DC@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:28:44 +0900 Subject: [PATCH 20/47] spammer update --- modules/member/member.controller.php | 6 +++--- modules/ncenterlite/lang/ko.php | 4 ++-- modules/ncenterlite/ncenterlite.class.php | 6 ++++-- modules/ncenterlite/ncenterlite.model.php | 1 + modules/ncenterlite/queries/insertUserConfig.xml | 1 + modules/ncenterlite/queries/updateUserConfig.xml | 1 + modules/ncenterlite/schemas/ncenterlite_user_set.xml | 1 + 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/member/member.controller.php b/modules/member/member.controller.php index e946a1166..3d3ddf6de 100644 --- a/modules/member/member.controller.php +++ b/modules/member/member.controller.php @@ -3149,7 +3149,7 @@ class MemberController extends Member * * @param bool $deprecated_allow_update_other */ - function updateMember($args, $deprecated_allow_update_other = FALSE) + function updateMember($args, $deprecated_allow_update_other = FALSE, $manual_updated = FALSE) { // Call a trigger (before) $output = ModuleHandler::triggerCall('member.updateMember', 'before', $args); @@ -3175,7 +3175,7 @@ class MemberController extends Member { unset($args->is_admin); unset($args->limit_date); - unset($args->description); + if(!$manual_updated) unset($args->description); if (!$deprecated_allow_update_other) { unset($args->denied); @@ -3389,7 +3389,7 @@ class MemberController extends Member if(!$args->user_name) $args->user_name = $orgMemberInfo->user_name; if(!$args->user_id) $args->user_id = $orgMemberInfo->user_id; if(!$args->nick_name) $args->nick_name = $orgMemberInfo->nick_name; - if($logged_info->is_admin !== 'Y') + if($logged_info->is_admin !== 'Y' && !$manual_updated) { $args->description = $orgMemberInfo->description; } diff --git a/modules/ncenterlite/lang/ko.php b/modules/ncenterlite/lang/ko.php index 80b6a69b5..1d60e27be 100644 --- a/modules/ncenterlite/lang/ko.php +++ b/modules/ncenterlite/lang/ko.php @@ -14,7 +14,7 @@ $lang->ncenterlite_type_scrap = '스크랩 알림'; $lang->ncenterlite_type_message = '쪽지 알림'; $lang->ncenterlite_type_test = '테스트 알림'; $lang->ncenterlite_type_admin_content = '관리자 알림'; -$lang->ncenterlite_type_custom = '기타 알림'; +$lang->ncenterlite_type_custom = '글 작성 알림'; $lang->ncenterlite_comment_noti = $lang->ncenterlite_type_comment; $lang->ncenterlite_comment_comment_noti = $lang->ncenterlite_type_comment_comment; $lang->ncenterlite_mention_noti = $lang->ncenterlite_type_mention; @@ -45,7 +45,7 @@ $lang->ncenterlite_message_noti_about = '새 쪽지가 도착하면 알림을 $lang->ncenterlite_vote_noti_about = '내 글이나 댓글이 추천을 받으면 알림을 받습니다.'; $lang->ncenterlite_scrap_noti_about = '누군가 내 글을 스크랩하면 알림을 받습니다.'; $lang->ncenterlite_admin_content_noti_about = '관리자 알림을 받습니다.'; -$lang->ncenterlite_custom_noti_about = '기타 알림을 받습니다.'; +$lang->ncenterlite_custom_noti_about = '팔로우한 회원의 작성 글 알림을 받습니다.(현재 여까/핫산 한정)'; $lang->ncneterlite_block_individual = '개별 문서/댓글 알림 차단'; $lang->ncneterlite_block_individual_about = '개별적으로 문서/댓글을 차단할 수 있습니다.'; $lang->ncenterlite_activate = '사용'; diff --git a/modules/ncenterlite/ncenterlite.class.php b/modules/ncenterlite/ncenterlite.class.php index 6e874de84..bebce6c33 100644 --- a/modules/ncenterlite/ncenterlite.class.php +++ b/modules/ncenterlite/ncenterlite.class.php @@ -75,11 +75,12 @@ class Ncenterlite extends ModuleObject { return true; } - + /* if($oDB->isColumnExists('ncenterlite_user_set','custom_notify')) { return true; } + */ // Extra data column if (!$oDB->isColumnExists('ncenterlite_notify', 'data')) @@ -165,11 +166,12 @@ class Ncenterlite extends ModuleObject { $oDB->dropColumn('ncenterlite_user_set', 'admin_content_notify'); } - + /* if($oDB->isColumnExists('ncenterlite_user_set','custom_notify')) { $oDB->dropColumn('ncenterlite_user_set', 'custom_notify'); } + */ // Composite index to speed up getNotifyList if(!$oDB->isIndexExists('ncenterlite_notify', 'idx_member_srl_and_readed')) diff --git a/modules/ncenterlite/ncenterlite.model.php b/modules/ncenterlite/ncenterlite.model.php index f27bc5769..42b3dc93b 100644 --- a/modules/ncenterlite/ncenterlite.model.php +++ b/modules/ncenterlite/ncenterlite.model.php @@ -123,6 +123,7 @@ class NcenterliteModel extends Ncenterlite 'vote' => 0, 'scrap' => 0, 'message' => 0, + 'custom' => 0, ); } diff --git a/modules/ncenterlite/queries/insertUserConfig.xml b/modules/ncenterlite/queries/insertUserConfig.xml index e09cf6e03..e5e73920b 100644 --- a/modules/ncenterlite/queries/insertUserConfig.xml +++ b/modules/ncenterlite/queries/insertUserConfig.xml @@ -10,6 +10,7 @@ + diff --git a/modules/ncenterlite/queries/updateUserConfig.xml b/modules/ncenterlite/queries/updateUserConfig.xml index 41ac087df..8db6182ba 100644 --- a/modules/ncenterlite/queries/updateUserConfig.xml +++ b/modules/ncenterlite/queries/updateUserConfig.xml @@ -9,6 +9,7 @@ + diff --git a/modules/ncenterlite/schemas/ncenterlite_user_set.xml b/modules/ncenterlite/schemas/ncenterlite_user_set.xml index d5d1569a3..f178d14d9 100644 --- a/modules/ncenterlite/schemas/ncenterlite_user_set.xml +++ b/modules/ncenterlite/schemas/ncenterlite_user_set.xml @@ -6,5 +6,6 @@ + From f131a616eb990e2b070a8381c3106ae979d40989 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 16 Feb 2026 21:56:44 +0900 Subject: [PATCH 21/47] Fix RVE-2026-1 arbitrary file association by extra var --- common/lang/en.php | 1 + common/lang/ko.php | 1 + modules/document/document.controller.php | 35 ++++++++++++++---------- modules/document/document.model.php | 1 + modules/extravar/models/Value.php | 26 ++++++++++++++---- 5 files changed, 44 insertions(+), 20 deletions(-) diff --git a/common/lang/en.php b/common/lang/en.php index b63f049ac..885c2057a 100644 --- a/common/lang/en.php +++ b/common/lang/en.php @@ -359,6 +359,7 @@ $lang->filter['invalid_alpha_number'] = 'The format of %s is invalid. Please ent $lang->filter['invalid_mid'] = 'The format of %s is invalid. Module ID should be begun with a letter. Subsequent characters may be letters, digits or underscore characters.'; $lang->filter['invalid_number'] = 'The format of %s is invalid. Please enter numbers only.'; $lang->filter['invalid_float'] = 'The format of %s is invalid. Please enter numbers only.'; +$lang->filter['invalid_file'] = 'The value of %s is not a valid file upload.'; $lang->filter['invalid_extension'] = 'The format of %s is invalid. e.g. gif, jpg, png'; $lang->security_warning_embed = 'Due to security concern, administrators are not allowed to view embedded items.
To view them, please use another non-administrator ID.'; $lang->msg_pc_to_mobile = 'View mobile optimized version of this page'; diff --git a/common/lang/ko.php b/common/lang/ko.php index d12c63646..f744f30c3 100644 --- a/common/lang/ko.php +++ b/common/lang/ko.php @@ -359,6 +359,7 @@ $lang->filter['invalid_alpha_number'] = '%s의 형식이 잘못되었습니다. $lang->filter['invalid_mid'] = '%s의 형식이 잘못되었습니다. 첫 글자는 영문으로 시작해야 하며 \'영문+숫자+_\'로만 입력해야 합니다.'; $lang->filter['invalid_number'] = '%s의 형식이 잘못되었습니다. 숫자로만 입력해야 합니다.'; $lang->filter['invalid_float'] = '%s의 형식이 잘못되었습니다. 숫자로만 입력해야 합니다.'; +$lang->filter['invalid_file'] = '%s의 값은 올바르게 업로드된 파일이 아닙니다.'; $lang->filter['invalid_extension'] = '%s의 형식이 잘못되었습니다. gif, jpg, png 등 쉼표로 구분하여 입력해야 합니다.'; $lang->security_invalid_session = '바르지 않은 접근입니다. 인증을 위해 다시 로그인해야 합니다.'; $lang->security_warning_embed = '보안 문제로 관리자 아이디로는 embed를 볼 수 없습니다. 확인하려면 다른 아이디로 접속하세요'; diff --git a/modules/document/document.controller.php b/modules/document/document.controller.php index dc3e0506b..72608b49f 100644 --- a/modules/document/document.controller.php +++ b/modules/document/document.controller.php @@ -897,7 +897,7 @@ class DocumentController extends Document } // Handle extra vars that support file upload. - if ($extra_item->type === 'file' && is_array($value)) + if ($extra_item->type === 'file' && $value) { $ev_output = $extra_item->uploadFile($value, $obj->document_srl, 'doc'); if (!$ev_output->toBool()) @@ -1295,16 +1295,20 @@ class DocumentController extends Document if ($extra_item->type === 'file') { // New upload - if (is_array($value) && isset($value['name'])) + if (is_array($value) && isset($value['tmp_name'])) { // Delete old file if (isset($old_extra_vars[$idx]->value)) { - $fc_output = FileController::getInstance()->deleteFile($old_extra_vars[$idx]->value); - if (!$fc_output->toBool()) + $old_file = FileModel::getFile($old_extra_vars[$idx]->value); + if ($old_file && $old_file->upload_target_srl == $obj->document_srl) { - $oDB->rollback(); - return $fc_output; + $fc_output = FileController::getInstance()->deleteFile($old_file->file_srl); + if (!$fc_output->toBool()) + { + $oDB->rollback(); + return $fc_output; + } } } // Insert new file @@ -1329,21 +1333,22 @@ class DocumentController extends Document return $ev_output; } // Delete old file - $fc_output = FileController::getInstance()->deleteFile($old_extra_vars[$idx]->value); - if (!$fc_output->toBool()) + $old_file = FileModel::getFile($old_extra_vars[$idx]->value); + if ($old_file && $old_file->upload_target_srl == $obj->document_srl) { - $oDB->rollback(); - return $fc_output; + $fc_output = FileController::getInstance()->deleteFile($old_file->file_srl); + if (!$fc_output->toBool()) + { + $oDB->rollback(); + return $fc_output; + } } } } // Leave current file unchanged - elseif (!$value) + elseif (isset($old_extra_vars[$idx]->value)) { - if (isset($old_extra_vars[$idx]->value)) - { - $value = $old_extra_vars[$idx]->value; - } + $value = $old_extra_vars[$idx]->value; } } } diff --git a/modules/document/document.model.php b/modules/document/document.model.php index 6a3137c03..e95bcb32d 100644 --- a/modules/document/document.model.php +++ b/modules/document/document.model.php @@ -111,6 +111,7 @@ class DocumentModel extends Document foreach($GLOBALS['XE_EXTRA_KEYS'][$module_srl] as $idx => $key) { $document_extra_vars[$idx] = clone($key); + $document_extra_vars[$idx]->parent_srl = $document_srl; // set variable value in user language if(isset($document_extra_values[$idx][$user_lang_code])) diff --git a/modules/extravar/models/Value.php b/modules/extravar/models/Value.php index 5bf9d2dd3..4e3a21bc8 100644 --- a/modules/extravar/models/Value.php +++ b/modules/extravar/models/Value.php @@ -25,6 +25,7 @@ class Value public $input_id = ''; public $input_name = ''; public $parent_type = 'document'; + public $parent_srl = null; public $type = 'text'; public $value = null; public $name = ''; @@ -159,7 +160,7 @@ class Value */ public function getValueHTML(): string { - return self::_getTypeValueHTML($this->type, $this->value); + return self::_getTypeValueHTML($this->type, $this->value, $this->parent_type, $this->parent_srl); } /** @@ -280,7 +281,7 @@ class Value $values = [$value]; } - // Check if a required value is empty. + // Check that a required value is not empty. if ($this->is_required === 'Y') { if ($this->type === 'file' && !$value && $old_value) @@ -298,7 +299,7 @@ class Value } } - // Check if a strict value is not one of the specified options. + // Check that a strict value equals one of the specified options. if ($this->is_strict === 'Y' && $value) { if ($this->canHaveOptions()) @@ -321,6 +322,15 @@ class Value } } + // Check that a file value is actually an uploaded file. + if ($this->type === 'file' && $value) + { + if (!isset($value['tmp_name']) || !is_uploaded_file($value['tmp_name'])) + { + return new BaseObject(-1, sprintf(lang('common.filter.invalid_file'), Context::replaceUserLang($this->name))); + } + } + return new BaseObject; } @@ -442,9 +452,11 @@ class Value * * @param string $type * @param string|array $value + * @param string $parent_type + * @param ?int $parent_srl * @return string */ - protected static function _getTypeValueHTML(string $type, $value): string + protected static function _getTypeValueHTML(string $type, $value, string $parent_type, ?int $parent_srl = null): string { // Return if the value is empty. $value = self::_getTypeValue($type, $value); @@ -511,10 +523,14 @@ class Value if ($value) { $file = FileModel::getFile($value); - if ($file) + if ($file && $file->upload_target_srl == $parent_srl) { return sprintf('%s (%s)', \RX_BASEURL . ltrim($file->download_url, './'), $file->source_filename, FileHandler::filesize($file->file_size)); } + elseif ($file) + { + return sprintf('%s (%s)', $file->source_filename, FileHandler::filesize($file->file_size)); + } else { return ''; From 187bffe9d281f14dbd20ccaab40de2602b912543 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 16 Feb 2026 21:59:14 +0900 Subject: [PATCH 22/47] Fix unit test for Validator --- tests/unit/classes/validator/condition.en.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/classes/validator/condition.en.js b/tests/unit/classes/validator/condition.en.js index 5801af061..d0e51a57a 100644 --- a/tests/unit/classes/validator/condition.en.js +++ b/tests/unit/classes/validator/condition.en.js @@ -18,5 +18,6 @@ v.cast('ADD_MESSAGE',['invalid_alpha_number','%s의 형식이 잘못되었습니 v.cast('ADD_MESSAGE',['invalid_mid','%s의 형식이 잘못되었습니다. 첫 글자는 영문으로 시작해야 하며 \'영문+숫자+_\'로만 입력해야 합니다.']); v.cast('ADD_MESSAGE',['invalid_number','%s의 형식이 잘못되었습니다. 숫자로만 입력해야 합니다.']); v.cast('ADD_MESSAGE',['invalid_float','%s의 형식이 잘못되었습니다. 숫자로만 입력해야 합니다.']); +v.cast('ADD_MESSAGE',['invalid_file','%s의 값은 올바르게 업로드된 파일이 아닙니다.']); v.cast('ADD_MESSAGE',['invalid_extension','%s의 형식이 잘못되었습니다. gif, jpg, png 등 쉼표로 구분하여 입력해야 합니다.']); })(jQuery); From a18b45f0f81a0adf230268a523a80eed0ba246ab Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 20 Feb 2026 21:40:37 +0900 Subject: [PATCH 23/47] Strip namespace prefixes before checking dangerous tags in SVG --- common/framework/filters/FileContentFilter.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/framework/filters/FileContentFilter.php b/common/framework/filters/FileContentFilter.php index 272fe3477..6bfa5cf53 100644 --- a/common/framework/filters/FileContentFilter.php +++ b/common/framework/filters/FileContentFilter.php @@ -44,7 +44,7 @@ class FileContentFilter $skip_xml = preg_match('/^(hwpx)$/', $ext); // Check SVG files. - if (($ext === 'svg' || $is_xml) && !self::_checkSVG($fp, 0, $filesize)) + if (($ext === 'svg' || $is_xml) && !self::_checkSVG($fp, 0, $filesize, $ext)) { fclose($fp); return false; @@ -89,11 +89,12 @@ class FileContentFilter * @param resource $fp * @param int $from * @param int $to + * @param string $ext * @return bool */ - protected static function _checkSVG($fp, $from, $to) + protected static function _checkSVG($fp, $from, $to, $ext) { - if (self::_matchStream('/(?:<|<)(?:script|iframe|foreignObject|object|embed|handler)|javascript:|xlink:href\s*=\s*"(?!data:)/i', $fp, $from, $to)) + if (self::_matchStream('/(?:<|<|:)(?:script|iframe|foreignObject|object|embed|handler)|javascript:|(?:\s|:)href\s*=\s*"(?!data:)/i', $fp, $from, $to)) { return false; } From bf2df84d0f2209313a50348e01b90c89fb1e78d5 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 20 Feb 2026 21:55:29 +0900 Subject: [PATCH 24/47] Use enshrined\svgSanitize to clean SVG file content --- common/framework/Security.php | 6 ++++++ modules/file/file.controller.php | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/common/framework/Security.php b/common/framework/Security.php index d5e0802cd..6871af61e 100644 --- a/common/framework/Security.php +++ b/common/framework/Security.php @@ -38,6 +38,12 @@ class Security if (!utf8_check($input)) return false; return Filters\FilenameFilter::clean($input); + // Clean up SVG content to prevent various attacks. + case 'svg': + if (!utf8_check($input)) return false; + $sanitizer = new \enshrined\svgSanitize\Sanitizer(); + return strval($sanitizer->sanitize($input)); + // Unknown filters. default: throw new Exception('Unknown filter type for sanitize: ' . $type); diff --git a/modules/file/file.controller.php b/modules/file/file.controller.php index 1e482ce9e..c42aaf6dc 100644 --- a/modules/file/file.controller.php +++ b/modules/file/file.controller.php @@ -936,6 +936,14 @@ class FileController extends File } } + // Sanitize SVG + if(!$manual_insert && !$this->user->isAdmin() && ($file_info['type'] === 'image/svg+xml' || $file_info['extension'] === 'svg')) + { + $dirty_svg = Rhymix\Framework\Storage::read($file_info['tmp_name']); + $clean_svg = Rhymix\Framework\Security::sanitize($dirty_svg, 'svg'); + Rhymix\Framework\Storage::write($file_info['tmp_name'], $clean_svg); + } + // Adjust if(!$manual_insert) { From 91744ec87cafb77cd3afc9dcb94b64ffd4bbf171 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 20 Feb 2026 21:57:07 +0900 Subject: [PATCH 25/47] Always download SVG as attachment --- modules/file/file.controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/file/file.controller.php b/modules/file/file.controller.php index c42aaf6dc..3f24cae0c 100644 --- a/modules/file/file.controller.php +++ b/modules/file/file.controller.php @@ -551,7 +551,7 @@ class FileController extends File { $download_type = 'inline'; } - if (Context::get('force_download') === 'Y') + if ($mime_type === 'image/svg+xml' || Context::get('force_download') === 'Y') { $download_type = 'attachment'; } From e4c60b56d47df1554adc6d978a391e28627b8f84 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 20 Feb 2026 21:57:35 +0900 Subject: [PATCH 26/47] Add unit tests for Security::sanitize() supporting SVG --- tests/unit/framework/SecurityTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unit/framework/SecurityTest.php b/tests/unit/framework/SecurityTest.php index 4246316be..9fb35a2b0 100644 --- a/tests/unit/framework/SecurityTest.php +++ b/tests/unit/framework/SecurityTest.php @@ -15,6 +15,16 @@ class SecurityTest extends \Codeception\Test\Unit // Filename (more thorough tests in FilenameFilterTest) $this->assertEquals('foo(bar).xls', Rhymix\Framework\Security::sanitize('foo.xls', 'filename')); + + // SVG #1 + $source = 'Test'; + $target = '' . "\n\n \n Test\n \n\n"; + $this->assertEquals($target, Rhymix\Framework\Security::sanitize($source, 'svg')); + + // SVG #2 + $source = ''; + $target = '' . "\n\n \n\n"; + $this->assertEquals($target, Rhymix\Framework\Security::sanitize($source, 'svg')); } public function testEncryption() From 18401d2688b3b7db1433c4a745c4093b6026e48d Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 21 Feb 2026 21:41:35 +0900 Subject: [PATCH 27/47] Remove reference to old theme file #2677 --- modules/menu/menu.admin.view.php | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/modules/menu/menu.admin.view.php b/modules/menu/menu.admin.view.php index 0587d1411..12a555f17 100644 --- a/modules/menu/menu.admin.view.php +++ b/modules/menu/menu.admin.view.php @@ -51,23 +51,14 @@ class MenuAdminView extends Menu $resultModuleList = $oMenuAdminModel->getModuleListInSitemap($site_srl); Context::set('module_list', $resultModuleList); + // Get installed layout list $oLayoutModel = getModel('layout'); $layoutList = $oLayoutModel->getLayoutList(); Context::set('layout_list', $layoutList); - // choice theme file - $theme_file = RX_BASEDIR.'files/theme/theme_info.php'; - if(is_readable($theme_file)) - { - include($theme_file); - Context::set('current_layout', $theme_info->layout); - } - else - { - $oModuleModel = getModel('module'); - $default_mid = $oModuleModel->getDefaultMid(); - Context::set('current_layout', $default_mid->layout_srl); - } + // Get current layout information + $default_mid = ModuleModel::getDefaultMid(); + Context::set('current_layout', $default_mid->layout_srl); // get default group list $oMemberModel = getModel('member'); From d47dd2d82492d8d701a155e7f1220756f99f35e1 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 21 Feb 2026 21:45:39 +0900 Subject: [PATCH 28/47] Remove reference to old themes in layout module --- modules/layout/layout.model.php | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/modules/layout/layout.model.php b/modules/layout/layout.model.php index b12e80112..b161ff6f8 100644 --- a/modules/layout/layout.model.php +++ b/modules/layout/layout.model.php @@ -81,11 +81,7 @@ class LayoutModel extends Layout } $token = explode('|@|', $val->layout); - if(count($token) == 2) - { - $thumbnailPath = sprintf('./themes/%s/layouts/%s/thumbnail.png' , $token[0], $token[1]); - } - else if($layoutType == 'M') + if($layoutType == 'M') { $thumbnailPath = sprintf('./m.layouts/%s/thumbnail.png' , $val->layout); } @@ -227,26 +223,13 @@ class LayoutModel extends Layout */ public static function isExistsLayoutFile($layout, $layoutType) { - //TODO If remove a support themes, remove this codes also. if($layoutType == 'P') { - $pathPrefix = RX_BASEDIR . 'layouts/'; - $themePathFormat = RX_BASEDIR . 'themes/%s/layouts/%s'; + $path = RX_BASEDIR . 'layouts/' . $layout; } else { - $pathPrefix = RX_BASEDIR . 'm.layouts/'; - $themePathFormat = RX_BASEDIR . 'themes/%s/m.layouts/%s'; - } - - if(strpos($layout, '|@|') !== FALSE) - { - list($themeName, $layoutName) = explode('|@|', $layout); - $path = sprintf($themePathFormat, $themeName, $layoutName); - } - else - { - $path = $pathPrefix . $layout; + $path = RX_BASEDIR . 'm.layouts/' . $layout; } if (file_exists($path . '/layout.html') && is_readable($path . '/layout.html')) @@ -333,12 +316,7 @@ class LayoutModel extends Layout */ public function getLayoutPath($layout_name = "", $layout_type = "P") { - $layout_parse = explode('|@|', $layout_name ?? ''); - if(count($layout_parse) > 1) - { - $class_path = './themes/'.$layout_parse[0].'/layouts/'.$layout_parse[1].'/'; - } - else if($layout_name == 'faceoff') + if($layout_name == 'faceoff') { $class_path = './modules/layout/faceoff/'; } From a53e293a5a112047dc069ef8290668bfa86d23cb Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 21 Feb 2026 21:51:41 +0900 Subject: [PATCH 29/47] Support searching admin memo in member list #2676 --- modules/member/lang/en.php | 1 + modules/member/lang/ko.php | 1 + modules/member/member.admin.model.php | 3 +++ modules/member/queries/getMemberList.xml | 1 + modules/member/queries/getMemberListWithinGroup.xml | 1 + 5 files changed, 7 insertions(+) diff --git a/modules/member/lang/en.php b/modules/member/lang/en.php index 5f701c334..24eceb38c 100644 --- a/modules/member/lang/en.php +++ b/modules/member/lang/en.php @@ -114,6 +114,7 @@ $lang->search_target_list['last_login_less'] = 'Last Login Date (less)'; $lang->search_target_list['last_login_ipaddress'] = 'Last Login IP address'; $lang->search_target_list['birthday'] = 'Birthday'; $lang->search_target_list['extra_vars'] = 'User Defined'; +$lang->search_target_list['description'] = 'Admin Memo'; $lang->cmd_modify_new_auth_email_address = 'New email address'; $lang->cmd_set_design_info = 'Desgin'; $lang->cmd_login = 'Login'; diff --git a/modules/member/lang/ko.php b/modules/member/lang/ko.php index ca5014207..dee729b3b 100644 --- a/modules/member/lang/ko.php +++ b/modules/member/lang/ko.php @@ -116,6 +116,7 @@ $lang->search_target_list['last_login_less'] = '최근 로그인 일시(이하)' $lang->search_target_list['last_login_ipaddress'] = '최근 로그인 IP 주소'; $lang->search_target_list['birthday'] = '생일'; $lang->search_target_list['extra_vars'] = '사용자 정의'; +$lang->search_target_list['description'] = '관리자 메모'; $lang->cmd_modify_new_auth_email_address = '신규 메일 주소로 변경 후 인증 메일 발송'; $lang->cmd_set_design_info = '디자인'; $lang->cmd_login = '로그인'; diff --git a/modules/member/member.admin.model.php b/modules/member/member.admin.model.php index 18f434a03..3f4a6f192 100644 --- a/modules/member/member.admin.model.php +++ b/modules/member/member.admin.model.php @@ -121,6 +121,9 @@ class MemberAdminModel extends Member case 'extra_vars' : $args->s_extra_vars = $search_keyword; break; + case 'description' : + $args->s_description = $search_keyword; + break; } } diff --git a/modules/member/queries/getMemberList.xml b/modules/member/queries/getMemberList.xml index f781b2b69..68fa93b75 100644 --- a/modules/member/queries/getMemberList.xml +++ b/modules/member/queries/getMemberList.xml @@ -19,6 +19,7 @@ + diff --git a/modules/member/queries/getMemberListWithinGroup.xml b/modules/member/queries/getMemberListWithinGroup.xml index 292ff0d7a..448f8b17a 100644 --- a/modules/member/queries/getMemberListWithinGroup.xml +++ b/modules/member/queries/getMemberListWithinGroup.xml @@ -22,6 +22,7 @@ + From 37b23341be394fd421e6d51a905260f00693e3b4 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 22 Feb 2026 20:18:05 +0900 Subject: [PATCH 30/47] Fix template path error in mobile document page #2679 --- modules/page/page.mobile.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/page/page.mobile.php b/modules/page/page.mobile.php index 967b13013..fb1c44b4d 100644 --- a/modules/page/page.mobile.php +++ b/modules/page/page.mobile.php @@ -19,16 +19,17 @@ class PageMobile extends PageView Context::set('document_srl', $document_srl); } Context::set('oDocument', $oDocument); + Context::set('page_content', $oDocument->getContent(false, false)); $oTemplate = Rhymix\Framework\Template::getInstance(); - $template_path = $this->getTemplatePath(); + $template_path = $this->getTemplatePath() ?: ($this->module_path . 'tpl'); if (preg_match('!/skins/!', $template_path)) { - $page_content = $oTemplate->compile($this->getTemplatePath(), 'content'); + $page_content = $oTemplate->compile($template_path, 'content'); } else { - $page_content = $oTemplate->compile($this->getTemplatePath(), 'mobile'); + $page_content = $oTemplate->compile($template_path, 'mobile'); } return $page_content; From 47e54bc564666a353f0ec3e4424f1b94ff85f934 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 23 Feb 2026 13:55:17 +0900 Subject: [PATCH 31/47] Fix typo in XML filter file #2679 --- modules/page/tpl/filter/insert_mpage_content.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/page/tpl/filter/insert_mpage_content.xml b/modules/page/tpl/filter/insert_mpage_content.xml index 06cbd7d2d..2fd5c1cbc 100644 --- a/modules/page/tpl/filter/insert_mpage_content.xml +++ b/modules/page/tpl/filter/insert_mpage_content.xml @@ -6,7 +6,7 @@ - + From cb947abb763dc9190fac6c1225b8c7e55116117d Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 23 Feb 2026 13:55:37 +0900 Subject: [PATCH 32/47] Remove unreasonable list_count default --- 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 0c61117a9..53066d5ca 100644 --- a/modules/page/page.admin.controller.php +++ b/modules/page/page.admin.controller.php @@ -175,7 +175,7 @@ class PageAdminController extends Page $oDocumentController = getController('document'); $obj = new stdClass(); $obj->module_srl = $module_srl; - $obj->list_count = 99999999; + $obj->list_count = 0; $output = $oDocumentModel->getDocumentList($obj); if(count($output->data)) { From 4c91040c359bb80cb4b74ce15abf56b4d9185e0c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Tue, 24 Feb 2026 17:05:59 +0900 Subject: [PATCH 33/47] Rename misleading label for list_order sort --- modules/board/board.admin.view.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/board/board.admin.view.php b/modules/board/board.admin.view.php index cb5ec0bd2..4469b68b0 100644 --- a/modules/board/board.admin.view.php +++ b/modules/board/board.admin.view.php @@ -58,7 +58,7 @@ class BoardAdminView extends Board { // install order (sorting) options foreach($this->order_target as $key) $order_target[$key] = lang($key); - $order_target['list_order'] = lang('document_srl'); + $order_target['list_order'] = lang('default_value'); $order_target['update_order'] = lang('last_update'); Context::set('order_target', $order_target); } From ed68509c98f8cd3ca377573280717b8cec9c117c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Tue, 24 Feb 2026 17:08:28 +0900 Subject: [PATCH 34/47] Add comment to DocumentItem::getBrowserTitle() --- modules/document/document.item.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/document/document.item.php b/modules/document/document.item.php index 0b5e5609e..4d60f1795 100644 --- a/modules/document/document.item.php +++ b/modules/document/document.item.php @@ -1650,6 +1650,10 @@ class DocumentItem extends BaseObject return ModuleModel::getModuleInfoByModuleSrl($this->get('module_srl'))->browser_title; } + /** + * Get the title of the module to which the document belongs. + * @return string + */ function getBrowserTitle() { return $this->getModuleName(); From f0f73c6ac8b24e4a566a32e79a5a5a8f1bc188de Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 26 Feb 2026 01:21:19 +0900 Subject: [PATCH 35/47] Update jQuery to 3.7.1 and recommend updating --- classes/display/HTMLDisplayHandler.php | 5 +- common/js/jquery-3.6.3.min.js | 2 - .../js/{jquery-3.6.3.js => jquery-3.7.1.js} | 1953 +++++++---------- common/js/jquery-3.7.1.min.js | 2 + modules/admin/lang/en.php | 2 +- modules/admin/lang/ko.php | 2 +- 6 files changed, 845 insertions(+), 1121 deletions(-) delete mode 100644 common/js/jquery-3.6.3.min.js rename common/js/{jquery-3.6.3.js => jquery-3.7.1.js} (87%) create mode 100644 common/js/jquery-3.7.1.min.js diff --git a/classes/display/HTMLDisplayHandler.php b/classes/display/HTMLDisplayHandler.php index c2f570ad5..31d9b6f0a 100644 --- a/classes/display/HTMLDisplayHandler.php +++ b/classes/display/HTMLDisplayHandler.php @@ -7,7 +7,7 @@ class HTMLDisplayHandler */ public const JQUERY_V2 = '2.2.4'; public const JQUERY_V2_MIGRATE = '1.4.1'; - public const JQUERY_V3 = '3.6.3'; + public const JQUERY_V3 = '3.7.1'; public const JQUERY_V3_MIGRATE = '3.4.0'; /** @@ -746,7 +746,8 @@ class HTMLDisplayHandler */ private function _loadCommonJSCSS() { - if (config('view.jquery_version') === 3) + $jquery_version = config('view.jquery_version') ?: 2; + if ($jquery_version == 3) { $jquery_version = self::JQUERY_V3; $jquery_migrate_version = self::JQUERY_V3_MIGRATE; diff --git a/common/js/jquery-3.6.3.min.js b/common/js/jquery-3.6.3.min.js deleted file mode 100644 index b5329e9ae..000000000 --- a/common/js/jquery-3.6.3.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.3 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),v={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},S=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||S).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.3",E=function(e,t){return new E.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,S)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=E)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{if(d.cssSupportsSelector&&!CSS.supports("selector(:is("+c+"))"))throw new Error;return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===E&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[E]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,S=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.cssSupportsSelector=ce(function(){return CSS.supports("selector(*)")&&C.querySelectorAll(":is(:jqfake)")&&!CSS.supports("selector(:is(*,:jqfake))")}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=E,!C.getElementsByName||!C.getElementsByName(E).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&S){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&S){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&S)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+E+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+E+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),d.cssSupportsSelector||y.push(":has"),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType&&e.documentElement||e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&S&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:S,!0)),N.test(r[1])&&E.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=S.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,D=E(S);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=S.createDocumentFragment().appendChild(S.createElement("div")),(fe=S.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?E.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),S.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;E.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||E.expando+"_"+Ct.guid++;return this[e]=!0,e}}),E.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||E.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?E(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=S.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),E.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=S.implementation.createHTMLDocument("")).createElement("base")).href=S.location.href,t.head.appendChild(r)):t=S),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&E(o).remove(),E.merge([],i.childNodes)));var r,i,o},E.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(E.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},E.expr.pseudos.animated=function(t){return E.grep(E.timers,function(e){return t===e.elem}).length},E.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=E.css(e,"position"),c=E(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=E.css(e,"top"),u=E.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,E.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){E.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===E.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===E.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=E(e).offset()).top+=E.css(e,"borderTopWidth",!0),i.left+=E.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-E.css(r,"marginTop",!0),left:t.left-i.left-E.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===E.css(e,"position"))e=e.offsetParent;return e||re})}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;E.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),E.each(["top","left"],function(e,n){E.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?E(e).position()[n]+"px":t})}),E.each({Height:"height",Width:"width"},function(a,s){E.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){E.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?E.css(e,t,i):E.style(e,t,n,i)},s,n?e:void 0,n)}})}),E.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){E.fn[t]=function(e){return this.on(t,e)}}),E.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){E.fn[n]=function(e,t){return 0 0 && ( length - 1 ) in obj; } -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.9 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2022-12-19 - */ -( function( window ) { + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var pop = arr.pop; + + +var sort = arr.sort; + + +var splice = arr.splice; + + +var whitespace = "[\\x20\\t\\r\\n\\f]"; + + +var rtrimCSS = new RegExp( + "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", + "g" +); + + + + +// Note: an element does not contain itself +jQuery.contains = function( a, b ) { + var bup = b && b.parentNode; + + return a === bup || !!( bup && bup.nodeType === 1 && ( + + // Support: IE 9 - 11+ + // IE doesn't have `contains` on SVG. + a.contains ? + a.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); +}; + + + + +// CSS string/identifier serialization +// https://drafts.csswg.org/cssom/#common-serializing-idioms +var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; + +function fcssescape( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; +} + +jQuery.escapeSelector = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + + + + +var preferredDoc = document, + pushNative = push; + +( function() { + var i, - support, Expr, - getText, - isXML, - tokenize, - compile, - select, outermostContext, sortInput, hasDuplicate, + push = pushNative, // Local document vars - setDocument, document, - docElem, + documentElement, documentIsHTML, rbuggyQSA, - rbuggyMatches, matches, - contains, // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, + expando = jQuery.expando, dirruns = 0, done = 0, classCache = createCache(), @@ -570,47 +664,22 @@ var i, return 0; }, - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + + "loop|multiple|open|readonly|required|scoped", // Regular expressions - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", @@ -629,101 +698,88 @@ var i, // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), + rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + + whitespace + "*" ), rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + ID: new RegExp( "^#(" + identifier + ")" ), + CLASS: new RegExp( "^\\.(" + identifier + ")" ), + TAG: new RegExp( "^(" + identifier + "|[*])" ), + ATTR: new RegExp( "^" + attributes ), + PSEUDO: new RegExp( "^" + pseudos ), + CHILD: new RegExp( + "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + bool: new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + + needsContext: new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, - rhtml = /HTML$/i, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, - rnative = /^[^{]+\{\s*\[native \w/, - // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\([^\\r\\n\\f])", "g" ), funescape = function( escape, nonHex ) { var high = "0x" + escape.slice( 1 ) - 0x10000; - return nonHex ? + if ( nonHex ) { // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + return nonHex; } - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + return high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, - // Used for iframes - // See setDocument() + // Used for iframes; see `setDocument`. + // Support: IE 9 - 11+, Edge 12 - 18+ // Removing the function wrapper causes a "Permission Denied" - // error in IE + // error in IE/Edge. unloadHandler = function() { setDocument(); }, inDisabledFieldset = addCombinator( function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + return elem.disabled === true && nodeName( elem, "fieldset" ); }, { dir: "parentNode", next: "legend" } ); +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + // Optimize for push.apply( _, NodeList ) try { push.apply( @@ -731,32 +787,22 @@ try { preferredDoc.childNodes ); - // Support: Android<4.0 + // Support: Android <=4.0 // Detect silently failing push.apply // eslint-disable-next-line no-unused-expressions arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { + push = { + apply: function( target, els ) { pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; + }, + call: function( target ) { + pushNative.apply( target, slice.call( arguments, 1 ) ); } }; } -function Sizzle( selector, context, results, seed ) { +function find( selector, context, results, seed ) { var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, @@ -790,11 +836,10 @@ function Sizzle( selector, context, results, seed ) { if ( nodeType === 9 ) { if ( ( elem = context.getElementById( m ) ) ) { - // Support: IE, Opera, Webkit - // TODO: identify versions + // Support: IE 9 only // getElementById can match elements by name instead of ID if ( elem.id === m ) { - results.push( elem ); + push.call( results, elem ); return results; } } else { @@ -804,14 +849,13 @@ function Sizzle( selector, context, results, seed ) { // Element context } else { - // Support: IE, Opera, Webkit - // TODO: identify versions + // Support: IE 9 only // getElementById can match elements by name instead of ID if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && + find.contains( context, elem ) && elem.id === m ) { - results.push( elem ); + push.call( results, elem ); return results; } } @@ -822,22 +866,15 @@ function Sizzle( selector, context, results, seed ) { return results; // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - + } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + if ( !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { newSelector = selector; newContext = context; @@ -850,7 +887,7 @@ function Sizzle( selector, context, results, seed ) { // as such selectors are not recognized by querySelectorAll. // Thanks to Andrew Dupont for this technique. if ( nodeType === 1 && - ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) { // Expand context for sibling selectors newContext = rsibling.test( selector ) && testContext( context.parentNode ) || @@ -858,11 +895,15 @@ function Sizzle( selector, context, results, seed ) { // We can use :scope instead of the ID hack if the browser // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when + // strict-comparing two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( newContext != context || !support.scope ) { // Capture the context ID, setting it first if necessary if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); + nid = jQuery.escapeSelector( nid ); } else { context.setAttribute( "id", ( nid = expando ) ); } @@ -879,27 +920,6 @@ function Sizzle( selector, context, results, seed ) { } try { - - // `qSA` may not throw for unrecognized parts using forgiving parsing: - // https://drafts.csswg.org/selectors/#forgiving-selector - // like the `:has()` pseudo-class: - // https://drafts.csswg.org/selectors/#relational - // `CSS.supports` is still expected to return `false` then: - // https://drafts.csswg.org/css-conditional-4/#typedef-supports-selector-fn - // https://drafts.csswg.org/css-conditional-4/#dfn-support-selector - if ( support.cssSupportsSelector && - - // eslint-disable-next-line no-undef - !CSS.supports( "selector(:is(" + newSelector + "))" ) ) { - - // Support: IE 11+ - // Throw to get to the same code path as an error directly in qSA. - // Note: once we only support browser supporting - // `CSS.supports('selector(...)')`, we can most likely drop - // the `try-catch`. IE doesn't implement the API. - throw new Error(); - } - push.apply( results, newContext.querySelectorAll( newSelector ) ); @@ -916,7 +936,7 @@ function Sizzle( selector, context, results, seed ) { } // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); + return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); } /** @@ -930,7 +950,8 @@ function createCache() { function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + // Use (key + " ") to avoid collision with native prototype properties + // (see https://github.com/jquery/sizzle/issues/157) if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries @@ -942,7 +963,7 @@ function createCache() { } /** - * Mark a function for special use by Sizzle + * Mark a function for special use by jQuery selector module * @param {Function} fn The function to mark */ function markFunction( fn ) { @@ -973,56 +994,13 @@ function assert( fn ) { } } -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - /** * Returns a function to use in pseudos for input types * @param {String} type */ function createInputPseudo( type ) { return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; + return nodeName( elem, "input" ) && elem.type === type; }; } @@ -1032,8 +1010,8 @@ function createInputPseudo( type ) { */ function createButtonPseudo( type ) { return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; + return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && + elem.type === type; }; } @@ -1069,14 +1047,13 @@ function createDisabledPseudo( disabled ) { } } - // Support: IE 6 - 11 + // Support: IE 6 - 11+ // Use the isDisabled shortcut property to check for disabled fieldset ancestors return elem.isDisabled === disabled || // Where there is no isDisabled, check manually - /* jshint -W018 */ elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; + inDisabledFieldset( elem ) === disabled; } return elem.disabled === disabled; @@ -1116,7 +1093,7 @@ function createPositionalPseudo( fn ) { } /** - * Checks a node for validity as a Sizzle context + * Checks a node for validity as a jQuery selector context * @param {Element|Object=} context * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ @@ -1124,31 +1101,13 @@ function testContext( context ) { return context && typeof context.getElementsByTagName !== "undefined" && context; } -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - var namespace = elem && elem.namespaceURI, - docElem = elem && ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - /** * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document + * @param {Element|Object} [node] An element or document object to use to set the document * @returns {Object} Returns the current document */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, +function setDocument( node ) { + var subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected @@ -1162,112 +1121,90 @@ setDocument = Sizzle.setDocument = function( node ) { // Update global variables document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); + documentElement = document.documentElement; + documentIsHTML = !jQuery.isXMLDoc( document ); + + // Support: iOS 7 only, IE 9 - 11+ + // Older browsers didn't support unprefixed `matches`. + matches = documentElement.matches || + documentElement.webkitMatchesSelector || + documentElement.msMatchesSelector; // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && + // Accessing iframe documents after unload throws "permission denied" errors + // (see trac-13936). + // Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`, + // all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well. + if ( documentElement.msMatchesSelector && + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + preferredDoc != document && ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } + // Support: IE 9 - 11+, Edge 12 - 18+ + subWindow.addEventListener( "unload", unloadHandler ); } - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; - } ); - - // Support: Chrome 105+, Firefox 104+, Safari 15.4+ - // Make sure forgiving mode is not used in `CSS.supports( "selector(...)" )`. - // - // `:is()` uses a forgiving selector list as an argument and is widely - // implemented, so it's a good one to test against. - support.cssSupportsSelector = assert( function() { - /* eslint-disable no-undef */ - - return CSS.supports( "selector(*)" ) && - - // Support: Firefox 78-81 only - // In old Firefox, `:is()` didn't use forgiving parsing. In that case, - // fail this test as there's no selector to test against that. - // `CSS.supports` uses unforgiving parsing - document.querySelectorAll( ":is(:jqfake)" ) && - - // `*` is needed as Safari & newer Chrome implemented something in between - // for `:has()` - it throws in `qSA` if it only contains an unsupported - // argument but multiple ones, one of which is supported, are fine. - // We want to play safe in case `:is()` gets the same treatment. - !CSS.supports( "selector(:is(*,:jqfake))" ); - - /* eslint-enable */ - } ); - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); - } ); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; - } ); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 + // Support: IE <10 // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programmatically-set names, // so use a roundabout getElementsByName test support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; + documentElement.appendChild( el ).id = jQuery.expando; + return !document.getElementsByName || + !document.getElementsByName( jQuery.expando ).length; + } ); + + // Support: IE 9 only + // Check to see if it's possible to do matchesSelector + // on a disconnected node. + support.disconnectedMatch = assert( function( el ) { + return matches.call( el, "*" ); + } ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // IE/Edge don't support the :scope pseudo-class. + support.scope = assert( function() { + return document.querySelectorAll( ":scope" ); + } ); + + // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only + // Make sure the `:has()` argument is parsed unforgivingly. + // We include `*` in the test to detect buggy implementations that are + // _selectively_ forgiving (specifically when the list includes at least + // one valid selector). + // Note that we treat complete lack of support for `:has()` as if it were + // spec-compliant support, which is fine because use of `:has()` in such + // environments will fail in the qSA path and fall back to jQuery traversal + // anyway. + support.cssHas = assert( function() { + try { + document.querySelector( ":has(*,:jqfake)" ); + return false; + } catch ( e ) { + return true; + } } ); // ID filter and find if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { + Expr.filter.ID = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute( "id" ) === attrId; }; }; - Expr.find[ "ID" ] = function( id, context ) { + Expr.find.ID = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; } else { - Expr.filter[ "ID" ] = function( id ) { + Expr.filter.ID = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && @@ -1278,7 +1215,7 @@ setDocument = Sizzle.setDocument = function( node ) { // Support: IE 6 - 7 only // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { + Expr.find.ID = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); @@ -1308,40 +1245,18 @@ setDocument = Sizzle.setDocument = function( node ) { } // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); + Expr.find.TAG = function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; + // DocumentFragment nodes don't have gEBTN + } else { + return context.querySelectorAll( tag ); + } + }; // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + Expr.find.CLASS = function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } @@ -1352,195 +1267,94 @@ setDocument = Sizzle.setDocument = function( node ) { // QSA and matchesSelector support - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { + var input; - var input; + documentElement.appendChild( el ).innerHTML = + "" + + ""; - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; + // Support: iOS <=7 - 8 only + // Boolean attributes and "value" are not treated correctly in some XML documents + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } + // Support: iOS <=7 - 8 only + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } + // Support: iOS 8 only + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } + // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ + // In some of the document kinds, these selectors wouldn't work natively. + // This is probably OK but for backwards compatibility we want to maintain + // handling them through jQuery traversal in jQuery 3.x. + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } + // Support: IE 9 - 11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ + // In some of the document kinds, these selectors wouldn't work natively. + // This is probably OK but for backwards compatibility we want to maintain + // handling them through jQuery traversal in jQuery 3.x. + documentElement.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + } ); - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); + if ( !support.cssHas ) { - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } - - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { - - assert( function( el ) { - - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); - } - - if ( !support.cssSupportsSelector ) { - - // Support: Chrome 105+, Safari 15.4+ - // `:has()` uses a forgiving selector list as an argument so our regular - // `try-catch` mechanism fails to catch `:has()` with arguments not supported - // natively like `:has(:contains("Foo"))`. Where supported & spec-compliant, - // we now use `CSS.supports("selector(:is(SELECTOR_TO_BE_TESTED))")`, but - // outside that we mark `:has` as buggy. + // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+ + // Our regular `try-catch` mechanism fails to detect natively-unsupported + // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`) + // in browsers that parse the `:has()` argument as a forgiving selector list. + // https://drafts.csswg.org/selectors/#relational now requires the argument + // to be parsed unforgivingly, but browsers have not yet fully adjusted. rbuggyQSA.push( ":has" ); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - - // Support: IE <9 only - // IE doesn't have `contains` on `document` so we need to check for - // `documentElement` presence. - // We need to fall back to `a` when `documentElement` is missing - // as `ownerDocument` of elements within `