Merge branch 'master' of github.com:Lastorder-DC/rhymix

This commit is contained in:
Lastorder-DC 2026-02-27 18:04:51 +09:00
commit 89bab34d8b
35 changed files with 964 additions and 1195 deletions

View file

@ -7,8 +7,8 @@ class HTMLDisplayHandler
*/
public const JQUERY_V2 = '2.2.4';
public const JQUERY_V2_MIGRATE = '1.4.1';
public const JQUERY_V3 = '3.6.3';
public const JQUERY_V3_MIGRATE = '3.4.0';
public const JQUERY_V3 = '3.7.1';
public const JQUERY_V3_MIGRATE = '3.6.0';
/**
* Default viewport setting
@ -746,7 +746,8 @@ class HTMLDisplayHandler
*/
private function _loadCommonJSCSS()
{
if (config('view.jquery_version') === 3)
$jquery_version = config('view.jquery_version') ?: 2;
if ($jquery_version == 3)
{
$jquery_version = self::JQUERY_V3;
$jquery_migrate_version = self::JQUERY_V3_MIGRATE;

View file

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

View file

@ -90,7 +90,7 @@ return array(
'minify_scripts' => 'common',
'concat_scripts' => 'none',
'delay_compile' => 0,
'jquery_version' => 2,
'jquery_version' => 3,
),
'admin' => array(
'allow' => array(),

View file

@ -38,6 +38,12 @@ class Security
if (!utf8_check($input)) return false;
return Filters\FilenameFilter::clean($input);
// Clean up SVG content to prevent various attacks.
case 'svg':
if (!utf8_check($input)) return false;
$sanitizer = new \enshrined\svgSanitize\Sanitizer();
return strval($sanitizer->sanitize($input));
// Unknown filters.
default:
throw new Exception('Unknown filter type for sanitize: ' . $type);

View file

@ -44,7 +44,7 @@ class FileContentFilter
$skip_xml = preg_match('/^(hwpx)$/', $ext);
// Check SVG files.
if (($ext === 'svg' || $is_xml) && !self::_checkSVG($fp, 0, $filesize))
if (($ext === 'svg' || $is_xml) && !self::_checkSVG($fp, 0, $filesize, $ext))
{
fclose($fp);
return false;
@ -89,11 +89,12 @@ class FileContentFilter
* @param resource $fp
* @param int $from
* @param int $to
* @param string $ext
* @return bool
*/
protected static function _checkSVG($fp, $from, $to)
protected static function _checkSVG($fp, $from, $to, $ext)
{
if (self::_matchStream('/(?:<|&lt;)(?:script|iframe|foreignObject|object|embed|handler)|javascript:|xlink:href\s*=\s*"(?!data:)/i', $fp, $from, $to))
if (self::_matchStream('/(?:<|&lt;|:)(?:script|iframe|foreignObject|object|embed|handler)|javascript:|(?:\s|:)href\s*=\s*"(?!data:)/i', $fp, $from, $to))
{
return false;
}

View file

@ -184,6 +184,8 @@ class ConfigParser
if (isset($db_info->use_ssl) && in_array($db_info->use_ssl, ['always', 'optional']))
{
$config['url']['ssl'] = 'always';
$config['session']['use_ssl'] = true;
$config['session']['use_ssl_cookies'] = true;
}
else
{

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

2
common/js/jquery-3.7.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -359,6 +359,7 @@ $lang->filter['invalid_alpha_number'] = 'The format of %s is invalid. Please ent
$lang->filter['invalid_mid'] = 'The format of %s is invalid. Module ID should be begun with a letter. Subsequent characters may be letters, digits or underscore characters.';
$lang->filter['invalid_number'] = 'The format of %s is invalid. Please enter numbers only.';
$lang->filter['invalid_float'] = 'The format of %s is invalid. Please enter numbers only.';
$lang->filter['invalid_file'] = 'The value of %s is not a valid file upload.';
$lang->filter['invalid_extension'] = 'The format of %s is invalid. e.g. gif, jpg, png';
$lang->security_warning_embed = 'Due to security concern, administrators are not allowed to view embedded items.<BR /> To view them, please use another non-administrator ID.';
$lang->msg_pc_to_mobile = 'View mobile optimized version of this page';

View file

@ -359,6 +359,7 @@ $lang->filter['invalid_alpha_number'] = '%s의 형식이 잘못되었습니다.
$lang->filter['invalid_mid'] = '%s의 형식이 잘못되었습니다. 첫 글자는 영문으로 시작해야 하며 \'영문+숫자+_\'로만 입력해야 합니다.';
$lang->filter['invalid_number'] = '%s의 형식이 잘못되었습니다. 숫자로만 입력해야 합니다.';
$lang->filter['invalid_float'] = '%s의 형식이 잘못되었습니다. 숫자로만 입력해야 합니다.';
$lang->filter['invalid_file'] = '%s의 값은 올바르게 업로드된 파일이 아닙니다.';
$lang->filter['invalid_extension'] = '%s의 형식이 잘못되었습니다. gif, jpg, png 등 쉼표로 구분하여 입력해야 합니다.';
$lang->security_invalid_session = '바르지 않은 접근입니다. 인증을 위해 다시 로그인해야 합니다.';
$lang->security_warning_embed = '보안 문제로 관리자 아이디로는 embed를 볼 수 없습니다. 확인하려면 다른 아이디로 접속하세요';

View file

@ -159,7 +159,7 @@ $lang->cmd_concat_js_only = 'Combine all JS';
$lang->cmd_concat_css_js = 'Combine both CSS and JS';
$lang->about_concat_scripts = 'Automatically combine CSS and JS scripts into as few files as possible. External scripts are not combined.';
$lang->jquery_version = 'jQuery Version';
$lang->about_jquery_version = 'You can select the default jQuery version for this site. Please note that jQuery 3.x may not be compatible with older features.';
$lang->about_jquery_version = 'You can select the default jQuery version for this site. Older third-party programs may require 2.x, but Rhymix recommends 3.x or higher for security.';
$lang->use_gzip = 'gzip Compression';
$lang->about_use_gzip = 'This option should be left off unless you know for sure that your webserver doesn\'t compress output by default.';
$lang->delay_session = 'Delay session start';

View file

@ -160,7 +160,7 @@ $lang->cmd_concat_js_only = 'JS만 합침';
$lang->cmd_concat_css_js = 'CSS와 JS를 모두 합침';
$lang->about_concat_scripts = 'CSS, JS 파일들을 하나로 합쳐서 전송합니다. 외부에서 로딩하는 스크립트는 합쳐지지 않습니다.';
$lang->jquery_version = 'jQuery 버전';
$lang->about_jquery_version = '기본으로 사용할 jQuery 버전을 선택합니다. jQuery 3.x는 오래된 기능과 호환되지 않을 수 있습니다.';
$lang->about_jquery_version = '기본으로 사용할 jQuery 버전을 선택합니다. 오래된 확장 기능은 jQuery 2.x를 요구할 수 있으나, 보안상 3.x 이상을 추천합니다.';
$lang->use_gzip = 'gzip 압축';
$lang->about_use_gzip = '웹서버가 gzip을 지원하지 않더라도 페이지를 강제로 압축하는 기능입니다. 대부분의 서버에는 필요하지 않습니다.';
$lang->delay_session = '세션 시작 지연';

View file

@ -58,7 +58,7 @@ class BoardAdminView extends Board {
// install order (sorting) options
foreach($this->order_target as $key) $order_target[$key] = lang($key);
$order_target['list_order'] = lang('document_srl');
$order_target['list_order'] = lang('default_value');
$order_target['update_order'] = lang('last_update');
Context::set('order_target', $order_target);
}

View file

@ -907,7 +907,7 @@ class DocumentController extends Document
}
// Handle extra vars that support file upload.
if ($extra_item->type === 'file' && is_array($value))
if ($extra_item->type === 'file' && $value)
{
$ev_output = $extra_item->uploadFile($value, $obj->document_srl, 'doc');
if (!$ev_output->toBool())
@ -1305,16 +1305,20 @@ class DocumentController extends Document
if ($extra_item->type === 'file')
{
// New upload
if (is_array($value) && isset($value['name']))
if (is_array($value) && isset($value['tmp_name']))
{
// Delete old file
if (isset($old_extra_vars[$idx]->value))
{
$fc_output = FileController::getInstance()->deleteFile($old_extra_vars[$idx]->value);
if (!$fc_output->toBool())
$old_file = FileModel::getFile($old_extra_vars[$idx]->value);
if ($old_file && $old_file->upload_target_srl == $obj->document_srl)
{
$oDB->rollback();
return $fc_output;
$fc_output = FileController::getInstance()->deleteFile($old_file->file_srl);
if (!$fc_output->toBool())
{
$oDB->rollback();
return $fc_output;
}
}
}
// Insert new file
@ -1339,21 +1343,22 @@ class DocumentController extends Document
return $ev_output;
}
// Delete old file
$fc_output = FileController::getInstance()->deleteFile($old_extra_vars[$idx]->value);
if (!$fc_output->toBool())
$old_file = FileModel::getFile($old_extra_vars[$idx]->value);
if ($old_file && $old_file->upload_target_srl == $obj->document_srl)
{
$oDB->rollback();
return $fc_output;
$fc_output = FileController::getInstance()->deleteFile($old_file->file_srl);
if (!$fc_output->toBool())
{
$oDB->rollback();
return $fc_output;
}
}
}
}
// Leave current file unchanged
elseif (!$value)
elseif (isset($old_extra_vars[$idx]->value))
{
if (isset($old_extra_vars[$idx]->value))
{
$value = $old_extra_vars[$idx]->value;
}
$value = $old_extra_vars[$idx]->value;
}
}
}

View file

@ -1650,6 +1650,10 @@ class DocumentItem extends BaseObject
return ModuleModel::getModuleInfoByModuleSrl($this->get('module_srl'))->browser_title;
}
/**
* Get the title of the module to which the document belongs.
* @return string
*/
function getBrowserTitle()
{
return $this->getModuleName();

View file

@ -111,6 +111,7 @@ class DocumentModel extends Document
foreach($GLOBALS['XE_EXTRA_KEYS'][$module_srl] as $idx => $key)
{
$document_extra_vars[$idx] = clone($key);
$document_extra_vars[$idx]->parent_srl = $document_srl;
// set variable value in user language
if(isset($document_extra_values[$idx][$user_lang_code]))

View file

@ -25,6 +25,7 @@ class Value
public $input_id = '';
public $input_name = '';
public $parent_type = 'document';
public $parent_srl = null;
public $type = 'text';
public $value = null;
public $name = '';
@ -159,7 +160,7 @@ class Value
*/
public function getValueHTML(): string
{
return self::_getTypeValueHTML($this->type, $this->value);
return self::_getTypeValueHTML($this->type, $this->value, $this->parent_type, $this->parent_srl);
}
/**
@ -280,7 +281,7 @@ class Value
$values = [$value];
}
// Check if a required value is empty.
// Check that a required value is not empty.
if ($this->is_required === 'Y')
{
if ($this->type === 'file' && !$value && $old_value)
@ -298,7 +299,7 @@ class Value
}
}
// Check if a strict value is not one of the specified options.
// Check that a strict value equals one of the specified options.
if ($this->is_strict === 'Y' && $value)
{
if ($this->canHaveOptions())
@ -321,6 +322,15 @@ class Value
}
}
// Check that a file value is actually an uploaded file.
if ($this->type === 'file' && $value)
{
if (!isset($value['tmp_name']) || !is_uploaded_file($value['tmp_name']))
{
return new BaseObject(-1, sprintf(lang('common.filter.invalid_file'), Context::replaceUserLang($this->name)));
}
}
return new BaseObject;
}
@ -442,9 +452,11 @@ class Value
*
* @param string $type
* @param string|array $value
* @param string $parent_type
* @param ?int $parent_srl
* @return string
*/
protected static function _getTypeValueHTML(string $type, $value): string
protected static function _getTypeValueHTML(string $type, $value, string $parent_type, ?int $parent_srl = null): string
{
// Return if the value is empty.
$value = self::_getTypeValue($type, $value);
@ -511,10 +523,14 @@ class Value
if ($value)
{
$file = FileModel::getFile($value);
if ($file)
if ($file && $file->upload_target_srl == $parent_srl)
{
return sprintf('<span><a href="%s">%s</a> (%s)</span>', \RX_BASEURL . ltrim($file->download_url, './'), $file->source_filename, FileHandler::filesize($file->file_size));
}
elseif ($file)
{
return sprintf('<span>%s (%s)</span>', $file->source_filename, FileHandler::filesize($file->file_size));
}
else
{
return '';

View file

@ -551,7 +551,7 @@ class FileController extends File
{
$download_type = 'inline';
}
if (Context::get('force_download') === 'Y')
if ($mime_type === 'image/svg+xml' || Context::get('force_download') === 'Y')
{
$download_type = 'attachment';
}
@ -936,6 +936,14 @@ class FileController extends File
}
}
// Sanitize SVG
if(!$manual_insert && !$this->user->isAdmin() && ($file_info['type'] === 'image/svg+xml' || $file_info['extension'] === 'svg'))
{
$dirty_svg = Rhymix\Framework\Storage::read($file_info['tmp_name']);
$clean_svg = Rhymix\Framework\Security::sanitize($dirty_svg, 'svg');
Rhymix\Framework\Storage::write($file_info['tmp_name'], $clean_svg);
}
// Adjust
if(!$manual_insert)
{

View file

@ -204,6 +204,13 @@ class installController extends install
// Set the default umask.
$config['file']['umask'] = Rhymix\Framework\Storage::recommendUmask();
// Set default security settings.
if ($config['url']['ssl'] === 'always')
{
$config['session']['use_ssl'] = true;
$config['session']['use_ssl_cookies'] = true;
}
// Load the new configuration.
Rhymix\Framework\Config::setAll($config);
Context::loadDBInfo($config);

View file

@ -28,6 +28,11 @@ class installView extends install
// Specify the template path.
$this->setTemplatePath($this->module_path.'tpl');
// Set default frontend configurations.
config('view.jquery_version', 3);
config('view.minify_scripts', 'none');
config('view.concat_scripts', 'none');
// Check the environment.
$oInstallController = getController('install');
self::$checkEnv = $oInstallController->checkInstallEnv();

View file

@ -81,11 +81,7 @@ class LayoutModel extends Layout
}
$token = explode('|@|', $val->layout);
if(count($token) == 2)
{
$thumbnailPath = sprintf('./themes/%s/layouts/%s/thumbnail.png' , $token[0], $token[1]);
}
else if($layoutType == 'M')
if($layoutType == 'M')
{
$thumbnailPath = sprintf('./m.layouts/%s/thumbnail.png' , $val->layout);
}
@ -227,26 +223,13 @@ class LayoutModel extends Layout
*/
public static function isExistsLayoutFile($layout, $layoutType)
{
//TODO If remove a support themes, remove this codes also.
if($layoutType == 'P')
{
$pathPrefix = RX_BASEDIR . 'layouts/';
$themePathFormat = RX_BASEDIR . 'themes/%s/layouts/%s';
$path = RX_BASEDIR . 'layouts/' . $layout;
}
else
{
$pathPrefix = RX_BASEDIR . 'm.layouts/';
$themePathFormat = RX_BASEDIR . 'themes/%s/m.layouts/%s';
}
if(strpos($layout, '|@|') !== FALSE)
{
list($themeName, $layoutName) = explode('|@|', $layout);
$path = sprintf($themePathFormat, $themeName, $layoutName);
}
else
{
$path = $pathPrefix . $layout;
$path = RX_BASEDIR . 'm.layouts/' . $layout;
}
if (file_exists($path . '/layout.html') && is_readable($path . '/layout.html'))
@ -333,12 +316,7 @@ class LayoutModel extends Layout
*/
public function getLayoutPath($layout_name = "", $layout_type = "P")
{
$layout_parse = explode('|@|', $layout_name ?? '');
if(count($layout_parse) > 1)
{
$class_path = './themes/'.$layout_parse[0].'/layouts/'.$layout_parse[1].'/';
}
else if($layout_name == 'faceoff')
if($layout_name == 'faceoff')
{
$class_path = './modules/layout/faceoff/';
}

View file

@ -114,6 +114,7 @@ $lang->search_target_list['last_login_less'] = 'Last Login Date (less)';
$lang->search_target_list['last_login_ipaddress'] = 'Last Login IP address';
$lang->search_target_list['birthday'] = 'Birthday';
$lang->search_target_list['extra_vars'] = 'User Defined';
$lang->search_target_list['description'] = 'Admin Memo';
$lang->cmd_modify_new_auth_email_address = 'New email address';
$lang->cmd_set_design_info = 'Desgin';
$lang->cmd_login = 'Login';

View file

@ -116,6 +116,7 @@ $lang->search_target_list['last_login_less'] = '최근 로그인 일시(이하)'
$lang->search_target_list['last_login_ipaddress'] = '최근 로그인 IP 주소';
$lang->search_target_list['birthday'] = '생일';
$lang->search_target_list['extra_vars'] = '사용자 정의';
$lang->search_target_list['description'] = '관리자 메모';
$lang->cmd_modify_new_auth_email_address = '신규 메일 주소로 변경 후 인증 메일 발송';
$lang->cmd_set_design_info = '디자인';
$lang->cmd_login = '로그인';

View file

@ -121,6 +121,9 @@ class MemberAdminModel extends Member
case 'extra_vars' :
$args->s_extra_vars = $search_keyword;
break;
case 'description' :
$args->s_description = $search_keyword;
break;
}
}

View file

@ -19,6 +19,7 @@
<condition operation="like" column="phone_number" var="s_phone_number" pipe="or" />
<condition operation="like" column="birthday" var="s_birthday" pipe="or" />
<condition operation="like" column="extra_vars" var="s_extra_vars" pipe="or" />
<condition operation="like" column="description" var="s_description" pipe="or" />
<condition operation="like_prefix" column="regdate" var="s_regdate" pipe="or" />
<condition operation="like_prefix" column="ipaddress" var="s_ipaddress" pipe="or" />
<condition operation="like_prefix" column="last_login" var="s_last_login" pipe="or" />

View file

@ -22,6 +22,7 @@
<condition operation="like" column="member.phone_number" var="s_phone_number" pipe="or" />
<condition operation="like" column="member.birthday" var="s_birthday" pipe="or" />
<condition operation="like" column="member.extra_vars" var="s_extra_vars" pipe="or" />
<condition operation="like" column="member.description" var="s_description" pipe="or" />
<condition operation="like_prefix" column="member.regdate" var="s_regdate" pipe="or" />
<condition operation="like_prefix" column="member.ipaddress" var="s_ipaddress" pipe="or" />
<condition operation="like_prefix" column="member.last_login" var="s_last_login" pipe="or" />

View file

@ -51,23 +51,14 @@ class MenuAdminView extends Menu
$resultModuleList = $oMenuAdminModel->getModuleListInSitemap($site_srl);
Context::set('module_list', $resultModuleList);
// Get installed layout list
$oLayoutModel = getModel('layout');
$layoutList = $oLayoutModel->getLayoutList();
Context::set('layout_list', $layoutList);
// choice theme file
$theme_file = RX_BASEDIR.'files/theme/theme_info.php';
if(is_readable($theme_file))
{
include($theme_file);
Context::set('current_layout', $theme_info->layout);
}
else
{
$oModuleModel = getModel('module');
$default_mid = $oModuleModel->getDefaultMid();
Context::set('current_layout', $default_mid->layout_srl);
}
// Get current layout information
$default_mid = ModuleModel::getDefaultMid();
Context::set('current_layout', $default_mid->layout_srl);
// get default group list
$oMemberModel = getModel('member');

View file

@ -175,7 +175,7 @@ class PageAdminController extends Page
$oDocumentController = getController('document');
$obj = new stdClass();
$obj->module_srl = $module_srl;
$obj->list_count = 99999999;
$obj->list_count = 0;
$output = $oDocumentModel->getDocumentList($obj);
if(count($output->data))
{

View file

@ -19,16 +19,17 @@ class PageMobile extends PageView
Context::set('document_srl', $document_srl);
}
Context::set('oDocument', $oDocument);
Context::set('page_content', $oDocument->getContent(false, false));
$oTemplate = Rhymix\Framework\Template::getInstance();
$template_path = $this->getTemplatePath();
$template_path = $this->getTemplatePath() ?: ($this->module_path . 'tpl');
if (preg_match('!/skins/!', $template_path))
{
$page_content = $oTemplate->compile($this->getTemplatePath(), 'content');
$page_content = $oTemplate->compile($template_path, 'content');
}
else
{
$page_content = $oTemplate->compile($this->getTemplatePath(), 'mobile');
$page_content = $oTemplate->compile($template_path, 'mobile');
}
return $page_content;

View file

@ -6,7 +6,7 @@
<param name="module_srl" target="module_srl" />
<param name="mid" target="mid" />
<param name="mcontent" target="content" />
<parma name="type" target="type" />
<param name="type" target="type" />
</parameter>
<response callback_func="completeInsertMobilePageContent">
<tag name="error" />

View file

@ -18,5 +18,6 @@ v.cast('ADD_MESSAGE',['invalid_alpha_number','%s의 형식이 잘못되었습니
v.cast('ADD_MESSAGE',['invalid_mid','%s의 형식이 잘못되었습니다. 첫 글자는 영문으로 시작해야 하며 \'영문+숫자+_\'로만 입력해야 합니다.']);
v.cast('ADD_MESSAGE',['invalid_number','%s의 형식이 잘못되었습니다. 숫자로만 입력해야 합니다.']);
v.cast('ADD_MESSAGE',['invalid_float','%s의 형식이 잘못되었습니다. 숫자로만 입력해야 합니다.']);
v.cast('ADD_MESSAGE',['invalid_file','%s의 값은 올바르게 업로드된 파일이 아닙니다.']);
v.cast('ADD_MESSAGE',['invalid_extension','%s의 형식이 잘못되었습니다. gif, jpg, png 등 쉼표로 구분하여 입력해야 합니다.']);
})(jQuery);

View file

@ -15,6 +15,16 @@ class SecurityTest extends \Codeception\Test\Unit
// Filename (more thorough tests in FilenameFilterTest)
$this->assertEquals('foo(bar).xls', Rhymix\Framework\Security::sanitize('foo<bar>.xls', 'filename'));
// SVG #1
$source = '<svg><rect><a href="javascript:alert(0)">Test</a></rect></svg>';
$target = '<?xml version="1.0" encoding="UTF-8"?>' . "\n<svg>\n <rect>\n <a>Test</a>\n </rect>\n</svg>\n";
$this->assertEquals($target, Rhymix\Framework\Security::sanitize($source, 'svg'));
// SVG #2
$source = '<svg><rect></rect><script></script></svg>';
$target = '<?xml version="1.0" encoding="UTF-8"?>' . "\n<svg>\n <rect></rect>\n</svg>\n";
$this->assertEquals($target, Rhymix\Framework\Security::sanitize($source, 'svg'));
}
public function testEncryption()