From b17c58f17f845ad520857200403c549afe905e09 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 14 Oct 2024 23:40:58 +0900 Subject: [PATCH 01/51] Implement admin scopes --- modules/module/lang/en.php | 4 ++ modules/module/lang/ko.php | 4 ++ modules/module/models/Permission.php | 64 ++++++++++++++++++++++ modules/module/module.admin.controller.php | 7 ++- modules/module/module.class.php | 12 ++++ modules/module/module.controller.php | 10 +++- modules/module/module.model.php | 52 +++++++++++++++--- modules/module/queries/getModuleAdmin.xml | 3 + modules/module/queries/insertAdminId.xml | 1 + modules/module/schemas/module_admins.xml | 1 + modules/module/tpl/module_grants.html | 17 +++++- 11 files changed, 163 insertions(+), 12 deletions(-) create mode 100644 modules/module/models/Permission.php diff --git a/modules/module/lang/en.php b/modules/module/lang/en.php index 6a81ee07f..d9172183d 100644 --- a/modules/module/lang/en.php +++ b/modules/module/lang/en.php @@ -97,6 +97,10 @@ $lang->about_mobile_page_count = 'You can set the number of page links to move p $lang->about_admin_id = 'You can grant someone permission to manage this module. Please enter the user ID or email address of the person you wish to add.'; $lang->about_grant_deatil = 'Registered users mean users who signed-up to the virtual sites (e.g., cafeXE).'; $lang->about_module = 'Rhymix consists of modules except the basic library. [Module Manage] module will show all installed modules and help you to manage them.'; +$lang->admin_scope = 'Scope of Admin Powers'; +$lang->admin_scopes['moderate:document'] = 'Manage documents'; +$lang->admin_scopes['moderate:comment'] = 'Manage comments'; +$lang->admin_scopes['config:*'] = 'Change settings'; $lang->extra_vars_is_strict = 'Specified values only'; $lang->extra_vars_options = 'Options'; $lang->about_extra_vars_is_strict = 'In single and multiple choice fields, only allow the values specified below. If you change the allowed values, it may affect previous posts.'; diff --git a/modules/module/lang/ko.php b/modules/module/lang/ko.php index 4b4ffe96a..212f0a39a 100644 --- a/modules/module/lang/ko.php +++ b/modules/module/lang/ko.php @@ -96,6 +96,10 @@ $lang->about_mobile_page_count = '목록 하단, 페이지를 이동하는 링 $lang->about_admin_id = '특정 회원에게 이 모듈의 관리 권한을 부여할 수 있습니다. 권한을 부여할 회원의 아이디 또는 이메일 주소를 입력해 주세요.'; $lang->about_grant_deatil = '가입한 사용자는 cafeXE 등 분양형 가상 사이트에 가입을 한 로그인 사용자를 의미합니다.'; $lang->about_module = 'Rhymix는 기본 라이브러리를 제외한 나머지는 모두 모듈로 구성되어 있습니다. 모듈 관리 모듈은 설치된 모든 모듈을 보여주고 관리를 돕습니다.'; +$lang->admin_scope = '관리자 권한 범위'; +$lang->admin_scopes['moderate:document'] = '문서 관리'; +$lang->admin_scopes['moderate:comment'] = '댓글 관리'; +$lang->admin_scopes['config:*'] = '모듈 설정 변경'; $lang->extra_vars_is_strict = '임의입력 금지'; $lang->extra_vars_options = '선택지'; $lang->about_extra_vars_is_strict = '단일/다중 선택에서 미리 주어진 선택지만 입력할 수 있도록 합니다. 선택지를 변경할 경우 기존 게시물에 영향을 줄 수 있습니다.'; diff --git a/modules/module/models/Permission.php b/modules/module/models/Permission.php new file mode 100644 index 000000000..5a3daa591 --- /dev/null +++ b/modules/module/models/Permission.php @@ -0,0 +1,64 @@ +{$scope}) && $scope !== 'scopes') + { + return boolval($this->{$scope}); + } + + if ($this->manager && $this->scopes && preg_match('/^(\w+):(.+)$/', $scope, $matches)) + { + if ($this->scopes === true) + { + return true; + } + if (is_array($this->scopes) && in_array($scope, $this->scopes)) + { + return true; + } + if (is_array($this->scopes) && in_array($matches[1] . ':*', $this->scopes)) + { + return true; + } + } + + return false; + } +} diff --git a/modules/module/module.admin.controller.php b/modules/module/module.admin.controller.php index cc7f6b577..8e4338cfb 100644 --- a/modules/module/module.admin.controller.php +++ b/modules/module/module.admin.controller.php @@ -292,6 +292,11 @@ class ModuleAdminController extends Module // Register Admin ID $oModuleController->deleteAdminId($module_srl); $admin_member = Context::get('admin_member'); + $scopes = Context::get('admin_scopes') ?: null; + if(is_string($scopes) && $scopes !== '') + { + $scopes = explode('|@|', $scopes); + } if($admin_member) { $admin_members = explode(',',$admin_member); @@ -299,7 +304,7 @@ class ModuleAdminController extends Module { $admin_id = trim($admin_id); if(!$admin_id) continue; - $oModuleController->insertAdminId($module_srl, $admin_id); + $oModuleController->insertAdminId($module_srl, $admin_id, $scopes); } } diff --git a/modules/module/module.class.php b/modules/module/module.class.php index e19fc2dbb..8845a2ddd 100644 --- a/modules/module/module.class.php +++ b/modules/module/module.class.php @@ -148,6 +148,12 @@ class Module extends ModuleObject { return true; } + + // check scope column on module_admins table + if (!$oDB->isColumnExists('module_admins', 'scopes')) + { + return true; + } } /** @@ -311,6 +317,12 @@ class Module extends ModuleObject return $output; } } + + // check scope column on module_admins table + if (!$oDB->isColumnExists('module_admins', 'scopes')) + { + $oDB->addColumn('module_admins', 'scopes', 'text', null, null, false, 'member_srl'); + } } /** diff --git a/modules/module/module.controller.php b/modules/module/module.controller.php index 1178cc8ce..93e76cfac 100644 --- a/modules/module/module.controller.php +++ b/modules/module/module.controller.php @@ -806,7 +806,7 @@ class ModuleController extends Module /** * @brief Specify the admin ID to a module */ - function insertAdminId($module_srl, $admin_id) + function insertAdminId($module_srl, $admin_id, $scopes = null) { if (strpos($admin_id, '@') !== false) { @@ -824,6 +824,14 @@ class ModuleController extends Module $args = new stdClass(); $args->module_srl = intval($module_srl); $args->member_srl = $member_info->member_srl; + if (is_array($scopes)) + { + $args->scopes = json_encode(array_values($scopes)); + } + else + { + $args->scopes = new Rhymix\Framework\Parsers\DBQuery\NullValue; + } $output = executeQuery('module.insertAdminId', $args); Rhymix\Framework\Cache::delete("site_and_module:module_admins:" . intval($module_srl)); diff --git a/modules/module/module.model.php b/modules/module/module.model.php index 473185703..4034a53e1 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -1853,7 +1853,9 @@ class ModuleModel extends Module } /** - * @brief Check if a member is a module administrator + * Check if a member is a module administrator + * + * @return array|bool */ public static function isModuleAdmin($member_info, $module_srl = null) { @@ -1882,14 +1884,22 @@ class ModuleModel extends Module $module_admins = array(); foreach ($output->data as $module_admin) { - $module_admins[$module_admin->member_srl] = true; + $module_admins[$module_admin->member_srl] = $module_admin->scopes ? json_decode($module_admin->scopes) : true; } if ($output->toBool()) { Rhymix\Framework\Cache::set("site_and_module:module_admins:$module_srl", $module_admins, 0, true); } } - return isset($module_admins[$member_info->member_srl]); + + if (isset($module_admins[$member_info->member_srl])) + { + return $module_admins[$member_info->member_srl]; + } + else + { + return false; + } } /** @@ -1900,8 +1910,14 @@ class ModuleModel extends Module $obj = new stdClass(); $obj->module_srl = $module_srl; $output = executeQueryArray('module.getAdminID', $obj); - if(!$output->toBool() || !$output->data) return; - + if (!$output->toBool() || !$output->data) + { + return; + } + foreach ($output->data as $row) + { + $row->scopes = !empty($row->scopes) ? json_decode($row->scopes) : null; + } return $output->data; } @@ -2129,7 +2145,12 @@ class ModuleModel extends Module } /** - * @brief Return privileges(granted) information by using module info, xml info and member info + * Get privileges(granted) information by using module info, xml info and member info + * + * @param object $module_info + * @param object $member_info + * @param ?object $xml_info + * @return Rhymix\Modules\Module\Models\Permission */ public static function getGrant($module_info, $member_info, $xml_info = null) { @@ -2148,8 +2169,6 @@ class ModuleModel extends Module } } - $grant = new stdClass; - // Get information of module.xml if(!$xml_info) { @@ -2172,6 +2191,7 @@ class ModuleModel extends Module $privilege_list = array_unique($privilege_list, SORT_STRING); // Grant first + $grant = new Rhymix\Modules\Module\Models\Permission; foreach($privilege_list as $val) { // If an administrator, grant all @@ -2180,7 +2200,7 @@ class ModuleModel extends Module $grant->{$val} = true; } // If a module manager, grant all (except 'root', 'is_admin') - else if($is_module_admin === true && $val !== 'root' && $val !== 'is_admin') + elseif ($is_module_admin && $val !== 'root' && $val !== 'is_admin') { $grant->{$val} = true; } @@ -2196,6 +2216,20 @@ class ModuleModel extends Module } } + // If module admin, add scopes + if ($member_info && $member_info->is_admin == 'Y') + { + $grant->scopes = true; + } + elseif ($is_module_admin) + { + $grant->scopes = $is_module_admin; + } + else + { + $grant->scopes = []; + } + // If access were not granted, check more if(!$grant->access) { diff --git a/modules/module/queries/getModuleAdmin.xml b/modules/module/queries/getModuleAdmin.xml index 55f163796..ef91b3f6c 100644 --- a/modules/module/queries/getModuleAdmin.xml +++ b/modules/module/queries/getModuleAdmin.xml @@ -2,6 +2,9 @@ + + + diff --git a/modules/module/queries/insertAdminId.xml b/modules/module/queries/insertAdminId.xml index 60f1ab088..ed510fcb0 100644 --- a/modules/module/queries/insertAdminId.xml +++ b/modules/module/queries/insertAdminId.xml @@ -5,6 +5,7 @@ + diff --git a/modules/module/schemas/module_admins.xml b/modules/module/schemas/module_admins.xml index 7de4e5545..b98994a83 100644 --- a/modules/module/schemas/module_admins.xml +++ b/modules/module/schemas/module_admins.xml @@ -1,5 +1,6 @@
+
diff --git a/modules/module/tpl/module_grants.html b/modules/module/tpl/module_grants.html index 630c44748..7845360e6 100644 --- a/modules/module/tpl/module_grants.html +++ b/modules/module/tpl/module_grants.html @@ -8,7 +8,7 @@
- +

{$lang->module_admin}

@@ -34,6 +34,21 @@

{$lang->about_admin_id}

+
+ +
+ {@ $default_scopes = array_keys($lang->admin_scopes->getArrayCopy())} + {@ $admin_scopes = $admin_member ? (array_first($admin_member)->scopes ?? $default_scopes) : $default_scopes} + + + +
+
From 008a15bcd5acf9aafa8fbd95c58d092e61548c85 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 14 Oct 2024 23:41:33 +0900 Subject: [PATCH 02/51] Use admin scopes to check manager privileges --- classes/module/ModuleObject.class.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index a203bf9aa..c235289bf 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -370,28 +370,36 @@ class ModuleObject extends BaseObject } } // If permission is 'manager', check 'is user have manager privilege(granted)' - else if(preg_match('/^(manager|([a-z0-9\_]+)-managers)$/', $permission, $type)) + else if(preg_match('/^(manager(?::(.+))?|([a-z0-9\_]+)-managers)$/', $permission, $type)) { - if($grant->manager) + // If permission is manager(:scope), check manager privilege and scope + if ($grant->manager) { - return true; + if (empty($type[2])) + { + return true; + } + elseif ($grant->can($type[2])) + { + return true; + } } // If permission is '*-managers', search modules to find manager privilege of the member - if(Context::get('is_logged') && isset($type[2])) + if(Context::get('is_logged') && isset($type[3])) { // Manager privilege of the member is found by search all modules, Pass - if($type[2] == 'all' && ModuleModel::findManagerPrivilege($member_info) !== false) + if($type[3] == 'all' && ModuleModel::findManagerPrivilege($member_info) !== false) { return true; } // Manager privilege of the member is found by search same module as this module, Pass - elseif($type[2] == 'same' && ModuleModel::findManagerPrivilege($member_info, $this->module) !== false) + elseif($type[3] == 'same' && ModuleModel::findManagerPrivilege($member_info, $this->module) !== false) { return true; } // Manager privilege of the member is found by search same module as the module, Pass - elseif(ModuleModel::findManagerPrivilege($member_info, $type[2]) !== false) + elseif(ModuleModel::findManagerPrivilege($member_info, $type[3]) !== false) { return true; } From 8c6beff8590b019bf5c6c18024f73925f92d5054 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 14 Oct 2024 23:42:06 +0900 Subject: [PATCH 03/51] Apply manager scopes to commonly used modules --- modules/board/conf/module.xml | 20 ++++++++++---------- modules/comment/conf/module.xml | 10 +++++----- modules/document/conf/module.xml | 24 ++++++++++++------------ modules/editor/conf/module.xml | 2 +- modules/file/conf/module.xml | 2 +- modules/point/conf/module.xml | 6 +++--- modules/rss/conf/module.xml | 2 +- 7 files changed, 33 insertions(+), 33 deletions(-) diff --git a/modules/board/conf/module.xml b/modules/board/conf/module.xml index 717fb91cb..da65fbfaa 100644 --- a/modules/board/conf/module.xml +++ b/modules/board/conf/module.xml @@ -116,21 +116,21 @@ - - - - - - - + + + + + + + - - + + - + diff --git a/modules/comment/conf/module.xml b/modules/comment/conf/module.xml index 83841a17a..00e6efbe0 100644 --- a/modules/comment/conf/module.xml +++ b/modules/comment/conf/module.xml @@ -13,8 +13,8 @@ - - + + @@ -23,9 +23,9 @@ - - - + + + diff --git a/modules/document/conf/module.xml b/modules/document/conf/module.xml index 7f7fe8faf..608e364c7 100644 --- a/modules/document/conf/module.xml +++ b/modules/document/conf/module.xml @@ -22,14 +22,14 @@ - - - - - - - - + + + + + + + + @@ -44,11 +44,11 @@ - + - - - + + + diff --git a/modules/editor/conf/module.xml b/modules/editor/conf/module.xml index 61169cbf1..aea50d220 100644 --- a/modules/editor/conf/module.xml +++ b/modules/editor/conf/module.xml @@ -12,7 +12,7 @@ - + diff --git a/modules/file/conf/module.xml b/modules/file/conf/module.xml index 3b0d1da9c..4c5c5f598 100644 --- a/modules/file/conf/module.xml +++ b/modules/file/conf/module.xml @@ -23,7 +23,7 @@ - + diff --git a/modules/point/conf/module.xml b/modules/point/conf/module.xml index 159469433..a455bbc0b 100644 --- a/modules/point/conf/module.xml +++ b/modules/point/conf/module.xml @@ -3,15 +3,15 @@ - + - + - + diff --git a/modules/rss/conf/module.xml b/modules/rss/conf/module.xml index 182f727c1..2bd09130c 100644 --- a/modules/rss/conf/module.xml +++ b/modules/rss/conf/module.xml @@ -7,7 +7,7 @@ - + From 8d8380a36685acd974b0983e9c4affc93207933e Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 14 Oct 2024 23:42:28 +0900 Subject: [PATCH 04/51] Apply manager scopes to Document and Comment isGranted() --- modules/comment/comment.item.php | 2 +- modules/document/document.item.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/comment/comment.item.php b/modules/comment/comment.item.php index 3be2d3244..096a2f887 100644 --- a/modules/comment/comment.item.php +++ b/modules/comment/comment.item.php @@ -126,7 +126,7 @@ class CommentItem extends BaseObject } $grant = ModuleModel::getGrant(ModuleModel::getModuleInfoByModuleSrl($this->get('module_srl')), $logged_info); - if ($grant->manager) + if ($grant->manager && $grant->can('moderate:comment')) { return $this->grant_cache = true; } diff --git a/modules/document/document.item.php b/modules/document/document.item.php index 4f83caf43..36fdf4c09 100644 --- a/modules/document/document.item.php +++ b/modules/document/document.item.php @@ -218,7 +218,7 @@ class DocumentItem extends BaseObject } $grant = ModuleModel::getGrant(ModuleModel::getModuleInfoByModuleSrl($this->get('module_srl')), $logged_info); - if ($grant->manager) + if ($grant->manager && $grant->can('moderate:document')) { return $this->grant_cache = true; } From a2e5434aec22787dc56cf30e3f886b8db8a17302 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 14 Oct 2024 23:42:53 +0900 Subject: [PATCH 05/51] Support manager scopes in Template v2 "can" directive --- common/framework/Template.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/common/framework/Template.php b/common/framework/Template.php index 90302bab9..0049e68d9 100644 --- a/common/framework/Template.php +++ b/common/framework/Template.php @@ -899,19 +899,23 @@ class Template protected function _v2_checkCapability(int $check_type, $capability): bool { $grant = \Context::get('grant'); - if ($check_type === 1) + if (!($grant instanceof \Rhymix\Modules\Module\Models\Permission)) { - return isset($grant->$capability) ? boolval($grant->$capability) : false; + return false; + } + elseif ($check_type === 1) + { + return $grant->can($capability); } elseif ($check_type === 2) { - return isset($grant->$capability) ? !boolval($grant->$capability) : true; + return !$grant->can($capability); } elseif (is_array($capability)) { foreach ($capability as $cap) { - if (isset($grant->$cap) && $grant->$cap) + if ($grant->can($cap)) { return true; } From 64b0d97fbb0342d52dbc158fc64a52e105dbddfa Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 17 Oct 2024 21:45:17 +0900 Subject: [PATCH 06/51] Add module.getModuleAdminScopes (after) event --- modules/module/module.admin.model.php | 15 +++++++++++++++ modules/module/tpl/module_grants.html | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/module/module.admin.model.php b/modules/module/module.admin.model.php index 72cd82bf1..d2e80c83f 100644 --- a/modules/module/module.admin.model.php +++ b/modules/module/module.admin.model.php @@ -197,6 +197,8 @@ class ModuleAdminModel extends Module // Extract admin ID set in the current module $admin_member = ModuleModel::getAdminId($module_srl) ?: []; Context::set('admin_member', $admin_member); + // Get defined scopes + Context::set('manager_scopes', $this->getModuleAdminScopes()); // Get a list of groups $group_list = MemberModel::getGroups(); Context::set('group_list', $group_list); @@ -286,6 +288,19 @@ class ModuleAdminModel extends Module $this->add('grantList', $grantList); } + /** + * Get defined scopes of module admin. + * + * @return array + */ + public function getModuleAdminScopes(): array + { + $obj = new \stdClass; + $obj->scopes = lang('module.admin_scopes')->getArrayCopy(); + ModuleHandler::triggerCall('module.getModuleAdminScopes', 'after', $obj); + return $obj->scopes; + } + /** * @brief Common:: skin setting page for the module */ diff --git a/modules/module/tpl/module_grants.html b/modules/module/tpl/module_grants.html index 7845360e6..3fd88cc3d 100644 --- a/modules/module/tpl/module_grants.html +++ b/modules/module/tpl/module_grants.html @@ -39,9 +39,9 @@ {$lang->admin_scope}
- {@ $default_scopes = array_keys($lang->admin_scopes->getArrayCopy())} + {@ $default_scopes = array_keys($manager_scopes)} {@ $admin_scopes = $admin_member ? (array_first($admin_member)->scopes ?? $default_scopes) : $default_scopes} - +
+
+ +
+ + +

{$lang->cmd_queue_webcron_display_errors_help}

+
+
+
From ccf8806bb4f0d0f3e4d136a35d3290466c39e21c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 27 Oct 2024 17:05:10 +0900 Subject: [PATCH 14/51] Add outgoing proxy setting --- common/defaults/config.php | 4 +++- modules/admin/controllers/systemconfig/Advanced.php | 9 +++++++++ modules/admin/lang/en.php | 3 +++ modules/admin/lang/ko.php | 3 +++ modules/admin/tpl/config_advanced.html | 7 +++++++ 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/common/defaults/config.php b/common/defaults/config.php index f28768d7c..8351d0056 100644 --- a/common/defaults/config.php +++ b/common/defaults/config.php @@ -151,5 +151,7 @@ return array( ], 'use_rewrite' => true, 'use_sso' => false, - 'other' => array(), + 'other' => [ + 'proxy' => null, + ], ); diff --git a/modules/admin/controllers/systemconfig/Advanced.php b/modules/admin/controllers/systemconfig/Advanced.php index 4135a0a0f..8622d1599 100644 --- a/modules/admin/controllers/systemconfig/Advanced.php +++ b/modules/admin/controllers/systemconfig/Advanced.php @@ -109,6 +109,7 @@ class Advanced extends Base Context::set('minify_scripts', Config::get('view.minify_scripts')); Context::set('concat_scripts', Config::get('view.concat_scripts')); Context::set('jquery_version', Config::get('view.jquery_version')); + Context::set('outgoing_proxy', Config::get('other.proxy')); $this->setTemplateFile('config_advanced'); } @@ -215,6 +216,13 @@ class Advanced extends Base Config::set('locale.auto_select_lang', $vars->auto_select_lang === 'Y'); Config::set('locale.default_timezone', $vars->default_timezone); + // Proxy + $proxy = trim($vars->outgoing_proxy ?? ''); + if ($proxy !== '' && !preg_match('!^(https?|socks)://.+!', $proxy)) + { + throw new Exception('msg_invalid_outgoing_proxy'); + } + // Other settings Config::set('url.rewrite', intval($vars->use_rewrite)); Config::set('use_rewrite', $vars->use_rewrite > 0); @@ -226,6 +234,7 @@ class Advanced extends Base Config::set('view.concat_scripts', $vars->concat_scripts ?: 'none'); Config::set('view.delay_compile', intval($vars->delay_template_compile)); Config::set('view.jquery_version', $vars->jquery_version == 3 ? 3 : 2); + Config::set('other.proxy', $proxy); // Save if (!Config::save()) diff --git a/modules/admin/lang/en.php b/modules/admin/lang/en.php index 44d44bf8b..78a478e17 100644 --- a/modules/admin/lang/en.php +++ b/modules/admin/lang/en.php @@ -203,6 +203,9 @@ $lang->cache_truncate_method_empty = 'Delete content of cache folder'; $lang->about_cache_truncate_method = 'It is faster and more reliable to delete the cache folder itself.
Choose the option to delete content only if the cache folder cannot be deleted, e.g. it is a mountpoint.'; $lang->cache_control_header = 'Cache-Control header'; $lang->about_cache_control_header = 'Select the Cache-Control header to apply to HTML pages that generally should not be cached.
Deselecting some of these options may help in certain circumstances, but at the cost of displaying outdated information.'; +$lang->outgoing_proxy = 'Proxy Outgoing Requests'; +$lang->about_outgoing_proxy = 'Use a proxy to hide the server\'s IP when making requests to other sites.
This setting does not apply to modules that implement their own HTTP clients.'; +$lang->msg_invalid_outgoing_proxy = 'Proxy URL must begin with http://, https:// or socks://'; $lang->msg_cache_handler_not_supported = 'Your server does not support the selected cache method, or Rhymix is unable to use the cache with the given settings.'; $lang->msg_invalid_default_url = 'The default URL is invalid.'; $lang->msg_default_url_ssl_inconsistent = 'In order to use SSL always, the default URL must also begin with https://'; diff --git a/modules/admin/lang/ko.php b/modules/admin/lang/ko.php index 06e3bfbd3..8ca26bb44 100644 --- a/modules/admin/lang/ko.php +++ b/modules/admin/lang/ko.php @@ -204,6 +204,9 @@ $lang->cache_truncate_method_empty = '캐시 내용만 삭제'; $lang->about_cache_truncate_method = '캐시 폴더를 삭제하는 방법이 더 빠르고 안정적입니다.
내용만 삭제하는 방법은 램디스크를 캐시 폴더로 사용하는 등 폴더 자체를 삭제해서는 안 되는 경우에만 선택하십시오.'; $lang->cache_control_header = '캐시 컨트롤 헤더'; $lang->about_cache_control_header = '브라우저 캐시를 적용하지 않을 일반 HTML 페이지에 적용할 Cache-Control 헤더 내용을 선택할 수 있습니다.
선택을 해제하면 뒤로가기 등 특정한 상황에서 성능이 개선될 수도 있지만, 오래된 정보가 노출되는 등 부작용이 발생할 수도 있습니다.'; +$lang->outgoing_proxy = '외부 요청 프록시'; +$lang->about_outgoing_proxy = '외부 요청시 프록시를 사용하여 서버 IP 노출을 방지합니다.
코어에서 제공하는 클래스와 함수를 사용하지 않고 외부 요청을 자체 구현한 서드파티 자료에는 적용되지 않습니다.'; +$lang->msg_invalid_outgoing_proxy = '프록시 주소는 http://, https:// 또는 socks://로 시작해야 합니다.'; $lang->msg_cache_handler_not_supported = '선택하신 캐시 방식을 서버에서 지원하지 않거나, 주어진 정보로 캐시에 접속할 수 없습니다.'; $lang->msg_invalid_default_url = '기본 URL이 올바르지 않습니다.'; $lang->msg_default_url_ssl_inconsistent = 'SSL을 항상 사용하실 경우 기본 URL도 https://로 시작해야 합니다.'; diff --git a/modules/admin/tpl/config_advanced.html b/modules/admin/tpl/config_advanced.html index 3378ea501..6373337fe 100644 --- a/modules/admin/tpl/config_advanced.html +++ b/modules/admin/tpl/config_advanced.html @@ -201,6 +201,13 @@

{$lang->about_cache_control_header}

+
+ +
+ +

{$lang->about_outgoing_proxy}

+
+
From a23308b728bb3c44e8037115fcd2f34510ca20f3 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 27 Oct 2024 17:07:18 +0900 Subject: [PATCH 15/51] Use proxy setting when making outgoing HTTP requests --- common/framework/HTTP.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/framework/HTTP.php b/common/framework/HTTP.php index 51e0b79ce..1948dee49 100644 --- a/common/framework/HTTP.php +++ b/common/framework/HTTP.php @@ -209,9 +209,10 @@ class HTTP ]; // Add proxy settings. - if (defined('__PROXY_SERVER__')) + $proxy = config('other.proxy') ?: (defined('__PROXY_SERVER__') ? constant('__PROXY_SERVER__') : ''); + if ($proxy !== '') { - $proxy = parse_url(constant('__PROXY_SERVER__')); + $proxy = parse_url($proxy); $proxy_scheme = preg_match('/^(https|socks)/', $proxy['scheme'] ?? '') ? ($proxy['scheme'] . '://') : 'http://'; $proxy_auth = (!empty($proxy['user']) && !empty($proxy['pass'])) ? ($proxy['user'] . ':' . $proxy['pass'] . '@') : ''; $settings['proxy'] = sprintf('%s%s%s:%s', $proxy_scheme, $proxy_auth, $proxy['host'], $proxy['port']); From 8049d5a8c92b9b4333a484a4f5c766365cd1ee60 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 27 Oct 2024 22:25:41 +0900 Subject: [PATCH 16/51] Use absolute path of the php executable in crontab example --- modules/admin/tpl/config_queue.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/admin/tpl/config_queue.html b/modules/admin/tpl/config_queue.html index b82d902a9..440d29367 100644 --- a/modules/admin/tpl/config_queue.html +++ b/modules/admin/tpl/config_queue.html @@ -118,7 +118,7 @@

{sprintf($lang->msg_queue_instructions['crontab1'], $user_info['name'] ?? 'PHP', $user_info['dir'] . 'logs')|noescape}

-
* * * * * php {\RX_BASEDIR}index.php common.cron >> {$user_info['dir']}logs{\DIRECTORY_SEPARATOR}queue.log 2>&1
+
* * * * * /usr/bin/php {\RX_BASEDIR}index.php common.cron >> {$user_info['dir']}logs{\DIRECTORY_SEPARATOR}queue.log 2>&1

{$lang->msg_queue_instructions['crontab2']|noescape}

@@ -137,7 +137,7 @@ Description=Rhymix Queue Service [Service] -ExecStart=php {\RX_BASEDIR}index.php common.cron +ExecStart=/usr/bin/php {\RX_BASEDIR}index.php common.cron User={$user_info['name']}

{$lang->msg_queue_instructions['systemd2']|noescape} From bf3d920a1d3121b35ef7e23eef47726461c0e3a0 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 27 Oct 2024 22:32:58 +0900 Subject: [PATCH 17/51] Improve crontab instructions for queue --- modules/admin/lang/en.php | 4 ++-- modules/admin/lang/ko.php | 4 ++-- modules/admin/tpl/config_queue.html | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/admin/lang/en.php b/modules/admin/lang/en.php index 78a478e17..1e54bc591 100644 --- a/modules/admin/lang/en.php +++ b/modules/admin/lang/en.php @@ -305,8 +305,8 @@ $lang->cmd_queue_config_keys['user'] = 'User'; $lang->cmd_queue_config_keys['pass'] = 'Password'; $lang->cmd_queue_config_keys['dbnum'] = 'DB Number'; $lang->msg_queue_instructions['same_as_php'] = '(same as PHP)'; -$lang->msg_queue_instructions['crontab1'] = 'Log into the server as the %s account and run crontab -e to paste the following content into your crontab. (DO NOT run it as root!)
The %s directory in the example should be replaced with a path where logs can be recorded.'; -$lang->msg_queue_instructions['crontab2'] = 'If you change the calling interval below, the crontab interval must be adjusted accordingly.'; +$lang->msg_queue_instructions['crontab1'] = 'Log into the server as the %s account and run crontab -e to paste the following content into your crontab. (DO NOT run it as root!)
If the account shown above cannot be logged into, such as apache or www-data, try running sudo crontab -e -u %s from a different account.'; +$lang->msg_queue_instructions['crontab2'] = 'The %s directory in the example should be replaced with a path where logs can be recorded.
If you change the calling interval below, the crontab interval must be adjusted accordingly.'; $lang->msg_queue_instructions['webcron'] = 'Configure an external cron service to make a GET request to the following URL every minute, or following the interval set below.
Check your logs to make sure that the cron service is reaching your website.'; $lang->msg_queue_instructions['systemd1'] = 'Put the following content in /etc/systemd/system/rhymix-queue.service'; $lang->msg_queue_instructions['systemd2'] = 'Put the following content in /etc/systemd/system/rhymix-queue.timer'; diff --git a/modules/admin/lang/ko.php b/modules/admin/lang/ko.php index 8ca26bb44..3cb78e0e1 100644 --- a/modules/admin/lang/ko.php +++ b/modules/admin/lang/ko.php @@ -301,8 +301,8 @@ $lang->cmd_queue_config_keys['user'] = '아이디'; $lang->cmd_queue_config_keys['pass'] = '암호'; $lang->cmd_queue_config_keys['dbnum'] = 'DB 번호'; $lang->msg_queue_instructions['same_as_php'] = 'PHP를 실행하는 계정과 동일한'; -$lang->msg_queue_instructions['crontab1'] = '%s 계정으로 서버에 로그인하여 crontab -e 명령을 실행한 후, 아래의 내용을 붙여넣으십시오. (root 권한으로 실행하지 마십시오.)
예제의 %s 디렉토리는 로그를 기록할 수 있는 경로로 변경하여 사용하십시오.'; -$lang->msg_queue_instructions['crontab2'] = '스크립트 호출 간격을 변경할 경우, 설정에 맞추어 crontab 실행 간격도 조절하여야 합니다.'; +$lang->msg_queue_instructions['crontab1'] = '%s 계정으로 서버에 로그인하여 crontab -e 명령을 실행한 후, 아래의 내용을 붙여넣으십시오. (root 권한으로 실행하지 마십시오!)
만약 apachewww-data처럼 로그인할 수 없는 계정이라면, 다른 계정에서 sudo crontab -e -u %s 명령을 실행해 볼 수 있습니다.'; +$lang->msg_queue_instructions['crontab2'] = '예제의 %s 디렉토리는 로그를 기록할 권한이 있는 경로로 변경하여 사용하십시오.
스크립트 호출 간격을 변경할 경우, 설정에 맞추어 crontab 실행 간격도 조절하여야 합니다.'; $lang->msg_queue_instructions['webcron'] = '아래의 URL을 1분 간격 또는 아래에서 설정한 호출 간격에 맞추어 GET으로 호출하도록 합니다.
웹크론 서비스가 방화벽이나 CDN 등에 의해 차단되지 않도록 주의하고, 정상적으로 호출되는지 서버 로그를 확인하십시오.'; $lang->msg_queue_instructions['systemd1'] = '/etc/systemd/system/rhymix-queue.service 파일에 아래와 같은 내용을 넣습니다.'; $lang->msg_queue_instructions['systemd2'] = '/etc/systemd/system/rhymix-queue.timer 파일에 아래와 같은 내용을 넣습니다.'; diff --git a/modules/admin/tpl/config_queue.html b/modules/admin/tpl/config_queue.html index 440d29367..4838a80f7 100644 --- a/modules/admin/tpl/config_queue.html +++ b/modules/admin/tpl/config_queue.html @@ -116,11 +116,11 @@ endif; }

- {sprintf($lang->msg_queue_instructions['crontab1'], $user_info['name'] ?? 'PHP', $user_info['dir'] . 'logs')|noescape} + {sprintf($lang->msg_queue_instructions['crontab1'], $user_info['name'] ?? 'PHP', $user_info['name'] ?? 'PHP')|noescape}

* * * * * /usr/bin/php {\RX_BASEDIR}index.php common.cron >> {$user_info['dir']}logs{\DIRECTORY_SEPARATOR}queue.log 2>&1

- {$lang->msg_queue_instructions['crontab2']|noescape} + {sprintf($lang->msg_queue_instructions['crontab2'], $user_info['dir'] . 'logs')|noescape}

From bf0093b56ac160c54d32f5c325aeadc4f76417e1 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 27 Oct 2024 22:50:02 +0900 Subject: [PATCH 18/51] Move most meta tags above the to below it #2419 --- classes/context/Context.class.php | 17 ++++++++--------- classes/display/HTMLDisplayHandler.php | 14 +++++++------- classes/module/ModuleHandler.class.php | 15 ++++++++------- common/tpl/common_layout.html | 9 ++++++++- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index 1d5dcc37c..e1edbf63c 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -2879,14 +2879,10 @@ class Context { return isset(self::$_instance->meta_tags[$name]) ? self::$_instance->meta_tags[$name]['content'] : null; } - - $ret = array(); - foreach(self::$_instance->meta_tags as $name => $content) + else { - $ret[] = array('name' => $name, 'is_http_equiv' => $content['is_http_equiv'], 'content' => escape($content['content'], false)); + return array_values(self::$_instance->meta_tags); } - - return $ret; } /** @@ -2894,14 +2890,17 @@ class Context * * @param string $name name of meta tag * @param string $content content of meta tag - * @param mixed $is_http_equiv value of http_equiv + * @param bool $is_http_equiv + * @param bool $is_before_title * @return void */ - public static function addMetaTag($name, $content, $is_http_equiv = false) + public static function addMetaTag($name, $content, $is_http_equiv = false, $is_before_title = true) { self::$_instance->meta_tags[$name] = array( + 'name' => $name, + 'content' => escape(self::replaceUserLang($content, true), false), 'is_http_equiv' => (bool)$is_http_equiv, - 'content' => self::replaceUserLang($content, true), + 'is_before_title' => (bool)$is_before_title, ); } diff --git a/classes/display/HTMLDisplayHandler.php b/classes/display/HTMLDisplayHandler.php index 52e5f697d..33dc2fd83 100644 --- a/classes/display/HTMLDisplayHandler.php +++ b/classes/display/HTMLDisplayHandler.php @@ -506,7 +506,7 @@ class HTMLDisplayHandler $description = Context::getMetaTag('description'); } Context::addOpenGraphData('og:description', $description); - Context::addMetaTag('description', $description); + Context::addMetaTag('description', $description, false, false); } // Add metadata about this page. @@ -641,7 +641,7 @@ class HTMLDisplayHandler { if ($tag !== '') { - Context::addOpenGraphData('og:article:tag', $tag, false); + Context::addOpenGraphData('og:article:tag', $tag); } } @@ -663,7 +663,7 @@ class HTMLDisplayHandler // Add author name for articles. if ($page_type === 'article' && $permitted && config('seo.og_use_nick_name')) { - Context::addMetaTag('author', $oDocument->getNickName()); + Context::addMetaTag('author', $oDocument->getNickName(), false, false); Context::addOpenGraphData('og:article:author', $oDocument->getNickName()); } @@ -683,21 +683,21 @@ class HTMLDisplayHandler function _addTwitterMetadata() { $card_type = $this->_image_type === 'document' ? 'summary_large_image' : 'summary'; - Context::addMetaTag('twitter:card', $card_type); + Context::addMetaTag('twitter:card', $card_type, false, false); foreach(Context::getOpenGraphData() as $val) { if ($val['property'] === 'og:title') { - Context::addMetaTag('twitter:title', $val['content']); + Context::addMetaTag('twitter:title', $val['content'], false, false); } if ($val['property'] === 'og:description') { - Context::addMetaTag('twitter:description', $val['content']); + Context::addMetaTag('twitter:description', $val['content'], false, false); } if ($val['property'] === 'og:image' && $this->_image_type === 'document') { - Context::addMetaTag('twitter:image', $val['content']); + Context::addMetaTag('twitter:image', $val['content'], false, false); } } } diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 7d518befa..42615a800 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -693,7 +693,8 @@ class ModuleHandler extends Handler } } - if ($kind === 'admin') { + if ($kind === 'admin') + { Context::addMetaTag('robots', 'noindex'); } @@ -865,29 +866,29 @@ class ModuleHandler extends Handler $module_config = ModuleModel::getModuleConfig('module'); if (!empty($module_info->meta_keywords)) { - Context::addMetaTag('keywords', $module_info->meta_keywords); + Context::addMetaTag('keywords', $module_info->meta_keywords, false, false); } elseif (!empty($site_module_info->settings->meta_keywords)) { - Context::addMetaTag('keywords', $site_module_info->settings->meta_keywords); + Context::addMetaTag('keywords', $site_module_info->settings->meta_keywords, false, false); } elseif (!empty($module_config->meta_keywords)) { - Context::addMetaTag('keywords', $module_config->meta_keywords); + Context::addMetaTag('keywords', $module_config->meta_keywords, false, false); } // Set meta description. if (!empty($module_info->meta_description)) { - Context::addMetaTag('description', $module_info->meta_description); + Context::addMetaTag('description', $module_info->meta_description, false, false); } elseif (!empty($site_module_info->settings->meta_description)) { - Context::addMetaTag('description', $site_module_info->settings->meta_description); + Context::addMetaTag('description', $site_module_info->settings->meta_description, false, false); } elseif (!empty($module_config->meta_description)) { - Context::addMetaTag('description', $module_config->meta_description); + Context::addMetaTag('description', $module_config->meta_description, false, false); } } diff --git a/common/tpl/common_layout.html b/common/tpl/common_layout.html index 04a7e326d..2e5c093dd 100644 --- a/common/tpl/common_layout.html +++ b/common/tpl/common_layout.html @@ -7,11 +7,13 @@ <meta charset="utf-8"> <meta name="generator" content="Rhymix"> <meta name="viewport" content="{{ config('mobile.viewport') ?? HTMLDisplayHandler::DEFAULT_VIEWPORT }}" /> +<meta name="csrf-token" content="{!! \Rhymix\Framework\Session::getGenericToken() !!}" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> @foreach (Context::getMetaTag() as $val) +@if ($val['is_before_title']) <meta http-equiv="{{ $val['name'] }}"|if="$val['is_http_equiv']" name="{{ $val['name'] }}"|if="!$val['is_http_equiv']" content="{{ $val['content'] }}" /> +@endif @endforeach -<meta name="csrf-token" content="{!! \Rhymix\Framework\Session::getGenericToken() !!}" /> <!-- TITLE --> <title>{{ Context::getBrowserTitle() }} @@ -55,6 +57,11 @@ @endforeach +@foreach (Context::getMetaTag() as $val) +@if (!$val['is_before_title']) + +@endif +@endforeach @foreach (Context::getOpenGraphData() as $og_metadata) @endforeach From 9c92ad1f0573a9c1cbfdd5aaff97e976bbbb25f4 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 27 Oct 2024 22:59:12 +0900 Subject: [PATCH 19/51] Remove X-UA-Compatible meta tag in all browsers except IE 11 --- classes/display/HTMLDisplayHandler.php | 6 ++++++ common/tpl/common_layout.html | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/classes/display/HTMLDisplayHandler.php b/classes/display/HTMLDisplayHandler.php index 33dc2fd83..b088f3d86 100644 --- a/classes/display/HTMLDisplayHandler.php +++ b/classes/display/HTMLDisplayHandler.php @@ -196,6 +196,12 @@ class HTMLDisplayHandler Context::set('favicon_url', $favicon_url); Context::set('mobicon_url', $mobicon_url); + // Only print the X-UA-Compatible meta tag if somebody is still using IE + if (preg_match('!Trident/7\.0!', $_SERVER['HTTP_USER_AGENT'] ?? '')) + { + Context::addMetaTag('X-UA-Compatible', 'IE=edge', true); + } + return $output; } diff --git a/common/tpl/common_layout.html b/common/tpl/common_layout.html index 2e5c093dd..25638bc49 100644 --- a/common/tpl/common_layout.html +++ b/common/tpl/common_layout.html @@ -8,7 +8,6 @@ - @foreach (Context::getMetaTag() as $val) @if ($val['is_before_title']) From e6bd94855e7f67b78fbd1035df4663b9f504aca0 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 27 Oct 2024 23:01:07 +0900 Subject: [PATCH 20/51] Remove double escape of meta tag content --- common/tpl/common_layout.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/tpl/common_layout.html b/common/tpl/common_layout.html index 25638bc49..b4d64176c 100644 --- a/common/tpl/common_layout.html +++ b/common/tpl/common_layout.html @@ -10,7 +10,7 @@ @foreach (Context::getMetaTag() as $val) @if ($val['is_before_title']) - + @endif @endforeach @@ -58,7 +58,7 @@ @foreach (Context::getMetaTag() as $val) @if (!$val['is_before_title']) - + @endif @endforeach @foreach (Context::getOpenGraphData() as $og_metadata) From 76bb57ad80c0c33d91a1542000e29484f112bbd1 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 27 Oct 2024 23:07:23 +0900 Subject: [PATCH 21/51] Move some meta tags back above the MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 원칙적으로 순서는 관계가 없으나, description이 title보다 먼저 나와야 한다고 주장하는 변태들이 있으므로 빌미를 주지 않기 위해 상단으로 다시 옮김. --- classes/display/HTMLDisplayHandler.php | 4 ++-- classes/module/ModuleHandler.class.php | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/classes/display/HTMLDisplayHandler.php b/classes/display/HTMLDisplayHandler.php index b088f3d86..4622d803f 100644 --- a/classes/display/HTMLDisplayHandler.php +++ b/classes/display/HTMLDisplayHandler.php @@ -512,7 +512,7 @@ class HTMLDisplayHandler $description = Context::getMetaTag('description'); } Context::addOpenGraphData('og:description', $description); - Context::addMetaTag('description', $description, false, false); + Context::addMetaTag('description', $description); } // Add metadata about this page. @@ -669,7 +669,7 @@ class HTMLDisplayHandler // Add author name for articles. if ($page_type === 'article' && $permitted && config('seo.og_use_nick_name')) { - Context::addMetaTag('author', $oDocument->getNickName(), false, false); + Context::addMetaTag('author', $oDocument->getNickName()); Context::addOpenGraphData('og:article:author', $oDocument->getNickName()); } diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 42615a800..95c535c3d 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -866,29 +866,29 @@ class ModuleHandler extends Handler $module_config = ModuleModel::getModuleConfig('module'); if (!empty($module_info->meta_keywords)) { - Context::addMetaTag('keywords', $module_info->meta_keywords, false, false); + Context::addMetaTag('keywords', $module_info->meta_keywords); } elseif (!empty($site_module_info->settings->meta_keywords)) { - Context::addMetaTag('keywords', $site_module_info->settings->meta_keywords, false, false); + Context::addMetaTag('keywords', $site_module_info->settings->meta_keywords); } elseif (!empty($module_config->meta_keywords)) { - Context::addMetaTag('keywords', $module_config->meta_keywords, false, false); + Context::addMetaTag('keywords', $module_config->meta_keywords); } // Set meta description. if (!empty($module_info->meta_description)) { - Context::addMetaTag('description', $module_info->meta_description, false, false); + Context::addMetaTag('description', $module_info->meta_description); } elseif (!empty($site_module_info->settings->meta_description)) { - Context::addMetaTag('description', $site_module_info->settings->meta_description, false, false); + Context::addMetaTag('description', $site_module_info->settings->meta_description); } elseif (!empty($module_config->meta_description)) { - Context::addMetaTag('description', $module_config->meta_description, false, false); + Context::addMetaTag('description', $module_config->meta_description); } } From 4f00389484fc2ebb97107a61eec083f5aeb5308a Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Tue, 5 Nov 2024 20:12:36 +0900 Subject: [PATCH 22/51] Exclude extravar module from server env page --- modules/admin/controllers/ServerEnv.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/admin/controllers/ServerEnv.php b/modules/admin/controllers/ServerEnv.php index 6305ef128..79a393aa2 100644 --- a/modules/admin/controllers/ServerEnv.php +++ b/modules/admin/controllers/ServerEnv.php @@ -20,7 +20,7 @@ class ServerEnv extends Base $info = array(); $skip = array( 'phpext' => array('core', 'session', 'spl', 'standard', 'date', 'ctype', 'tokenizer', 'apache2handler', 'filter', 'reflection'), - 'module' => array('addon', 'admin', 'adminlogging', 'advanced_mailer', 'autoinstall', 'board', 'comment', 'communication', 'counter', 'document', 'editor', 'file', 'importer', 'install', 'integration_search', 'krzip', 'layout', 'member', 'menu', 'message', 'module', 'ncenterlite', 'opage', 'page', 'point', 'poll', 'rss', 'session', 'spamfilter', 'tag', 'trackback', 'trash', 'widget'), + 'module' => array('addon', 'admin', 'adminlogging', 'advanced_mailer', 'autoinstall', 'board', 'comment', 'communication', 'counter', 'document', 'editor', 'extravar', 'file', 'importer', 'install', 'integration_search', 'krzip', 'layout', 'member', 'menu', 'message', 'module', 'ncenterlite', 'opage', 'page', 'point', 'poll', 'rss', 'session', 'spamfilter', 'tag', 'trackback', 'trash', 'widget'), 'addon' => array('adminlogging', 'autolink', 'counter', 'member_extra_info', 'point_level_icon', 'photoswipe', 'resize_image'), 'layout' => array('default', 'user_layout', 'xedition'), 'widget' => array('content', 'counter_status', 'language_select', 'login_info', 'mcontent', 'pollWidget'), From 70625171caf9f9efe262a338bb217cc3a9c53180 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 10 Nov 2024 14:46:30 +0900 Subject: [PATCH 23/51] Keep content text in separate variable before decoding --- common/framework/drivers/mail/mailgun.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/framework/drivers/mail/mailgun.php b/common/framework/drivers/mail/mailgun.php index cc1f339e9..118b6345e 100644 --- a/common/framework/drivers/mail/mailgun.php +++ b/common/framework/drivers/mail/mailgun.php @@ -116,12 +116,13 @@ class Mailgun extends Base implements \Rhymix\Framework\Drivers\MailInterface // Send the API request. $url = self::$_url . '/' . $this->_config['api_domain'] . '/messages.mime'; $request = \Rhymix\Framework\HTTP::post($url, $data, $headers, [], $settings); - $result = @json_decode($request->getBody()->getContents()); + $result_text = $request->getBody()->getContents(); + $result = @json_decode($result_text); // Parse the result. if (!$result) { - $message->errors[] = 'Mailgun: API error: ' . $request->getBody()->getContents(); + $message->errors[] = 'Mailgun: API error: ' . $result_text; return false; } elseif (!$result->id) From cfa6d761aa7624098571c7453170f7147098d856 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 10 Nov 2024 14:46:42 +0900 Subject: [PATCH 24/51] Improve detection of invalid FCM tokens --- common/framework/drivers/push/fcmv1.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/framework/drivers/push/fcmv1.php b/common/framework/drivers/push/fcmv1.php index 6cd947749..390161af0 100644 --- a/common/framework/drivers/push/fcmv1.php +++ b/common/framework/drivers/push/fcmv1.php @@ -151,7 +151,8 @@ class FCMv1 extends Base implements PushInterface foreach ($responses as $i => $response) { $status_code = $response->getStatusCode(); - $result = @json_decode($response->getBody()->getContents()); + $result_text = $response->getBody()->getContents(); + $result = @json_decode($result_text); if ($status_code === 200) { $output->success[$tokens[$i]] = $result->name ?? ''; @@ -164,6 +165,10 @@ class FCMv1 extends Base implements PushInterface { $output->invalid[$tokens[$i]] = $tokens[$i]; } + elseif (str_contains($error_message, 'Requested entity was not found')) + { + $output->invalid[$tokens[$i]] = $tokens[$i]; + } } else { @@ -196,7 +201,8 @@ class FCMv1 extends Base implements PushInterface foreach ($responses as $i => $response) { $status_code = $response->getStatusCode(); - $result = @json_decode($response->getBody()->getContents()); + $result_text = $response->getBody()->getContents(); + $result = @json_decode($result_text); if ($status_code === 200) { $output->success[$topics[$i]] = $result->name ?? ''; From 41d33837f28a408d6baa22ed85f5c2c34778af28 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sat, 16 Nov 2024 17:04:40 +0900 Subject: [PATCH 25/51] Fix #2431 undefined properties --- classes/display/HTMLDisplayHandler.php | 2 +- modules/board/board.view.php | 2 +- modules/board/skins/xedition/_comment.html | 6 ++++-- modules/board/skins/xedition/comment_form.html | 6 ++++-- widgets/content/content.class.php | 6 +++--- widgets/content/skins/default/image_title_content.html | 6 +++--- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/classes/display/HTMLDisplayHandler.php b/classes/display/HTMLDisplayHandler.php index 4622d803f..c851b09c7 100644 --- a/classes/display/HTMLDisplayHandler.php +++ b/classes/display/HTMLDisplayHandler.php @@ -138,7 +138,7 @@ class HTMLDisplayHandler { // handle separately if the layout is faceoff - if($layout_info && $layout_info->type == 'faceoff') + if($layout_info && isset($layout_info->type) && $layout_info->type == 'faceoff') { $oLayoutModel->doActivateFaceOff($layout_info); Context::set('layout_info', $layout_info); diff --git a/modules/board/board.view.php b/modules/board/board.view.php index 4480b9219..f665cba28 100644 --- a/modules/board/board.view.php +++ b/modules/board/board.view.php @@ -903,7 +903,7 @@ class BoardView extends Board } } - if ($this->module_info->protect_admin_content_update !== 'N') + if (($this->module_info->protect_admin_content_update ?? 'N') !== 'N') { $member_info = MemberModel::getMemberInfo($oDocument->get('member_srl')); if(isset($member_info->is_admin) && $member_info->is_admin == 'Y' && $this->user->is_admin != 'Y') diff --git a/modules/board/skins/xedition/_comment.html b/modules/board/skins/xedition/_comment.html index c87c379dc..f5f52e574 100644 --- a/modules/board/skins/xedition/_comment.html +++ b/modules/board/skins/xedition/_comment.html @@ -91,8 +91,10 @@ </span> <input cond="$is_logged" type="checkbox" name="notify_message" value="Y" id="notify_message" class="iCheck" /> <label cond="$is_logged" for="notify_message">{$lang->notify}</label> - <input cond="$module_info->secret=='Y'" type="checkbox" name="is_secret" value="Y" id="is_secret" class="iCheck" /> - <label cond="$module_info->secret=='Y'" for="is_secret">{$lang->secret}</label> + <!--@if(isset($module_info->secret) && $module_info->secret === 'Y')--> + <input type="checkbox" name="is_secret" value="Y" id="is_secret" class="iCheck" /> + <label for="is_secret">{$lang->secret}</label> + <!--@endif--> </div> <div class="write_captcha" cond="isset($captcha) && $captcha && $captcha->isTargetAction('comment')"> {$captcha} diff --git a/modules/board/skins/xedition/comment_form.html b/modules/board/skins/xedition/comment_form.html index e8b8de3b7..2d0310b08 100644 --- a/modules/board/skins/xedition/comment_form.html +++ b/modules/board/skins/xedition/comment_form.html @@ -29,8 +29,10 @@ </span> <input cond="$is_logged" type="checkbox" name="notify_message" value="Y" checked="checked"|cond="$oComment->get('notify_message')=='Y'" id="notify_message" class="iCheck" /> <label cond="$is_logged" for="notify_message">{$lang->notify}</label> - <input cond="$module_info->secret=='Y'" type="checkbox" name="is_secret" value="Y" id="is_secret" checked="checked"|cond="$oComment->get('is_secret')=='Y'" class="iCheck" /> - <label cond="$module_info->secret=='Y'" for="is_secret">{$lang->secret}</label> + <!--@if(isset($module_info->secret) && $module_info->secret === 'Y')--> + <input type="checkbox" name="is_secret" value="Y" id="is_secret" checked="checked"|cond="$oComment->get('is_secret')=='Y'" class="iCheck" /> + <label for="is_secret">{$lang->secret}</label> + <!--@endif--> </div> <div class="write_captcha" cond="isset($captcha) && $captcha && $captcha->isTargetAction('comment')"> {$captcha} diff --git a/widgets/content/content.class.php b/widgets/content/content.class.php index 54e281f8f..37e5594b8 100644 --- a/widgets/content/content.class.php +++ b/widgets/content/content.class.php @@ -187,7 +187,7 @@ class content extends WidgetHandler $obj->sort_index = $args->order_target; $obj->list_count = $args->list_count * $args->page_count; $obj->statusList = [1]; - if($args->show_secret != 'Y') + if(($args->show_secret ?? 'N') !== 'Y') { $obj->is_secret = 'N'; } @@ -201,7 +201,7 @@ class content extends WidgetHandler foreach($output as $key => $oComment) { $oDocument = getModel('document')->getDocument($oComment->get('document_srl'), false, false); - if(!$oDocument->isExists() || $oDocument->isSecret() && $args->show_secret != 'Y') + if(!$oDocument->isExists() || $oDocument->isSecret() && ($args->show_secret ?? 'N') !== 'Y') { continue; } @@ -256,7 +256,7 @@ class content extends WidgetHandler $obj->order_type = $args->order_type=="desc"?"desc":"asc"; } - if($args->show_secret == 'Y') + if(($args->show_secret ?? 'N') == 'Y') { $obj->statusList = array('PUBLIC', 'SECRET'); } diff --git a/widgets/content/skins/default/image_title_content.html b/widgets/content/skins/default/image_title_content.html index a3307dc6f..95e34c5f5 100644 --- a/widgets/content/skins/default/image_title_content.html +++ b/widgets/content/skins/default/image_title_content.html @@ -44,7 +44,7 @@ <span class="icon">{$item->printExtraImages()}</span> <!--@end--> - <!--@if($widget_info->option_view_arr[$j+1]=='regdate')--> + <!--@if(isset($widget_info->option_view_arr[$j+1]) && $widget_info->option_view_arr[$j+1]=='regdate')--> <span class="date">{$item->getRegdate("Y-m-d")}</span> <span class="hour">{$item->getRegdate("H:i")}</span> <!--@end--> </p> @@ -52,7 +52,7 @@ <!--@else if($widget_info->option_view_arr[$j]=='content')--> <p class="text" style="margin-left:{$widget_info->thumbnail_width+20}px;"> {$item->getContent()} - <!--@if($widget_info->option_view_arr[$j+1]=='regdate')--> + <!--@if(isset($widget_info->option_view_arr[$j+1]) && $widget_info->option_view_arr[$j+1]=='regdate')--> <span class="date">{$item->getRegdate("Y-m-d")}</span> <span class="hour">{$item->getRegdate("H:i")}</span> <!--@end--> </p> @@ -60,7 +60,7 @@ <!--@else if($widget_info->option_view_arr[$j]=='nickname')--> <p class="authorArea" style="margin-left:{$widget_info->thumbnail_width+20}px;"> <a href="#" onclick="return false;" class="author member_{$item->getMemberSrl()}" target="_blank"|cond="$widget_info->new_window">{$item->getNickName($widget_info->nickname_cut_size)}</a> - <!--@if($widget_info->option_view_arr[$j+1]=='regdate')--> + <!--@if(isset($widget_info->option_view_arr[$j+1]) && $widget_info->option_view_arr[$j+1]=='regdate')--> <span class="date">{$item->getRegdate("Y-m-d")}</span> <span class="hour">{$item->getRegdate("H:i")}</span> <!--@end--> </p> From 7517d94113cd8c4f79f9ea4e5cd7525ab1aefb54 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sat, 16 Nov 2024 17:16:12 +0900 Subject: [PATCH 26/51] Fix #2430 remove meaningless constructor in member.class.php --- modules/member/member.class.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/modules/member/member.class.php b/modules/member/member.class.php index 6b9a3335a..8e1c59bf9 100644 --- a/modules/member/member.class.php +++ b/modules/member/member.class.php @@ -14,16 +14,6 @@ class Member extends ModuleObject public const NOUSE_EXTRA_VARS = ['error_return_url', 'success_return_url', '_rx_ajax_compat', '_rx_ajax_form', '_rx_csrf_token', 'ruleset', 'captchaType', 'use_editor', 'use_html']; public const STATUS_LIST = ['APPROVED', 'DENIED', 'UNAUTHED', 'SUSPENDED', 'DELETED']; - /** - * constructor - * - * @return void - */ - function __construct() - { - parent::__construct(); - } - /** * Implement if additional tasks are necessary when installing * From ad76223531bf7aea55e7bb4ff17835a73527ade4 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sat, 16 Nov 2024 17:17:58 +0900 Subject: [PATCH 27/51] Fix #2425 open admin board link in new tab --- modules/board/tpl/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/board/tpl/index.html b/modules/board/tpl/index.html index 3d4a90dac..e5dd1d75e 100644 --- a/modules/board/tpl/index.html +++ b/modules/board/tpl/index.html @@ -32,7 +32,7 @@ </td> <td class="domain_prefix"><span class="domain">{$val->domain ?? ''}</span>{\RX_BASEURL}</td> <td>{$val->mid}</td> - <td><a href="{getSiteUrl($val->domain,'','mid',$val->mid)}">{$val->browser_title}</a></td> + <td><a href="{getSiteUrl($val->domain,'','mid',$val->mid)}" target="_blank">{$val->browser_title}</a></td> <td>{zdate($val->regdate,"Y-m-d")}</td> <td> <a href="{getUrl('act','dispBoardAdminBoardInfo','module_srl',$val->module_srl)}"><i class="x_icon-cog"></i> {$lang->cmd_setup}</a>   From e4a100c8968043693d492fa4a19693d5f362828d Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sat, 16 Nov 2024 17:20:50 +0900 Subject: [PATCH 28/51] Fix #2420 display module_srl in board and page list --- modules/board/tpl/index.html | 2 +- modules/page/tpl/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/board/tpl/index.html b/modules/board/tpl/index.html index e5dd1d75e..2be552def 100644 --- a/modules/board/tpl/index.html +++ b/modules/board/tpl/index.html @@ -22,7 +22,7 @@ </thead> <tbody> <tr loop="$board_list => $no,$val"> - <td>{$no}</td> + <td>{$val->module_srl}</td> <td> <block cond="!$val->module_category_srl"> <block cond="$val->site_srl">{$lang->virtual_site}</block> diff --git a/modules/page/tpl/index.html b/modules/page/tpl/index.html index 802486361..25190e960 100644 --- a/modules/page/tpl/index.html +++ b/modules/page/tpl/index.html @@ -58,7 +58,7 @@ jQuery(function($){ <tbody> <!--@foreach($page_list as $no => $val)--> <tr class="row{$cycle_idx}"> - <td>{$no}</td> + <td>{$val->module_srl}</td> <td> <!--@if(!$val->module_category_srl)--> <!--@if($val->site_srl)--> From e61723ce5c42614f60bb4415ad96a901c580f1c7 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sat, 16 Nov 2024 22:32:56 +0900 Subject: [PATCH 29/51] Add member_srl column and reorder columns in admin_log table #2421 --- modules/adminlogging/adminlogging.class.php | 38 ++++++++++++++++++--- modules/adminlogging/schemas/admin_log.xml | 10 +++--- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/modules/adminlogging/adminlogging.class.php b/modules/adminlogging/adminlogging.class.php index 21cd2bc3a..bf58737fb 100644 --- a/modules/adminlogging/adminlogging.class.php +++ b/modules/adminlogging/adminlogging.class.php @@ -18,7 +18,7 @@ class adminlogging extends ModuleObject */ function moduleInstall() { - + } /** @@ -27,7 +27,23 @@ class adminlogging extends ModuleObject */ function checkUpdate() { - return FALSE; + $oDB = DB::getInstance(); + if (!$oDB->isColumnExists('admin_log', 'member_srl')) + { + return true; + } + if (!$oDB->isIndexExists('admin_log', 'idx_member_srl')) + { + return true; + } + + $column_info = $oDB->getColumnInfo('admin_log', 'request_vars'); + if ($column_info->xetype !== 'bigtext') + { + return true; + } + + return false; } /** @@ -36,7 +52,21 @@ class adminlogging extends ModuleObject */ function moduleUpdate() { - + $oDB = DB::getInstance(); + if (!$oDB->isColumnExists('admin_log', 'member_srl')) + { + $oDB->addColumn('admin_log', 'member_srl', 'number', null, 0, true, 'site_srl'); + } + if (!$oDB->isIndexExists('admin_log', 'idx_member_srl')) + { + $oDB->addIndex('admin_log', 'idx_member_srl', ['member_srl']); + } + + $column_info = $oDB->getColumnInfo('admin_log', 'request_vars'); + if ($column_info->xetype !== 'bigtext') + { + $oDB->modifyColumn('admin_log', 'request_vars', 'bigtext'); + } } /** @@ -45,7 +75,7 @@ class adminlogging extends ModuleObject */ function recompileCache() { - + } } diff --git a/modules/adminlogging/schemas/admin_log.xml b/modules/adminlogging/schemas/admin_log.xml index 02ba0af03..31a022493 100644 --- a/modules/adminlogging/schemas/admin_log.xml +++ b/modules/adminlogging/schemas/admin_log.xml @@ -1,8 +1,10 @@ <table name="admin_log"> - <column name="ipaddress" type="varchar" size="60" notnull="notnull" index="idx_admin_ip" /> - <column name="regdate" type="date" index="idx_admin_date" /> - <column name="site_srl" type="number" size="11" default="0" /> + <column name="id" type="number" notnull="notnull" primary_key="primary_key" auto_increment="auto_increment" /> + <column name="site_srl" type="number" default="0" /> + <column name="member_srl" type="number" default="0" index="idx_member_srl" /> <column name="module" type="varchar" size="100" /> <column name="act" type="varchar" size="100" /> - <column name="request_vars" type="text" /> + <column name="request_vars" type="bigtext" /> + <column name="regdate" type="date" index="idx_admin_date" /> + <column name="ipaddress" type="varchar" size="60" notnull="notnull" index="idx_admin_ip" /> </table> From 25564913b85beb0e5fae82805be8e1026514246c Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sat, 16 Nov 2024 22:36:37 +0900 Subject: [PATCH 30/51] Record member_srl in admin_log table, and change request_vars format to JSON #2421 --- modules/adminlogging/adminlogging.controller.php | 11 +++++------ modules/adminlogging/queries/insertLog.xml | 9 +++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/adminlogging/adminlogging.controller.php b/modules/adminlogging/adminlogging.controller.php index 7b35ee58f..1a0c354bf 100644 --- a/modules/adminlogging/adminlogging.controller.php +++ b/modules/adminlogging/adminlogging.controller.php @@ -31,23 +31,22 @@ class adminloggingController extends adminlogging * Insert log * @return void */ - function insertLog($module, $act) + public function insertLog($module, $act) { - if(!$module || !$act) + if (!$module || !$act) { return; } $args = new stdClass(); + $args->member_srl = $this->user->member_srl; $args->module = $module; $args->act = $act; - $args->ipaddress = \RX_CLIENT_IP; + $args->request_vars = json_encode(Context::getRequestVars(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); $args->regdate = date('YmdHis'); - $args->requestVars = print_r(Context::getRequestVars(), TRUE); - + $args->ipaddress = \RX_CLIENT_IP; $output = executeQuery('adminlogging.insertLog', $args); } - } /* End of file adminlogging.controller.php */ /* Location: ./modules/adminlogging/adminlogging.controller.php */ diff --git a/modules/adminlogging/queries/insertLog.xml b/modules/adminlogging/queries/insertLog.xml index 9c4f6ab06..8a0f95e0c 100644 --- a/modules/adminlogging/queries/insertLog.xml +++ b/modules/adminlogging/queries/insertLog.xml @@ -3,11 +3,12 @@ <table name="admin_log" /> </tables> <columns> - <column name="ipaddress" var="ipaddress" notnull="notnull" /> - <column name="regdate" var="regdate" /> - <column name="site_srl" var="siteSrl" filter="number" default="0" /> + <column name="site_srl" var="site_srl" filter="number" default="0" /> + <column name="member_srl" var="member_srl" filter="number" default="0" /> <column name="module" var="module" /> <column name="act" var="act" /> - <column name="request_vars" var="requestVars" /> + <column name="request_vars" var="request_vars" /> + <column name="regdate" var="regdate" default="curdate()" /> + <column name="ipaddress" var="ipaddress" default="ipaddress()" /> </columns> </query> From 5c654c94b7925d96fa617a0dce8a375f3ad9ebad Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sat, 16 Nov 2024 22:38:39 +0900 Subject: [PATCH 31/51] Delete unnecessary init() method in adminloggingController --- modules/adminlogging/adminlogging.controller.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/modules/adminlogging/adminlogging.controller.php b/modules/adminlogging/adminlogging.controller.php index 1a0c354bf..1d9b239ce 100644 --- a/modules/adminlogging/adminlogging.controller.php +++ b/modules/adminlogging/adminlogging.controller.php @@ -11,22 +11,6 @@ */ class adminloggingController extends adminlogging { - - /** - * Initialization - * @return void - */ - function init() - { - // forbit access if the user is not an administrator - $oMemberModel = getModel('member'); - $logged_info = $oMemberModel->getLoggedInfo(); - if($logged_info->is_admin != 'Y') - { - throw new Rhymix\Framework\Exceptions\NotPermitted('admin.msg_is_not_administrator'); - } - } - /** * Insert log * @return void From 737fc40b6676ce3486fc42d0aae295cdd70369ba Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 17 Nov 2024 18:06:33 +0900 Subject: [PATCH 32/51] Add option to control handling of comments in spamfilter keyword #2429 --- modules/spamfilter/lang/en.php | 3 ++- modules/spamfilter/lang/ko.php | 3 ++- modules/spamfilter/spamfilter.admin.controller.php | 7 ++++--- modules/spamfilter/tpl/denied_word_list.html | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/spamfilter/lang/en.php b/modules/spamfilter/lang/en.php index af87ce170..c549fe8e8 100644 --- a/modules/spamfilter/lang/en.php +++ b/modules/spamfilter/lang/en.php @@ -14,10 +14,11 @@ $lang->word = 'Keyword'; $lang->hit = 'Hit'; $lang->latest_hit = 'Latest Hits'; $lang->custom_message = 'Error Message'; +$lang->enable_description = 'Enter # as description'; $lang->about_custom_message = 'You can customize the error message that will be displayed if a spam keyword is found.<br>%s can be used as a placeholder for the keyword. If not used, the keyword will be hidden.'; $lang->about_interval = 'All articles attempted for posting within the assigned time will be blocked.'; $lang->about_denied_ip = 'Please enter one IP address (e.g. 127.0.0.1) or range (e.g. 127.0.0.0/24) per line. Comments may start with // or #.'; -$lang->about_denied_word = 'Please enter one keyword (2 to 180 characters) per line. Comments may start with #.<br>Formats such as /spam(key|word)?/ will be treated as a regular expression, and must use the proper syntax.<br>Spam keywords are not case sensitive.'; +$lang->about_denied_word = 'Please enter one keyword (2 to 180 characters) per line. Comments start with #. If you need to block a keyword that includes #, disable the checkbox above.<br>Formats such as /spam(key|word)?/ will be treated as a regular expression, and must use the proper syntax.<br>Spam keywords are not case sensitive.'; $lang->msg_alert_limited_by_config = 'Please do not post repeatedly within %d seconds. If you keep trying, your IP address will be blocked.'; $lang->msg_alert_limited_message_by_config = 'Please do not send messages repeatedly within %d seconds. If you keep trying, your IP address will be blocked.'; $lang->msg_alert_denied_word = 'The word "%s" is not allowed on this site.'; diff --git a/modules/spamfilter/lang/ko.php b/modules/spamfilter/lang/ko.php index 82f13a8f7..53391dbc0 100644 --- a/modules/spamfilter/lang/ko.php +++ b/modules/spamfilter/lang/ko.php @@ -14,10 +14,11 @@ $lang->word = '키워드'; $lang->hit = '히트'; $lang->latest_hit = '최근 히트'; $lang->custom_message = '차단 메시지 설정'; +$lang->enable_description = '# 뒷부분은 설명으로 입력'; $lang->about_custom_message = '스팸 키워드 발견시 표시할 에러 메시지를 지정할 수 있습니다.<br>%s를 넣으면 그 자리에 해당 키워드를 표시하고, 그렇지 않으면 키워드를 숨깁니다.'; $lang->about_interval = '지정된 시간 내에 글을 등록하지 못하게 합니다.'; $lang->about_denied_ip = '한 줄에 하나씩 IP 주소 또는 대역을 입력하세요. "//" 또는 "#" 뒷부분은 설명으로 저장됩니다.<br>예: 127.0.0.1 // 설명, 127.0.0.1 #설명<br>IP 대역 표기법은 <a href="https://github.com/rhymix/rhymix-docs/blob/master/ko/misc/ipfilter.md" target="_blank">매뉴얼</a>을 참고하십시오.'; -$lang->about_denied_word = '한 줄에 하나씩 스팸 키워드(2~180자)를 입력하세요. "#" 뒷부분은 설명으로 입력됩니다.<br>/스팸(키+|워드)?/ 와 같은 형태로 입력하면 정규식으로 간주하며, 올바른 정규식 문법을 사용해야 합니다.<br>대소문자는 구분하지 않습니다.'; +$lang->about_denied_word = '한 줄에 하나씩 스팸 키워드(2~180자)를 입력하세요. "#" 뒷부분은 설명으로 입력됩니다. "#"을 포함하는 키워드를 차단하려면 위의 설정을 해제하세요.<br>/스팸(키+|워드)?/ 와 같은 형태로 입력하면 정규식으로 간주하며, 올바른 정규식 문법을 사용해야 합니다.<br>대소문자는 구분하지 않습니다.'; $lang->msg_alert_limited_by_config = '%d초 이내에 연속 글 작성은 금지됩니다. 계속 시도하면 IP가 차단될 수 있습니다.'; $lang->msg_alert_limited_message_by_config = '%d초 이내에 연속 쪽지 발송은 금지됩니다. 계속 시도하면 IP가 차단될 수 있습니다.'; $lang->msg_alert_denied_word = '"%s"은(는) 사용이 금지된 단어입니다.'; diff --git a/modules/spamfilter/spamfilter.admin.controller.php b/modules/spamfilter/spamfilter.admin.controller.php index e4887f600..c24f082e5 100644 --- a/modules/spamfilter/spamfilter.admin.controller.php +++ b/modules/spamfilter/spamfilter.admin.controller.php @@ -177,9 +177,10 @@ class SpamfilterAdminController extends Spamfilter { //스팸 키워드 추가 $word_list = Context::get('word_list'); + $enable_description = Context::get('enable_description') ?? 'N'; if($word_list) { - $output = $this->insertWord($word_list); + $output = $this->insertWord($word_list, $enable_description); if(!$output->toBool() && !$output->get('fail_list')) return $output; if($output->get('fail_list')) $message_fail = '<em>'.sprintf(lang('msg_faillist'),$output->get('fail_list')).'</em>'; @@ -258,7 +259,7 @@ class SpamfilterAdminController extends Spamfilter * @brief Register the spam word * The post, which contains the newly registered spam word, should be considered as a spam */ - public function insertWord($word_list) + public function insertWord($word_list, $enable_description = 'Y') { if (!is_array($word_list)) { @@ -273,7 +274,7 @@ class SpamfilterAdminController extends Spamfilter { continue; } - if (preg_match('/^(.+?)#(.+)$/', $word, $matches)) + if ($enable_description === 'Y' && preg_match('/^(.+?)#(.+)$/', $word, $matches)) { $word = trim($matches[1]); $description = trim($matches[2]); diff --git a/modules/spamfilter/tpl/denied_word_list.html b/modules/spamfilter/tpl/denied_word_list.html index e1f0b9904..eb51e9bdd 100644 --- a/modules/spamfilter/tpl/denied_word_list.html +++ b/modules/spamfilter/tpl/denied_word_list.html @@ -44,6 +44,7 @@ <input type="hidden" name="active" value="word" /> <input type="hidden" name="xe_validator_id" value="modules/spamfilter/tpl/1" /> <textarea name="word_list" title="{$lang->add_denied_word}" rows="4" cols="42" style="width:100%"></textarea> + <label><input type="checkbox" name="enable_description" value="Y" checked="checked" /> {$lang->enable_description}</label> <p class="x_help-block">{$lang->about_denied_word}</p> <span class="x_pull-right" style="margin-right:-14px"> <button type="submit" class="x_btn x_btn-primary">{$lang->add_denied_word}</button> From a90f444182d1945d0a3f834172cba42f5a03e96a Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 17 Nov 2024 20:43:40 +0900 Subject: [PATCH 33/51] Block voting and reporting from spam IP #2423 --- modules/spamfilter/conf/module.xml | 6 ++- modules/spamfilter/spamfilter.controller.php | 44 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/modules/spamfilter/conf/module.xml b/modules/spamfilter/conf/module.xml index 76ed5c668..00440ac48 100644 --- a/modules/spamfilter/conf/module.xml +++ b/modules/spamfilter/conf/module.xml @@ -17,11 +17,15 @@ <action name="procSpamfilterAdminInsertConfigCaptcha" type="controller" /> </actions> <eventHandlers> + <eventHandler before="document.manage" class="controller" method="triggerManageDocument" /> <eventHandler before="document.insertDocument" class="controller" method="triggerInsertDocument" /> <eventHandler before="document.updateDocument" class="controller" method="triggerInsertDocument" /> - <eventHandler before="document.manage" class="controller" method="triggerManageDocument" /> + <eventHandler before="document.updateVotedCount" class="controller" method="triggerVote" /> + <eventHandler before="document.declaredDocument" class="controller" method="triggerDeclare" /> <eventHandler before="comment.insertComment" class="controller" method="triggerInsertComment" /> <eventHandler before="comment.updateComment" class="controller" method="triggerInsertComment" /> + <eventHandler before="comment.updateVotedCount" class="controller" method="triggerVote" /> + <eventHandler before="comment.declaredComment" class="controller" method="triggerDeclare" /> <eventHandler before="communication.sendMessage" class="controller" method="triggerSendMessage" /> <eventHandler before="moduleObject.proc" class="controller" method="triggerCheckCaptcha" /> </eventHandlers> diff --git a/modules/spamfilter/spamfilter.controller.php b/modules/spamfilter/spamfilter.controller.php index 815bead6a..435aa3802 100644 --- a/modules/spamfilter/spamfilter.controller.php +++ b/modules/spamfilter/spamfilter.controller.php @@ -182,6 +182,50 @@ class SpamfilterController extends Spamfilter return $output; } + /** + * Block voting from a spam IP. + */ + function triggerVote(&$obj) + { + if ($_SESSION['avoid_log']) + { + return; + } + + if ($this->user->isAdmin() || (Context::get('grant')->manager ?? false)) + { + return; + } + + $output = SpamfilterModel::getInstance()->isDeniedIP(); + if (!$output->toBool()) + { + return $output; + } + } + + /** + * Block reporting from a spam IP. + */ + function triggerDeclare(&$obj) + { + if ($_SESSION['avoid_log']) + { + return; + } + + if ($this->user->isAdmin() || (Context::get('grant')->manager ?? false)) + { + return; + } + + $output = SpamfilterModel::getInstance()->isDeniedIP(); + if (!$output->toBool()) + { + return $output; + } + } + /** * @brief The routine process to check the time it takes to store a message, when writing it, and to ban IP/word */ From a04dc1270f9814aecfd0782982390953096e22f2 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 17 Nov 2024 20:58:56 +0900 Subject: [PATCH 34/51] Make all spamfilter model methods static --- modules/spamfilter/spamfilter.controller.php | 25 +++++++-------- modules/spamfilter/spamfilter.model.php | 33 ++++++++------------ 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/modules/spamfilter/spamfilter.controller.php b/modules/spamfilter/spamfilter.controller.php index 435aa3802..0e75d92ee 100644 --- a/modules/spamfilter/spamfilter.controller.php +++ b/modules/spamfilter/spamfilter.controller.php @@ -50,9 +50,8 @@ class SpamfilterController extends Spamfilter if($grant->manager) return; } - $oFilterModel = getModel('spamfilter'); // Check if the IP is prohibited - $output = $oFilterModel->isDeniedIP(); + $output = SpamfilterModel::isDeniedIP(); if(!$output->toBool()) return $output; // Check if there is a ban on the word $filter_targets = [$obj->title, $obj->content, $obj->tags ?? '']; @@ -71,7 +70,7 @@ class SpamfilterController extends Spamfilter } } } - $output = $oFilterModel->isDeniedWord(implode("\n", $filter_targets)); + $output = SpamfilterModel::isDeniedWord(implode("\n", $filter_targets)); if(!$output->toBool()) { return $output; @@ -79,7 +78,7 @@ class SpamfilterController extends Spamfilter // Check the specified time beside the modificaiton time if($obj->document_srl == 0) { - $output = $oFilterModel->checkLimited(); + $output = SpamfilterModel::checkLimited(); if(!$output->toBool()) return $output; } // Save a log @@ -103,9 +102,8 @@ class SpamfilterController extends Spamfilter if($grant->manager) return; } - $oFilterModel = getModel('spamfilter'); // Check if the IP is prohibited - $output = $oFilterModel->isDeniedIP(); + $output = SpamfilterModel::isDeniedIP(); if(!$output->toBool()) return $output; // Check if there is a ban on the word if($is_logged) @@ -116,12 +114,12 @@ class SpamfilterController extends Spamfilter { $text = $obj->content . ' ' . $obj->nick_name . ' ' . $obj->homepage; } - $output = $oFilterModel->isDeniedWord($text); + $output = SpamfilterModel::isDeniedWord($text); if(!$output->toBool()) return $output; // If the specified time check is not modified if(!$obj->__isupdate) { - $output = $oFilterModel->checkLimited(); + $output = SpamfilterModel::checkLimited(); if(!$output->toBool()) return $output; } unset($obj->__isupdate); @@ -197,7 +195,7 @@ class SpamfilterController extends Spamfilter return; } - $output = SpamfilterModel::getInstance()->isDeniedIP(); + $output = SpamfilterModel::isDeniedIP(); if (!$output->toBool()) { return $output; @@ -219,7 +217,7 @@ class SpamfilterController extends Spamfilter return; } - $output = SpamfilterModel::getInstance()->isDeniedIP(); + $output = SpamfilterModel::isDeniedIP(); if (!$output->toBool()) { return $output; @@ -240,16 +238,15 @@ class SpamfilterController extends Spamfilter $logged_info = Context::get('logged_info'); if($logged_info->is_admin == 'Y') return; - $oFilterModel = getModel('spamfilter'); // Check if the IP is prohibited - $output = $oFilterModel->isDeniedIP(); + $output = SpamfilterModel::isDeniedIP(); if(!$output->toBool()) return $output; // Check if there is a ban on the word $text = $obj->title . ' ' . $obj->content; - $output = $oFilterModel->isDeniedWord($text); + $output = SpamfilterModel::isDeniedWord($text); if(!$output->toBool()) return $output; // Check the specified time - $output = $oFilterModel->checkLimited(TRUE); + $output = SpamfilterModel::checkLimited(TRUE); if(!$output->toBool()) return $output; // Save a log $this->insertLog(); diff --git a/modules/spamfilter/spamfilter.model.php b/modules/spamfilter/spamfilter.model.php index feee6bf42..dcd23d6cb 100644 --- a/modules/spamfilter/spamfilter.model.php +++ b/modules/spamfilter/spamfilter.model.php @@ -7,17 +7,10 @@ */ class SpamfilterModel extends Spamfilter { - /** - * @brief Initialization - */ - function init() - { - } - /** * @brief Return the user setting values of the Spam filter module */ - function getConfig() + public static function getConfig() { return ModuleModel::getModuleConfig('spamfilter') ?: new stdClass; } @@ -25,7 +18,7 @@ class SpamfilterModel extends Spamfilter /** * @brief Return the list of registered IP addresses which were banned */ - function getDeniedIPList($sort_index = 'regdate') + public static function getDeniedIPList($sort_index = 'regdate') { $args = new stdClass(); $args->sort_index = $sort_index; @@ -38,12 +31,12 @@ class SpamfilterModel extends Spamfilter /** * @brief Check if the ipaddress is in the list of banned IP addresses */ - function isDeniedIP() + public static function isDeniedIP() { $ip_list = Rhymix\Framework\Cache::get('spamfilter:denied_ip_list'); if ($ip_list === null) { - $ip_list = $this->getDeniedIPList(); + $ip_list = self::getDeniedIPList(); Rhymix\Framework\Cache::set('spamfilter:denied_ip_list', $ip_list); } if (!count($ip_list)) @@ -75,7 +68,7 @@ class SpamfilterModel extends Spamfilter /** * @brief Return the list of registered Words which were banned */ - function getDeniedWordList($sort_index = 'hit') + public static function getDeniedWordList($sort_index = 'hit') { $args = new stdClass(); $args->sort_index = $sort_index; @@ -86,12 +79,12 @@ class SpamfilterModel extends Spamfilter /** * @brief Check if the text, received as a parameter, is banned or not */ - function isDeniedWord($text) + public static function isDeniedWord($text) { $word_list = Rhymix\Framework\Cache::get('spamfilter:denied_word_list'); if ($word_list === null) { - $word_list = $this->getDeniedWordList(); + $word_list = self::getDeniedWordList(); Rhymix\Framework\Cache::set('spamfilter:denied_word_list', $word_list); } if (!count($word_list)) @@ -128,7 +121,7 @@ class SpamfilterModel extends Spamfilter $args->word = $word; executeQuery('spamfilter.updateDeniedWordHit', $args); - $config = $this->getConfig(); + $config = self::getConfig(); if($config->custom_message) { @@ -161,9 +154,9 @@ class SpamfilterModel extends Spamfilter /** * @brief Check the specified time */ - function checkLimited($isMessage = FALSE) + public static function checkLimited($isMessage = FALSE) { - $config = $this->getConfig(); + $config = self::getConfig(); if($config->limits != 'Y') return new BaseObject(); $limit_count = $config->limits_count ?: 3; @@ -177,7 +170,7 @@ class SpamfilterModel extends Spamfilter } } - $count = $this->getLogCount($interval); + $count = self::getLogCount($interval); // Ban the IP address if the interval is exceeded if($count>=$limit_count) @@ -272,7 +265,7 @@ class SpamfilterModel extends Spamfilter /** * @brief Check if the trackbacks have already been registered to a particular article */ - function isInsertedTrackback($document_srl) + public static function isInsertedTrackback($document_srl) { $oTrackbackModel = getModel('trackback'); if (is_object($oTrackbackModel) && method_exists($oTrackbackModel, 'getTrackbackCountByIPAddress')) @@ -289,7 +282,7 @@ class SpamfilterModel extends Spamfilter /** * @brief Return the number of logs recorded within the interval for the specified IPaddress */ - function getLogCount($time = 60, $ipaddress='') + public static function getLogCount($time = 60, $ipaddress='') { if(!$ipaddress) $ipaddress = \RX_CLIENT_IP; From fe6625bfbb3f4f7cf90c2a9a8baf667ca2f3f262 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 17 Nov 2024 21:32:16 +0900 Subject: [PATCH 35/51] Allow selecting which actions to block if the user's IP is listed in spamfilter #2423 --- modules/spamfilter/lang/en.php | 4 +- modules/spamfilter/lang/ko.php | 6 +- .../spamfilter.admin.controller.php | 3 +- modules/spamfilter/spamfilter.controller.php | 61 ++++++++++++++++--- modules/spamfilter/tpl/config_block.html | 30 +++++++++ 5 files changed, 91 insertions(+), 13 deletions(-) diff --git a/modules/spamfilter/lang/en.php b/modules/spamfilter/lang/en.php index c549fe8e8..8d09bb889 100644 --- a/modules/spamfilter/lang/en.php +++ b/modules/spamfilter/lang/en.php @@ -28,7 +28,9 @@ $lang->cmd_spamfilter_except_member = 'Except Members'; $lang->cmd_spamfilter_filter_html = 'HTML'; $lang->cmd_spamfilter_is_regexp = 'REGEXP'; $lang->cmd_interval = 'Block Post/Comment Spam'; -$lang->cmd_interval_help = 'Block IP addresses that post or comment too much in a short time. Blocked IP addresses will not be able to post, comment, or send messages.'; +$lang->cmd_interval_help = 'Block IP addresses that post or comment too much in a short time.'; +$lang->cmd_blocked_actions = 'Blocked actions'; +$lang->cmd_blocked_actions_help = 'The actions above will be disabled from blocked IP addresses.'; $lang->cmd_check_trackback = 'Block Trackback Spam'; $lang->cmd_check_trackback_help = 'Block IP addresses that send multiple trackbacks to the same document.<br>This only works if the trackback module is installed.'; $lang->cmd_limits_interval = 'Block Interval'; diff --git a/modules/spamfilter/lang/ko.php b/modules/spamfilter/lang/ko.php index 53391dbc0..3b2e1dfb6 100644 --- a/modules/spamfilter/lang/ko.php +++ b/modules/spamfilter/lang/ko.php @@ -27,8 +27,10 @@ $lang->msg_alert_trackback_denied = '한 글에는 하나의 트랙백만 허용 $lang->cmd_spamfilter_except_member = '회원 제외'; $lang->cmd_spamfilter_filter_html = 'HTML'; $lang->cmd_spamfilter_is_regexp = '정규식'; -$lang->cmd_interval = '글, 댓글 스팸 차단'; -$lang->cmd_interval_help = '지정한 시간 내에 다수의 글이나 댓글을 작성하면 스패머로 간주하고 글, 댓글 작성과 엮인글 발송, 쪽지 발송을 차단합니다.'; +$lang->cmd_interval = '단시간 다수 작성 차단'; +$lang->cmd_interval_help = '지정한 시간 내에 다수의 글이나 댓글을 작성하면 스패머로 간주하고 IP를 차단합니다.'; +$lang->cmd_blocked_actions = '차단할 행동'; +$lang->cmd_blocked_actions_help = '차단된 IP에서는 위의 행동들을 할 수 없게 됩니다.'; $lang->cmd_check_trackback = '트랙백 스팸 차단'; $lang->cmd_check_trackback_help = '하나의 글에 2회 이상 엮인글을 등록하면 스패머로 간주하고 엮인글을 차단합니다.<br>트랙백 모듈이 설치되어 있는 경우에만 적용됩니다.'; $lang->cmd_limits_interval = '글, 댓글 제한 시간'; diff --git a/modules/spamfilter/spamfilter.admin.controller.php b/modules/spamfilter/spamfilter.admin.controller.php index c24f082e5..38e99cab0 100644 --- a/modules/spamfilter/spamfilter.admin.controller.php +++ b/modules/spamfilter/spamfilter.admin.controller.php @@ -20,7 +20,7 @@ class SpamfilterAdminController extends Spamfilter $config = ModuleModel::getModuleConfig('spamfilter') ?: new stdClass; // Get the default information - $args = Context::gets('limits', 'limits_interval', 'limits_count', 'ipv4_block_range', 'ipv6_block_range', 'except_ip', 'custom_message'); + $args = Context::gets('limits', 'limits_interval', 'limits_count', 'blocked_actions', 'ipv4_block_range', 'ipv6_block_range', 'except_ip', 'custom_message'); // Set default values if($args->limits != 'Y') @@ -38,6 +38,7 @@ class SpamfilterAdminController extends Spamfilter $args->except_ip = array_map('trim', preg_split('/[\n,]/', trim($args->except_ip ?? ''), -1, \PREG_SPLIT_NO_EMPTY)); $args->limits_interval = intval($args->limits_interval); $args->limits_count = intval($args->limits_count); + $args->blocked_actions = array_values($args->blocked_actions ?? []); $args->custom_message = escape(utf8_trim($args->custom_message)); foreach ($args as $key => $val) { diff --git a/modules/spamfilter/spamfilter.controller.php b/modules/spamfilter/spamfilter.controller.php index 0e75d92ee..4aacea483 100644 --- a/modules/spamfilter/spamfilter.controller.php +++ b/modules/spamfilter/spamfilter.controller.php @@ -52,7 +52,15 @@ class SpamfilterController extends Spamfilter // Check if the IP is prohibited $output = SpamfilterModel::isDeniedIP(); - if(!$output->toBool()) return $output; + if(!$output->toBool()) + { + $config = SpamfilterModel::getConfig(); + if (!isset($config->blocked_actions) || in_array('document', $config->blocked_actions)) + { + return $output; + } + } + // Check if there is a ban on the word $filter_targets = [$obj->title, $obj->content, $obj->tags ?? '']; if(!$is_logged) @@ -104,7 +112,15 @@ class SpamfilterController extends Spamfilter // Check if the IP is prohibited $output = SpamfilterModel::isDeniedIP(); - if(!$output->toBool()) return $output; + if(!$output->toBool()) + { + $config = SpamfilterModel::getConfig(); + if (!isset($config->blocked_actions) || in_array('comment', $config->blocked_actions)) + { + return $output; + } + } + // Check if there is a ban on the word if($is_logged) { @@ -185,7 +201,7 @@ class SpamfilterController extends Spamfilter */ function triggerVote(&$obj) { - if ($_SESSION['avoid_log']) + if (!empty($_SESSION['avoid_log'])) { return; } @@ -195,6 +211,16 @@ class SpamfilterController extends Spamfilter return; } + $config = SpamfilterModel::getConfig(); + if ($obj->point > 0 && isset($config->blocked_actions) && !in_array('vote_up', $config->blocked_actions)) + { + return; + } + if ($obj->point < 0 && isset($config->blocked_actions) && !in_array('vote_down', $config->blocked_actions)) + { + return; + } + $output = SpamfilterModel::isDeniedIP(); if (!$output->toBool()) { @@ -207,7 +233,7 @@ class SpamfilterController extends Spamfilter */ function triggerDeclare(&$obj) { - if ($_SESSION['avoid_log']) + if (!empty($_SESSION['avoid_log'])) { return; } @@ -217,6 +243,12 @@ class SpamfilterController extends Spamfilter return; } + $config = SpamfilterModel::getConfig(); + if (isset($config->blocked_actions) && !in_array('declare', $config->blocked_actions)) + { + return; + } + $output = SpamfilterModel::isDeniedIP(); if (!$output->toBool()) { @@ -229,25 +261,36 @@ class SpamfilterController extends Spamfilter */ function triggerSendMessage(&$obj) { - if($_SESSION['avoid_log']) return; + if($this->user->isAdmin() || !empty($_SESSION['avoid_log'])) + { + return; + } + if(isset($obj->use_spamfilter) && $obj->use_spamfilter === false) { return; } - $logged_info = Context::get('logged_info'); - if($logged_info->is_admin == 'Y') return; - // Check if the IP is prohibited $output = SpamfilterModel::isDeniedIP(); - if(!$output->toBool()) return $output; + if(!$output->toBool()) + { + $config = SpamfilterModel::getConfig(); + if (!isset($config->blocked_actions) || in_array('message', $config->blocked_actions)) + { + return $output; + } + } + // Check if there is a ban on the word $text = $obj->title . ' ' . $obj->content; $output = SpamfilterModel::isDeniedWord($text); if(!$output->toBool()) return $output; + // Check the specified time $output = SpamfilterModel::checkLimited(TRUE); if(!$output->toBool()) return $output; + // Save a log $this->insertLog(); } diff --git a/modules/spamfilter/tpl/config_block.html b/modules/spamfilter/tpl/config_block.html index 3ffbf8f10..d94daa11b 100644 --- a/modules/spamfilter/tpl/config_block.html +++ b/modules/spamfilter/tpl/config_block.html @@ -27,6 +27,36 @@ <p class="x_help-block">{$lang->cmd_interval_help}</p> </div> </div> + <div class="x_control-group"> + <label class="x_control-label">{$lang->cmd_blocked_actions}</label> + <div class="x_controls"> + <label class="x_inline"> + <input type="checkbox" name="blocked_actions[]" value="document" checked="checked"|cond="!$config->blocked_actions || in_array('document', $config->blocked_actions)" /> + {$lang->document} + </label> + <label class="x_inline"> + <input type="checkbox" name="blocked_actions[]" value="comment" checked="checked"|cond="!$config->blocked_actions || in_array('comment', $config->blocked_actions)" /> + {$lang->comment} + </label> + <label class="x_inline"> + <input type="checkbox" name="blocked_actions[]" value="vote_up" checked="checked"|cond="!$config->blocked_actions || in_array('vote_up', $config->blocked_actions)" /> + {$lang->cmd_vote} + </label> + <label class="x_inline"> + <input type="checkbox" name="blocked_actions[]" value="vote_down" checked="checked"|cond="!$config->blocked_actions || in_array('vote_down', $config->blocked_actions)" /> + {$lang->cmd_vote_down} + </label> + <label class="x_inline"> + <input type="checkbox" name="blocked_actions[]" value="declare" checked="checked"|cond="!$config->blocked_actions || in_array('declare', $config->blocked_actions)" /> + {$lang->cmd_declare} + </label> + <label class="x_inline"> + <input type="checkbox" name="blocked_actions[]" value="message" checked="checked"|cond="!$config->blocked_actions || in_array('message', $config->blocked_actions)" /> + {$lang->member_message} + </label> + <p class="x_help-block">{$lang->cmd_blocked_actions_help}</p> + </div> + </div> <div class="x_control-group"> <label class="x_control-label" for="custom_message">{$lang->custom_message}</label> <div class="x_controls"> From b3a311488b13e1cb1584f300bd15a1a5d3b79e30 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 17 Nov 2024 21:59:26 +0900 Subject: [PATCH 36/51] Fix #2432 untranslated group name --- modules/member/member.admin.view.php | 4 ++++ modules/member/member.view.php | 5 +++++ modules/member/tpl/member_list.html | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/member/member.admin.view.php b/modules/member/member.admin.view.php index a573d6c18..dbb4cd74a 100644 --- a/modules/member/member.admin.view.php +++ b/modules/member/member.admin.view.php @@ -58,6 +58,10 @@ class MemberAdminView extends Member // retrieve group list $this->group_list = $oMemberModel->getGroups(); + foreach ($this->group_list as $group) + { + $group->title = Context::replaceUserLang($group->title, true); + } Context::set('group_list', $this->group_list); $security = new Security(); diff --git a/modules/member/member.view.php b/modules/member/member.view.php index e6aee4846..72846cf20 100644 --- a/modules/member/member.view.php +++ b/modules/member/member.view.php @@ -176,6 +176,11 @@ class MemberView extends Member $member_info->email_address = sprintf('%s@%s', $protect_id, $email_host); } + foreach ($member_info->group_list ?? [] as $key => $val) + { + $member_info->group_list[$key] = Context::replaceUserLang($val, true); + } + Context::set('memberInfo', get_object_vars($member_info)); $extendForm = MemberModel::getCombineJoinForm($member_info); diff --git a/modules/member/tpl/member_list.html b/modules/member/tpl/member_list.html index 0b2bb461b..dfd63161a 100644 --- a/modules/member/tpl/member_list.html +++ b/modules/member/tpl/member_list.html @@ -61,7 +61,7 @@ <i class="no_profile">?</i> <!--@end--> </td> - {@ $member_info['group_list'] = implode(', ', $member_info['group_list'])} + {@ $member_info['group_list'] = Context::replaceUserLang(implode(', ', $member_info['group_list']), true)} <td class="nowr" loop="$usedIdentifiers=>$name,$title"> <!--@if($name === 'email_address')--> <a href="#popup_menu_area" class="member_{$member_info['member_srl']}">{getEncodeEmailAddress($member_info['email_address'])}</a> From 7ffb1c50fc5122b67f7901effd872372dad75226 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 17 Nov 2024 22:18:38 +0900 Subject: [PATCH 37/51] Define RX_WINDOWS based on PHP_OS_FAMILY --- common/constants.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constants.php b/common/constants.php index da9ab15e6..2bce7548e 100644 --- a/common/constants.php +++ b/common/constants.php @@ -125,7 +125,7 @@ else /** * RX_WINDOWS is true if the operating system is Windows. */ -define('RX_WINDOWS', strncasecmp(PHP_OS, 'WIN', 3) === 0); +define('RX_WINDOWS', PHP_OS_FAMILY === 'Windows'); /** * XE core compatibility constants (may be used by XE-compatible plugins and themes). From b6fc630f4a57d8a5c7136c676d4aa567c0ca44d4 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 17 Nov 2024 22:22:54 +0900 Subject: [PATCH 38/51] Use RX_WINDOWS consistently --- modules/admin/controllers/maintenance/CacheReset.php | 2 +- modules/admin/controllers/maintenance/Cleanup.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/admin/controllers/maintenance/CacheReset.php b/modules/admin/controllers/maintenance/CacheReset.php index b40845f27..7eb18ad2c 100644 --- a/modules/admin/controllers/maintenance/CacheReset.php +++ b/modules/admin/controllers/maintenance/CacheReset.php @@ -95,7 +95,7 @@ class CacheReset extends Base // If possible, use system command to speed up recursive deletion if (function_exists('exec') && !preg_match('/(?<!_)exec/', ini_get('disable_functions'))) { - if (strncasecmp(\PHP_OS, 'win', 3) == 0) + if (\RX_WINDOWS) { @exec('rmdir /S /Q ' . escapeshellarg($tmp_dir)); } diff --git a/modules/admin/controllers/maintenance/Cleanup.php b/modules/admin/controllers/maintenance/Cleanup.php index e1870720b..5213679c0 100644 --- a/modules/admin/controllers/maintenance/Cleanup.php +++ b/modules/admin/controllers/maintenance/Cleanup.php @@ -176,7 +176,7 @@ class Cleanup extends Base { return $cache = true; } - if (preg_match('/Win/i', \PHP_OS)) + if (\RX_WINDOWS) { return $cache = false; } From 8d1429b286a3a550c624e97280e7a7194dd6ffe7 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 17 Nov 2024 22:23:15 +0900 Subject: [PATCH 39/51] Always check filesystem case sensitivity in Linux --- modules/admin/controllers/maintenance/Cleanup.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/admin/controllers/maintenance/Cleanup.php b/modules/admin/controllers/maintenance/Cleanup.php index 5213679c0..7e5e3e45e 100644 --- a/modules/admin/controllers/maintenance/Cleanup.php +++ b/modules/admin/controllers/maintenance/Cleanup.php @@ -172,10 +172,6 @@ class Cleanup extends Base } // Return default values for most common operating systems. - if (preg_match('/Linux/', \PHP_OS)) - { - return $cache = true; - } if (\RX_WINDOWS) { return $cache = false; From f35ffad7669140c2a40bcca2cc0b3a60f6adaed8 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 17 Nov 2024 22:24:57 +0900 Subject: [PATCH 40/51] Call opcache_reset() after recreating cache files --- modules/admin/controllers/maintenance/CacheReset.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/admin/controllers/maintenance/CacheReset.php b/modules/admin/controllers/maintenance/CacheReset.php index 7eb18ad2c..c287dbce0 100644 --- a/modules/admin/controllers/maintenance/CacheReset.php +++ b/modules/admin/controllers/maintenance/CacheReset.php @@ -115,6 +115,12 @@ class CacheReset extends Base $oAutoinstallAdminController = getAdminController('autoinstall'); $oAutoinstallAdminController->checkInstalled(); + // Opcache reset + if (function_exists('opcache_reset')) + { + opcache_reset(); + } + $this->setMessage('success_updated'); } } From 28cc1fe113c67cd86bbecca2f5626fa62b9171cd Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Sun, 17 Nov 2024 23:35:01 +0900 Subject: [PATCH 41/51] Remove document context menu from default mobile skin, too #2415 --- modules/page/m.skins/default/mobile.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/page/m.skins/default/mobile.html b/modules/page/m.skins/default/mobile.html index 02011ab25..1fbdf370c 100644 --- a/modules/page/m.skins/default/mobile.html +++ b/modules/page/m.skins/default/mobile.html @@ -1,2 +1,2 @@ <h1 cond="($module_info->display_mobile_title ?? '') !== 'hide'">{$oDocument->getTitle()}</h1> -{$oDocument->getContent(($module_info->display_popupmenu ?? '') !== 'hide')} +{$oDocument->getContent(false)} From a8016bd05cbc27cb7ac2ac35b21a880e109e6d9e Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Mon, 18 Nov 2024 11:29:24 +0900 Subject: [PATCH 42/51] Version 2.1.19 --- common/constants.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/constants.php b/common/constants.php index 2bce7548e..b3bebb644 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.18'); +define('RX_VERSION', '2.1.19'); /** * RX_MICROTIME is the startup time of the current script, in microseconds since the Unix epoch. From d5796b6f9dcbcb90377db97d46a88e584da68cd6 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Mon, 18 Nov 2024 23:57:23 +0900 Subject: [PATCH 43/51] Fix #2273 change minimum PHP version to 7.4 --- common/autoload.php | 4 ++-- common/constants.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/autoload.php b/common/autoload.php index 8d7f98b3d..233ce1b83 100644 --- a/common/autoload.php +++ b/common/autoload.php @@ -11,10 +11,10 @@ if (defined('RX_VERSION')) /** * Check PHP version. */ -if (PHP_VERSION_ID < 70205) +if (PHP_VERSION_ID < 70400) { header('HTTP/1.1 500 Internal Server Error'); - echo 'Rhymix requires PHP 7.2.5 or higher.'; + echo 'Rhymix requires PHP 7.4 or higher.'; exit(1); } diff --git a/common/constants.php b/common/constants.php index b3bebb644..6cce49864 100644 --- a/common/constants.php +++ b/common/constants.php @@ -143,7 +143,7 @@ define('__XE_VERSION_ALPHA__', false); define('__XE_VERSION_BETA__', false); define('__XE_VERSION_RC__', false); define('__XE_VERSION_STABLE__', true); -define('__XE_MIN_PHP_VERSION__', '7.2.5'); +define('__XE_MIN_PHP_VERSION__', '7.4.0'); define('__XE_RECOMMEND_PHP_VERSION__', '7.4.0'); define('__ZBXE_VERSION__', RX_VERSION); define('_XE_LOCATION_', 'ko'); From b2051f99346c1801d81845d7a345b9785a92bf95 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Mon, 18 Nov 2024 23:59:02 +0900 Subject: [PATCH 44/51] Remove condition for PHP < 7.3 --- common/framework/Cookie.php | 19 +------------------ common/framework/Session.php | 12 +----------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/common/framework/Cookie.php b/common/framework/Cookie.php index 997b39410..2374be6c8 100644 --- a/common/framework/Cookie.php +++ b/common/framework/Cookie.php @@ -79,24 +79,7 @@ class Cookie $options['samesite'] = config('cookie.samesite') ?? 'Lax'; } - // PHP 7.3+ supports the samesite attribute natively. PHP 7.2 requires a hack. - if (\PHP_VERSION_ID >= 70300) - { - $result = setcookie($name, $value, $options); - } - else - { - $expires = $options['expires']; - $path = $options['path'] ?? '/'; - $domain = $options['domain'] ?? null; - $secure = $options['secure'] ?? false; - $httponly = $options['httponly'] ?? false; - if (!empty($options['samesite'])) - { - $path = ($path ?: '/') . '; SameSite=' . $options['samesite']; - } - $result = setcookie($name, $value, $expires, $path, $domain, $secure, $httponly); - } + $result = setcookie($name, $value, $options); // Make the cookie immediately available server-side. if ($result && $options['expires'] >= 0) diff --git a/common/framework/Session.php b/common/framework/Session.php index c1e92a067..e88de76d3 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -79,17 +79,7 @@ class Session ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); ini_set('session.use_strict_mode', 1); - if ($samesite) - { - if (PHP_VERSION_ID >= 70300) - { - ini_set('session.cookie_samesite', $samesite); - } - else - { - $path = ($path ?: '/') . '; SameSite=' . $samesite; - } - } + ini_set('session.cookie_samesite', $samesite ? 1 : 0); session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly); session_name($session_name = Config::get('session.name') ?: session_name()); From 7e142722d464bac5c34e4b5a96e7cc46a0e4a1ad Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Tue, 19 Nov 2024 00:00:45 +0900 Subject: [PATCH 45/51] Remove polyfill for is_countable() --- common/functions.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/common/functions.php b/common/functions.php index ae2616abe..d2b2d0d23 100644 --- a/common/functions.php +++ b/common/functions.php @@ -740,20 +740,6 @@ function is_empty_html_content($str): bool return $str === ''; } -/** - * Polyfill for is_countable() in PHP < 7.3 - * - * @param mixed $var - * @return bool -**/ -if (!function_exists('is_countable')) -{ - function is_countable($var) - { - return is_array($var) || $var instanceof Countable; - } -} - /** * Polyfill for str_starts_with() in PHP < 8.0 * From f7038ebde52e9258a73adac730b722f754b7ebb1 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Tue, 19 Nov 2024 00:04:02 +0900 Subject: [PATCH 46/51] Remove PHP 7.2 and 7.3 from test matrix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd44c0340..bed5e2e3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ jobs: strategy: fail-fast: false matrix: - php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] + php: [ '7.4', '8.0', '8.1', '8.2', '8.3' ] name: PHP ${{ matrix.php }} steps: From 84e57ff87683b4f02cce62d6b0235546bdd20fa7 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Tue, 19 Nov 2024 00:52:51 +0900 Subject: [PATCH 47/51] Update minimum PHP version in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b85c5ca17..b892e26e1 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Rhymix는 개발자와 사용자가 서로의 권리와 책임을 존중하는 ### 설치 환경 -Rhymix를 사용하려면 PHP 7.2.5 이상, MySQL 또는 MariaDB가 필요합니다. +Rhymix를 사용하려면 PHP 7.4 이상, MySQL 또는 MariaDB가 필요합니다. 자세한 설치 환경은 [매뉴얼](https://rhymix.org/manual/introduction/requirements)을 참고하십시오. ### 개발 참여 @@ -108,7 +108,7 @@ This requires the most convenience for the average user over any other CMS. ### Installation Environment -Rhymix requires PHP 7.2.5 or higher, and MySQL or MariaDB. +Rhymix requires PHP 7.4 or higher, and MySQL or MariaDB. Please see the [online manual](https://rhymix.org/manual/introduction/requirements) for more information on server requirements. ### Participation in Development From d3b3dc7b0e720ce6d3f5a53d32ec58331d19d113 Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Wed, 20 Nov 2024 14:03:22 +0900 Subject: [PATCH 48/51] Improve compatibility with old DB versions that can't take current_timestamp() as a default value for DATETIME column --- common/framework/drivers/queue/db.php | 4 ++-- modules/module/schemas/task_queue.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/framework/drivers/queue/db.php b/common/framework/drivers/queue/db.php index c45eedc5d..a55bd71ad 100644 --- a/common/framework/drivers/queue/db.php +++ b/common/framework/drivers/queue/db.php @@ -93,8 +93,8 @@ class DB implements QueueInterface public function addTask(string $handler, ?object $args = null, ?object $options = null): int { $oDB = RFDB::getInstance(); - $stmt = $oDB->prepare('INSERT INTO task_queue (handler, args, options) VALUES (?, ?, ?)'); - $result = $stmt->execute([$handler, serialize($args), serialize($options)]); + $stmt = $oDB->prepare('INSERT INTO task_queue (handler, args, options, regdate) VALUES (?, ?, ?, ?)'); + $result = $stmt->execute([$handler, serialize($args), serialize($options), date('Y-m-d H:i:s')]); return $result ? $oDB->getInsertID() : 0; } diff --git a/modules/module/schemas/task_queue.xml b/modules/module/schemas/task_queue.xml index 73e18ab33..0a0d4cd18 100644 --- a/modules/module/schemas/task_queue.xml +++ b/modules/module/schemas/task_queue.xml @@ -3,5 +3,5 @@ <column name="handler" type="varchar" size="191" notnull="notnull" /> <column name="args" type="longtext" notnull="notnull" /> <column name="options" type="longtext" notnull="notnull" /> - <column name="regdate" type="datetime" notnull="notnull" default="current_timestamp()" index="idx_regdate" /> + <column name="regdate" type="datetime" notnull="notnull" index="idx_regdate" /> </table> From b6ba2cf685b3bd8146f79824720011ffb7ed7d74 Mon Sep 17 00:00:00 2001 From: dewekk <60457472+dewekk@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:23:13 +0900 Subject: [PATCH 49/51] =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=EA=B0=80=20=ED=99=95=EC=9E=A5=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AD=EC=A0=9C=20=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/document/conf/module.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/document/conf/module.xml b/modules/document/conf/module.xml index 608e364c7..b6e6105d2 100644 --- a/modules/document/conf/module.xml +++ b/modules/document/conf/module.xml @@ -47,7 +47,7 @@ <action name="procDocumentAdminMoveToTrash" type="controller" permission="manager:moderate:document" check_type="document" check_var="document_srl" /> <action name="procDocumentAdminRestoreTrash" type="controller" /> <action name="procDocumentAdminInsertExtraVar" type="controller" permission="manager:config:*" check_var="module_srl" ruleset="insertExtraVar" /> - <action name="procDocumentAdminDeleteExtraVar" type="controller" permission="manage:config:*" check_var="module_srl" /> + <action name="procDocumentAdminDeleteExtraVar" type="controller" permission="manager:config:*" check_var="module_srl" /> <action name="procDocumentAdminMoveExtraVar" type="controller" permission="manager:config:*" check_var="module_srl" /> </actions> <eventHandlers> From 697eb5d9090ad8cb17e8d37286e12b2ff554cc77 Mon Sep 17 00:00:00 2001 From: dewekk <60457472+dewekk@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:53:27 +0900 Subject: [PATCH 50/51] =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EC=84=A4=EC=A0=95=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 문서 관리 권한이 없음에도 모든 모듈 관리자의 isGranted() 값이 true로 지정되는 문제 --- modules/board/board.view.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/board/board.view.php b/modules/board/board.view.php index f665cba28..6400783a7 100644 --- a/modules/board/board.view.php +++ b/modules/board/board.view.php @@ -289,9 +289,6 @@ class BoardView extends Board } } - // check the manage grant - if($this->grant->manager) $oDocument->setGrant(); - // if the consultation function is enabled, and the document is not a notice if($this->consultation && !$oDocument->isNotice()) { From 87573ef8e4cf37727d109ff624b515ee93b827fd Mon Sep 17 00:00:00 2001 From: Kijin Sung <kijin@kijinsung.com> Date: Thu, 21 Nov 2024 23:07:24 +0900 Subject: [PATCH 51/51] Highlight warning about merged board permissions #2437 --- modules/board/lang/en.php | 2 +- modules/board/lang/ko.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/board/lang/en.php b/modules/board/lang/en.php index c26790353..e85952457 100644 --- a/modules/board/lang/en.php +++ b/modules/board/lang/en.php @@ -97,7 +97,7 @@ $lang->cmd_do_not_message = 'Never'; $lang->delete_placeholder = 'Delete Placeholder'; $lang->msg_document_notify_mail = '[%s] The new post : %s'; $lang->cmd_board_combined_board = 'Combined Board'; -$lang->about_board_combined_board = 'You can use this board to view documents from other boards. Press the Ctrl key and click to select multiple boards.<br />Caution: view permissions for the current board will apply to all affected documents.'; +$lang->about_board_combined_board = 'You can use this board to view documents from other boards. Press the Ctrl key and click to select multiple boards.<br /><span style="color:red">Warning: permissions for the current board will apply to all affected documents and comments.</span>'; $lang->cmd_board_include_modules = 'Include Boards'; $lang->cmd_board_include_modules_none = '(None)'; $lang->cmd_board_include_days = 'Include Duration'; diff --git a/modules/board/lang/ko.php b/modules/board/lang/ko.php index 778004a5f..5f73c5afa 100644 --- a/modules/board/lang/ko.php +++ b/modules/board/lang/ko.php @@ -109,7 +109,7 @@ $lang->cmd_document_vote_user = '이 글의 추천인 목록'; $lang->cmd_comment_vote_user = '이 댓글의 추천인 목록'; $lang->msg_not_target = '문서 또는 댓글의 추천인목록만 조회가능합니다.'; $lang->cmd_board_combined_board = '통합 게시판'; -$lang->about_board_combined_board = '다른 게시판의 글을 모아서 볼 수 있습니다. 여러 게시판을 선택하려면 Ctrl 키를 누르고 클릭하세요.<br />주의: 현재 게시판의 읽기 권한 설정이 모든 글에 적용됩니다.'; +$lang->about_board_combined_board = '다른 게시판의 글을 모아서 볼 수 있습니다. 여러 게시판을 선택하려면 Ctrl 키를 누르고 클릭하세요.<br /><span style="color:red">주의: 현재 게시판의 권한 설정이 모든 글에 적용됩니다.</span>'; $lang->cmd_board_include_modules = '포함할 게시판 선택'; $lang->cmd_board_include_modules_none = '(포함하지 않음)'; $lang->cmd_board_include_days = '포함할 기간';