From 1cd3cfc7d9dd5226dbae2b89a4b3911f83c2bdb7 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 13:24:01 +0900 Subject: [PATCH 01/71] Support different rewrite levels for XE compatibility --- common/defaults/config.php | 1 + common/framework/router.php | 28 ++++++++++++++++++++++++ modules/admin/admin.admin.controller.php | 3 ++- modules/admin/admin.admin.view.php | 2 +- modules/admin/lang/en.php | 5 ++++- modules/admin/lang/ko.php | 3 +++ modules/admin/tpl/config_advanced.html | 5 +++-- 7 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 common/framework/router.php diff --git a/common/defaults/config.php b/common/defaults/config.php index abc96e02a..7972452de 100644 --- a/common/defaults/config.php +++ b/common/defaults/config.php @@ -50,6 +50,7 @@ return array( 'http_port' => null, 'https_port' => null, 'ssl' => 'none', + 'rewrite' => 1, ), 'session' => array( 'delay' => false, diff --git a/common/framework/router.php b/common/framework/router.php new file mode 100644 index 000000000..8449d0c13 --- /dev/null +++ b/common/framework/router.php @@ -0,0 +1,28 @@ +default_timezone); // Other settings - Rhymix\Framework\Config::set('use_rewrite', $vars->use_rewrite === 'Y'); + Rhymix\Framework\Config::set('url.rewrite', intval($vars->use_rewrite)); + Rhymix\Framework\Config::set('use_rewrite', $vars->use_rewrite > 0); Rhymix\Framework\Config::set('session.delay', $vars->delay_session === 'Y'); Rhymix\Framework\Config::set('session.use_db', $vars->use_db_session === 'Y'); Rhymix\Framework\Config::set('view.manager_layout', $vars->manager_layout ?: 'module'); diff --git a/modules/admin/admin.admin.view.php b/modules/admin/admin.admin.view.php index bc6967233..98eeeab16 100644 --- a/modules/admin/admin.admin.view.php +++ b/modules/admin/admin.admin.view.php @@ -544,7 +544,7 @@ class adminAdminView extends admin Context::set('selected_timezone', Rhymix\Framework\Config::get('locale.default_timezone')); // Other settings - Context::set('use_rewrite', Rhymix\Framework\Config::get('use_rewrite')); + Context::set('use_rewrite', Rhymix\Framework\Router::getRewriteLevel()); Context::set('use_mobile_view', (config('mobile.enabled') !== null ? config('mobile.enabled') : config('use_mobile_view')) ? true : false); Context::set('tablets_as_mobile', config('mobile.tablets') ? true : false); Context::set('mobile_viewport', config('mobile.viewport') ?: 'width=device-width, initial-scale=1.0, user-scalable=yes'); diff --git a/modules/admin/lang/en.php b/modules/admin/lang/en.php index 2649d5e7e..c30fea8a5 100644 --- a/modules/admin/lang/en.php +++ b/modules/admin/lang/en.php @@ -264,7 +264,10 @@ $lang->trash = 'Recycle Bin'; $lang->accusation = 'Report'; $lang->status = 'Status'; $lang->action = 'Execute'; -$lang->use_rewrite = 'Use Rewrite Mode'; +$lang->use_rewrite = 'Use Short URLs'; +$lang->use_rewrite_0 = 'None'; +$lang->use_rewrite_1 = 'XE-compatible URLs only'; +$lang->use_rewrite_2 = 'All supported URLs'; $lang->timezone = 'Time Zone'; $lang->use_mobile_view = 'Enable Mobile View'; $lang->about_use_mobile_view = 'Show mobile page when visitors access with mobile devices.'; diff --git a/modules/admin/lang/ko.php b/modules/admin/lang/ko.php index 7aa1e75f6..2c68f3645 100644 --- a/modules/admin/lang/ko.php +++ b/modules/admin/lang/ko.php @@ -261,6 +261,9 @@ $lang->accusation = '신고'; $lang->status = '상태'; $lang->action = '실행'; $lang->use_rewrite = '짧은 주소 사용'; +$lang->use_rewrite_0 = '사용하지 않음'; +$lang->use_rewrite_1 = 'XE와 호환되는 주소 형태만 사용'; +$lang->use_rewrite_2 = '모든 주소 형태를 사용'; $lang->timezone = '표준 시간대'; $lang->use_mobile_view = '모바일 뷰 사용'; $lang->about_use_mobile_view = '모바일 기기로 접속시 모바일 페이지를 보여줍니다.'; diff --git a/modules/admin/tpl/config_advanced.html b/modules/admin/tpl/config_advanced.html index df1d9125f..8d1d77abf 100644 --- a/modules/admin/tpl/config_advanced.html +++ b/modules/admin/tpl/config_advanced.html @@ -13,8 +13,9 @@
- - + + +
From baddbd3cba41289784c043af9ec8ea45a4a47d15 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 14:16:36 +0900 Subject: [PATCH 02/71] Support XE-compatible rewrite rules --- .htaccess | 31 +--------- classes/context/Context.class.php | 16 +++-- common/framework/router.php | 60 +++++++++++++++++++ .../server_config/rhymix-nginx-subdir.conf | 32 +--------- common/manual/server_config/rhymix-nginx.conf | 32 +--------- 5 files changed, 81 insertions(+), 90 deletions(-) diff --git a/.htaccess b/.htaccess index ae3580590..71d1f48ee 100644 --- a/.htaccess +++ b/.htaccess @@ -13,32 +13,7 @@ RewriteRule ^(.+)/(addons|files|layouts|m\.layouts|modules|widgets|widgetstyles) RewriteCond %{SCRIPT_FILENAME} !-f RewriteRule ^(.+)\.min\.(css|js)$ ./$1.$2 [L] -# rss, blogAPI -RewriteRule ^(rss|atom)$ ./index.php?module=rss&act=$1 [L] -RewriteRule ^([a-zA-Z0-9_]+)/(rss|atom|api)$ ./index.php?mid=$1&act=$2 [L] - -# trackback -RewriteRule ^([0-9]+)/(.+)/trackback$ ./index.php?document_srl=$1&key=$2&act=trackback [L] -RewriteRule ^([a-zA-Z0-9_]+)/([0-9]+)/(.+)/trackback$ ./index.php?mid=$1&document_srl=$2&key=$3&act=trackback [L] - -# document category -RewriteRule ^([a-zA-Z0-9_]+)/category/([0-9]+)$ ./index.php?mid=$1&category=$2 [L,QSA] - -# document permanent link -RewriteRule ^([0-9]+)$ ./index.php?document_srl=$1 [L,QSA] - -# admin module link -RewriteRule ^admin/?$ ./index.php?module=admin [L,QSA] - -# mid link +# all other short URLs +RewriteCond %{SCRIPT_FILENAME} !-f RewriteCond %{SCRIPT_FILENAME} !-d -RewriteRule ^([a-zA-Z0-9_]+)/?$ ./index.php?mid=$1 [L,QSA] - -# mid + document link -RewriteRule ^([a-zA-Z0-9_]+)/([0-9]+)$ ./index.php?mid=$1&document_srl=$2 [L,QSA] - -# mid + entry title -RewriteRule ^([a-zA-Z0-9_]+)/entry/(.+)$ ./index.php?mid=$1&entry=$2 [L,QSA] - -# file download -RewriteRule ^files/download/([0-9]+)/([a-zA-Z0-9_-]+)/(.+)$ ./index.php?act=procFileOutput&file_srl=$1&file_key=$2&filename=$3 [L] +RewriteRule . index.php [L] diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index 02930c18f..e041f89b9 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -249,7 +249,7 @@ class Context // Set information about the current request. self::_checkGlobalVars(); self::setRequestMethod(); - self::setRequestArguments(); + self::setRequestArguments(Rhymix\Framework\Router::getRequestArguments()); self::setUploadInfo(); // Load system configuration. @@ -1185,9 +1185,9 @@ class Context * * @return void */ - public static function setRequestArguments() + public static function setRequestArguments($router_args = []) { - foreach($_REQUEST as $key => $val) + foreach($router_args ?: $_REQUEST as $key => $val) { if($val === '' || isset(self::$_reserved_keys[$key]) || self::get($key)) { @@ -1211,7 +1211,7 @@ class Context } // Set deprecated request parameters. - if(!$_POST && !empty($GLOBALS['HTTP_RAW_POST_DATA'])) + if($_SERVER['REQUEST_METHOD'] === 'POST' && !$_POST && !empty($GLOBALS['HTTP_RAW_POST_DATA'])) { if(self::getRequestMethod() === 'XMLRPC') { @@ -1231,6 +1231,10 @@ class Context foreach((array)$params as $key => $val) { + if (isset($router_args[$key])) + { + continue; + } $key = escape($key); $val = self::_filterXmlVars($key, $val); self::set($key, $val, true); @@ -1242,6 +1246,10 @@ class Context parse_str($GLOBALS['HTTP_RAW_POST_DATA'], $params); foreach($params as $key => $val) { + if (isset($router_args[$key])) + { + continue; + } $key = escape($key); $val = self::_filterRequestVar($key, $val); self::set($key, $val, true); diff --git a/common/framework/router.php b/common/framework/router.php index 8449d0c13..39138332e 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -7,6 +7,21 @@ namespace Rhymix\Framework; */ class Router { + /** + * List of XE-compatible rewrite rules. + */ + protected static $_xe_compatible_rules = array( + 'admin' => ['module' => 'admin'], + '(?rss|atom)' => [], + '(?[0-9]+)' => [], + '(?[a-zA-Z0-9_-]+)/?' => [], + '(?[a-zA-Z0-9_-]+)/(?[0-9]+)' => [], + '(?[a-zA-Z0-9_-]+)/category/(?[0-9]+)' => [], + '(?[a-zA-Z0-9_-]+)/entry/(?[^/]+)' => [], + '(?[a-zA-Z0-9_-]+)/(?rss|atom|api)' => [], + 'files/download/(?[0-9]+)/(?[a-zA-Z0-9_-]+)/(?[^/]+)' => ['act' => 'procFileOutput'], + ); + /** * Return the currently configured rewrite level. * @@ -25,4 +40,49 @@ class Router } return intval($level); } + + /** + * Extract request arguments from the current URL. + * + * @return array + */ + public static function getRequestArguments(): array + { + // Get the local part of the current URL. + $url = $_SERVER['REQUEST_URI']; + if (starts_with(\RX_BASEURL, $url)) + { + $url = substr($url, strlen(\RX_BASEURL)); + } + + // Separate additional arguments from the URL. + $args = array(); + $argstart = strpos($url, '?'); + if ($argstart !== false) + { + @parse_str(substr($url, $argstart + 1), $args); + $url = substr($url, 0, $argstart); + } + + // Decode the URL into plain UTF-8. + $url = urldecode($url); + if ($url === '' || (function_exists('mb_check_encoding') && !mb_check_encoding($url, 'UTF-8'))) + { + return array(); + } + + // Try XE-compatible rules. + foreach (self::$_xe_compatible_rules as $regexp => $additional_args) + { + if (preg_match('#^' . $regexp . '$#', $url, $matches)) + { + $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); + $allargs = array_merge($additional_args ?: [], $matches, $args ?: []); + return $allargs; + } + } + + // If no pattern matches, return an empty array. + return array(); + } } diff --git a/common/manual/server_config/rhymix-nginx-subdir.conf b/common/manual/server_config/rhymix-nginx-subdir.conf index 69b8ef0df..06d370827 100644 --- a/common/manual/server_config/rhymix-nginx-subdir.conf +++ b/common/manual/server_config/rhymix-nginx-subdir.conf @@ -25,33 +25,7 @@ location ~ ^/rhymix/(.+)\.min\.(css|js)$ { try_files $uri $uri/ /rhymix/$1.$2; } -# rss, blogAPI -rewrite ^/rhymix/(rss|atom)$ /rhymix/index.php?module=rss&act=$1 last; -rewrite ^/rhymix/([a-zA-Z0-9_]+)/(rss|atom|api)$ /rhymix/index.php?mid=$1&act=$2 last; - -# trackback -rewrite ^/rhymix/([0-9]+)/(.+)/trackback$ /rhymix/index.php?document_srl=$1&key=$2&act=trackback last; -rewrite ^/rhymix/([a-zA-Z0-9_]+)/([0-9]+)/(.+)/trackback$ /rhymix/index.php?mid=$1&document_srl=$2&key=$3&act=trackback last; - -# administrator page -rewrite ^/rhymix/admin/?$ /rhymix/index.php?module=admin last; - -# document category -rewrite ^/rhymix/([a-zA-Z0-9_]+)/category/([0-9]+)$ /rhymix/index.php?mid=$1&category=$2 last; - -# document permanent link -rewrite ^/rhymix/([0-9]+)$ /rhymix/index.php?document_srl=$1 last; - -# mid link -location ~ ^/rhymix/([a-zA-Z0-9_]+)/?$ { - try_files $uri $uri/ /rhymix/index.php?mid=$1; +# all other short URLs +location /rhymix/ { + try_files $uri $uri/ /rhymix/index.php$is_args$args; } - -# mid + document link -rewrite ^/rhymix/([a-zA-Z0-9_]+)/([0-9]+)$ /rhymix/index.php?mid=$1&document_srl=$2 last; - -# mid + entry title -rewrite ^/rhymix/([a-zA-Z0-9_]+)/entry/(.+)$ /rhymix/index.php?mid=$1&entry=$2 last; - -# file download -rewrite ^/rhymix/files/download/([0-9]+)/([a-zA-Z0-9_-]+)/(.+)$ /rhymix/index.php?act=procFileOutput&file_srl=$1&file_key=$2&filename=$3 last; diff --git a/common/manual/server_config/rhymix-nginx.conf b/common/manual/server_config/rhymix-nginx.conf index 11aed6f3f..04306e0bd 100644 --- a/common/manual/server_config/rhymix-nginx.conf +++ b/common/manual/server_config/rhymix-nginx.conf @@ -25,33 +25,7 @@ location ~ ^/(.+)\.min\.(css|js)$ { try_files $uri $uri/ /$1.$2; } -# rss, blogAPI -rewrite ^/(rss|atom)$ /index.php?module=rss&act=$1 last; -rewrite ^/([a-zA-Z0-9_]+)/(rss|atom|api)$ /index.php?mid=$1&act=$2 last; - -# trackback -rewrite ^/([0-9]+)/(.+)/trackback$ /index.php?document_srl=$1&key=$2&act=trackback last; -rewrite ^/([a-zA-Z0-9_]+)/([0-9]+)/(.+)/trackback$ /index.php?mid=$1&document_srl=$2&key=$3&act=trackback last; - -# administrator page -rewrite ^/admin/?$ /index.php?module=admin last; - -# document category -rewrite ^/([a-zA-Z0-9_]+)/category/([0-9]+)$ /index.php?mid=$1&category=$2 last; - -# document permanent link -rewrite ^/([0-9]+)$ /index.php?document_srl=$1 last; - -# mid link -location ~ ^/([a-zA-Z0-9_]+)/?$ { - try_files $uri $uri/ /index.php?mid=$1; +# all other short URLs +location / { + try_files $uri $uri/ /index.php$is_args$args; } - -# mid + document link -rewrite ^/([a-zA-Z0-9_]+)/([0-9]+)$ /index.php?mid=$1&document_srl=$2 last; - -# mid + entry title -rewrite ^/([a-zA-Z0-9_]+)/entry/(.+)$ /index.php?mid=$1&entry=$2 last; - -# file download -rewrite ^/files/download/([0-9]+)/([a-zA-Z0-9_-]+)/(.+)$ /index.php?act=procFileOutput&file_srl=$1&file_key=$2&filename=$3 last; From a680f8932686efa5006c1b28d3a0a66f4d5301f8 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 14:54:05 +0900 Subject: [PATCH 03/71] Adjust some parts of Context class for better integration with Router --- classes/context/Context.class.php | 32 ++++++++++++++----------------- common/framework/router.php | 14 +++++++++++++- modules/module/module.model.php | 2 +- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index e041f89b9..2bf76bda4 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -8,12 +8,6 @@ */ class Context { - /** - * Allow rewrite - * @var bool TRUE: using rewrite mod, FALSE: otherwise - */ - public $allow_rewrite = FALSE; - /** * Request method * @var string GET|POST|XMLRPC|JSON @@ -246,15 +240,15 @@ class Context self::$_instance = self::getInstance(); } + // Load system configuration. + self::loadDBInfo(); + // Set information about the current request. self::_checkGlobalVars(); self::setRequestMethod(); - self::setRequestArguments(Rhymix\Framework\Router::getRequestArguments()); + self::setRequestArguments(Rhymix\Framework\Router::getRequestArguments(Rhymix\Framework\Router::getRewriteLevel())); self::setUploadInfo(); - // Load system configuration. - self::loadDBInfo(); - // If Rhymix is installed, get virtual site information. if(self::isInstalled()) { @@ -266,8 +260,7 @@ class Context define('RX_BASEURL', parse_url($default_url, PHP_URL_PATH)); } } - $oModuleModel = ModuleModel::getInstance(); - $site_module_info = $oModuleModel->getDefaultMid() ?: new stdClass; + $site_module_info = ModuleModel::getDefaultMid() ?: new stdClass; self::set('site_module_info', $site_module_info); self::set('_default_timezone', ($site_module_info->settings && $site_module_info->settings->timezone) ? $site_module_info->settings->timezone : null); self::set('_default_url', self::$_instance->db_info->default_url = self::getDefaultUrl($site_module_info)); @@ -380,8 +373,6 @@ class Context // set authentication information in Context and session if (self::isInstalled()) { - $oModuleModel->loadModuleExtends(); - if (Rhymix\Framework\Session::getMemberSrl()) { MemberController::getInstance()->setSessionInfo(); @@ -507,7 +498,6 @@ class Context // Copy to old format for backward compatibility. self::$_instance->db_info = self::convertDBInfo($config); - self::$_instance->allow_rewrite = self::$_instance->db_info->use_rewrite === 'Y'; } /** @@ -1621,6 +1611,12 @@ class Context { static $current_domain = null; static $site_module_info = null; + static $rewrite_level = null; + if ($rewrite_level === null) + { + $rewrite_level = Rhymix\Framework\Router::getRewriteLevel(); + } + if ($site_module_info === null) { $site_module_info = self::get('site_module_info'); @@ -1730,7 +1726,7 @@ class Context if(count($get_vars) > 0) { // if using rewrite mod - if(self::$_instance->allow_rewrite) + if($rewrite_level) { $var_keys = array_keys($get_vars); sort($var_keys); @@ -2615,11 +2611,11 @@ class Context /** * Check whether it is allowed to use rewrite mod * - * @return bool True if it is allowed to use rewrite mod, otherwise FALSE + * @return int The currently configured rewrite level */ public static function isAllowRewrite() { - return self::$_instance->allow_rewrite; + return Rhymix\Framework\Router::getRewriteLevel(); } /** diff --git a/common/framework/router.php b/common/framework/router.php index 39138332e..afd9b208e 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -44,9 +44,10 @@ class Router /** * Extract request arguments from the current URL. * + * @param int $rewrite_level * @return array */ - public static function getRequestArguments(): array + public static function getRequestArguments(int $rewrite_level): array { // Get the local part of the current URL. $url = $_SERVER['REQUEST_URI']; @@ -71,6 +72,17 @@ class Router return array(); } + // Try to detect the prefix. This might be $mid. + if ($rewrite_level > 0 && preg_match('#^([a-zA-Z0-9_-]+)#', $url, $matches)) + { + $prefix = $matches[1]; + $module_info = \ModuleModel::getModuleInfoByMid($prefix); + if ($module_info && $module_info->module) + { + // TODO + } + } + // Try XE-compatible rules. foreach (self::$_xe_compatible_rules as $regexp => $additional_args) { diff --git a/modules/module/module.model.php b/modules/module/module.model.php index c7c249dd4..c114622d7 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -211,7 +211,7 @@ class moduleModel extends module */ public static function getModuleInfoByMid($mid, $site_srl = 0, $columnList = array()) { - if(!$mid || ($mid && !preg_match("/^[a-z][a-z0-9_]+$/i", $mid))) + if(!$mid || ($mid && !preg_match("/^[a-z][a-z0-9_-]+$/i", $mid))) { return; } From f0b8836595856fc9d23a46921a84ca66a8b36d70 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 20:20:30 +0900 Subject: [PATCH 04/71] Fix short URL for category --- common/framework/router.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/framework/router.php b/common/framework/router.php index afd9b208e..931af4b0e 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -16,7 +16,7 @@ class Router '(?[0-9]+)' => [], '(?[a-zA-Z0-9_-]+)/?' => [], '(?[a-zA-Z0-9_-]+)/(?[0-9]+)' => [], - '(?[a-zA-Z0-9_-]+)/category/(?[0-9]+)' => [], + '(?[a-zA-Z0-9_-]+)/category/(?[0-9]+)' => [], '(?[a-zA-Z0-9_-]+)/entry/(?[^/]+)' => [], '(?[a-zA-Z0-9_-]+)/(?rss|atom|api)' => [], 'files/download/(?[0-9]+)/(?[a-zA-Z0-9_-]+)/(?[^/]+)' => ['act' => 'procFileOutput'], From 7cf17bc43f40bf35d0e408dcfe6614420180b07b Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 22:41:18 +0900 Subject: [PATCH 05/71] Implement SimpleXML parsers for info.xml and module.xml --- .../framework/parsers/moduleactionparser.php | 161 +++++++++ common/framework/parsers/moduleinfoparser.php | 120 +++++++ modules/module/module.model.php | 328 ++---------------- 3 files changed, 311 insertions(+), 298 deletions(-) create mode 100644 common/framework/parsers/moduleactionparser.php create mode 100644 common/framework/parsers/moduleinfoparser.php diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php new file mode 100644 index 000000000..158ae958d --- /dev/null +++ b/common/framework/parsers/moduleactionparser.php @@ -0,0 +1,161 @@ +admin_index_act = ''; + $info->default_index_act = ''; + $info->setup_index_act = ''; + $info->simple_setup_index_act = ''; + $info->action = new \stdClass; + $info->menu = new \stdClass; + $info->grant = new \stdClass; + $info->permission = new \stdClass; + $info->permission_check = new \stdClass; + + // Parse grants. + foreach ($xml->grants->grant as $grant) + { + $grant_info = new \stdClass; + $grant_info->title = self::_getElementsByLang($grant, 'title', $lang); + $grant_info->default = trim($grant['default']); + $grant_name = trim($grant['name']); + $info->grant->{$grant_name} = $grant_info; + } + + // Parse permissions. + foreach ($xml->permissions->permission as $permission) + { + $action_name = trim($permission['action']); + $permission = trim($permission['target']); + $info->permission->{$action_name} = $permission; + + $check = new \stdClass; + $check->key = trim($permission['check_var']) ?: trim($permission['check-var']); + $check->type = trim($permission['check_type']) ?: trim($permission['check-type']); + $info->permission_check->{$action_name} = $check; + } + + // Parse menus. + foreach ($xml->menus->menu as $menu) + { + $menu_info = new \stdClass; + $menu_info->title = self::_getElementsByLang($menu, 'title', $lang); + $menu_info->index = null; + $menu_info->acts = array(); + $menu_info->type = trim($menu['type']); + $menu_name = trim($menu['name']); + $info->menu->{$menu_name} = $menu_info; + } + + // Parse actions. + foreach ($xml->actions->action as $action) + { + $action_name = trim($action['name']); + $permission = trim($action['permission']); + if ($permission) + { + $info->permission->{$action_name} = $permission; + if (isset($info->permission_check->{$action_name})) + { + $info->permission_check->{$action_name} = new \stdClass; + } + $info->permission_check->{$action_name}->key = trim($action['check_var']) ?: trim($action['check-var']); + $info->permission_check->{$action_name}->type = trim($action['check_type']) ?: trim($action['check-type']); + } + + $action_info = new \stdClass; + $action_info->type = trim($action['type']); + $action_info->grant = trim($action['grant']) ?: 'guest'; + $action_info->standalone = trim($action['standalone']) === 'false' ? 'false' : 'true'; + $action_info->ruleset = trim($action['ruleset']); + $action_info->method = trim($action['method']); + $action_info->check_csrf = (trim($action['check_csrf']) ?: trim($action['check-csrf'])) === 'false' ? 'false' : 'true'; + $action_info->meta_noindex = (trim($action['meta_noindex']) ?: trim($action['meta-noindex'])) === 'true' ? 'true' : 'false'; + $info->action->{$action_name} = $action_info; + + $menu_name = trim($action['menu_name']); + if ($menu_name) + { + $info->menu->{$menu_name}->acts[] = $action_name; + if (toBool($action['menu_index'])) + { + $info->menu->{$menu_name}->index = $action_name; + } + } + if (toBool($action['index'])) + { + $info->default_index_act = $action_name; + } + if (toBool($action['admin_index'])) + { + $info->admin_index_act = $action_name; + } + if (toBool($action['setup_index'])) + { + $info->setup_index_act = $action_name; + } + if (toBool($action['simple_setup_index'])) + { + $info->simple_setup_index_act = $action_name; + } + } + + // Return the complete result. + return $info; + } + + /** + * Get child elements that match a language. + * + * @param SimpleXMLElement $parent + * @param string $tag_name + * @param string $lang + * @return string|null + */ + protected static function _getElementsByLang(\SimpleXMLElement $parent, string $tag_name, string $lang): ?string + { + // If there is a child element that matches the language, return it. + foreach ($parent->{$tag_name} as $child) + { + $attribs = $child->attributes('xml', true); + if (strval($attribs['lang']) === $lang) + { + return trim($child); + } + } + + // Otherwise, return the first child element. + foreach ($parent->{$tag_name} as $child) + { + return trim($child); + } + + // If there are no child elements, return null. + return null; + } +} diff --git a/common/framework/parsers/moduleinfoparser.php b/common/framework/parsers/moduleinfoparser.php new file mode 100644 index 000000000..22144e6cf --- /dev/null +++ b/common/framework/parsers/moduleinfoparser.php @@ -0,0 +1,120 @@ +title = self::_getElementsByLang($xml, 'title', $lang); + $info->description = self::_getElementsByLang($xml, 'description', $lang); + $info->version = trim($xml->version); + $info->homepage = trim($xml->homepage); + $info->category = trim($xml->category) ?: 'service'; + $info->date = date('Ymd', strtotime($xml->date . 'T12:00:00Z')); + $info->license = trim($xml->license); + $info->license_link = trim($xml->license['link']); + $info->author = array(); + + foreach ($xml->author as $author) + { + $author_info = new \stdClass; + $author_info->name = self::_getElementsByLang($author, 'name', $lang); + $author_info->email_address = trim($author['email_address']); + $author_info->homepage = trim($author['link']); + $info->author[] = $author_info; + } + } + + // Parse version 0.1 + else + { + $info->title = self::_getElementsByLang($xml, 'title', $lang); + $info->description = self::_getElementsByLang($xml->author, 'description', $lang); + $info->version = trim($xml['version']); + $info->homepage = trim($xml->homepage); + $info->category = trim($xml['category']) ?: 'service'; + $info->date = date('Ymd', strtotime($xml->author['date'] . 'T12:00:00Z')); + $info->license = trim($xml->license); + $info->license_link = trim($xml->license['link']); + $info->author = array(); + + foreach ($xml->author as $author) + { + $author_info = new \stdClass; + $author_info->name = self::_getElementsByLang($author, 'name', $lang); + $author_info->email_address = trim($author['email_address']); + $author_info->homepage = trim($author['link']); + $info->author[] = $author_info; + } + } + + // Add information about actions. + $action_info = ModuleActionParser::loadXML(strtr($filename, ['info.xml' => 'module.xml'])); + $info->admin_index_act = $action_info->admin_index_act; + $info->default_index_act = $action_info->default_index_act; + $info->setup_index_act = $action_info->setup_index_act; + $info->simple_setup_index_act = $action_info->simple_setup_index_act; + + // Return the complete result. + return $info; + } + + /** + * Get child elements that match a language. + * + * @param SimpleXMLElement $parent + * @param string $tag_name + * @param string $lang + * @return string|null + */ + protected static function _getElementsByLang(\SimpleXMLElement $parent, string $tag_name, string $lang): ?string + { + // If there is a child element that matches the language, return it. + foreach ($parent->{$tag_name} as $child) + { + $attribs = $child->attributes('xml', true); + if (strval($attribs['lang']) === $lang) + { + return trim($child); + } + } + + // Otherwise, return the first child element. + foreach ($parent->{$tag_name} as $child) + { + return trim($child); + } + + // If there are no child elements, return null. + return null; + } +} diff --git a/modules/module/module.model.php b/modules/module/module.model.php index c114622d7..de077e463 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -686,73 +686,24 @@ class moduleModel extends module */ public static function getModuleInfoXml($module) { - // Get a path of the requested module. Return if not exists. + // Check the path and XML file name. $module_path = ModuleHandler::getModulePath($module); - if(!$module_path) return; - // Read the xml file for module skin information - $xml_file = sprintf("%s/conf/info.xml", $module_path); - if(!file_exists($xml_file)) return; - - $oXmlParser = new XmlParser(); - $tmp_xml_obj = $oXmlParser->loadXmlFile($xml_file); - $xml_obj = $tmp_xml_obj->module; - - if(!$xml_obj) return; - - // Module Information - $module_info = new stdClass(); - if($xml_obj->version && $xml_obj->attrs->version == '0.2') + if (!$module_path) return; + $xml_file = $module_path . 'conf/info.xml'; + if (!file_exists($xml_file)) return; + + // Load the XML file and cache the definition. + $mtime1 = filemtime($xml_file); + $mtime2 = filemtime($module_path . 'conf/module.xml'); + $cache_key = sprintf('site_and_module:module_info_xml:%s:%d:%d', $module, $mtime1, $mtime2); + $info = Rhymix\Framework\Cache::get($cache_key); + if($info === null) { - // module format 0.2 - $module_info->title = $xml_obj->title->body; - $module_info->description = $xml_obj->description->body; - $module_info->version = $xml_obj->version->body; - $module_info->homepage = $xml_obj->link->body; - $module_info->category = $xml_obj->category->body; - if(!$module_info->category) $module_info->category = 'service'; - $date_obj = (object)array('y' => 0, 'm' => 0, 'd' => 0); - sscanf($xml_obj->date->body, '%d-%d-%d', $date_obj->y, $date_obj->m, $date_obj->d); - $module_info->date = sprintf('%04d%02d%02d', $date_obj->y, $date_obj->m, $date_obj->d); - $module_info->license = $xml_obj->license->body; - $module_info->license_link = $xml_obj->license->attrs->link; - - if(!is_array($xml_obj->author)) $author_list[] = $xml_obj->author; - else $author_list = $xml_obj->author; - - foreach($author_list as $author) - { - $author_obj = new stdClass(); - $author_obj->name = $author->name->body; - $author_obj->email_address = $author->attrs->email_address; - $author_obj->homepage = $author->attrs->link; - $module_info->author[] = $author_obj; - } + $info = Rhymix\Framework\Parsers\ModuleInfoParser::loadXML($xml_file); + Rhymix\Framework\Cache::set($cache_key, $info); } - else - { - // module format 0.1 - $module_info->title = $xml_obj->title->body; - $module_info->description = $xml_obj->author->description->body; - $module_info->version = $xml_obj->attrs->version; - $module_info->category = $xml_obj->attrs->category; - if(!$module_info->category) $module_info->category = 'service'; - $date_obj = (object)array('y' => 0, 'm' => 0, 'd' => 0); - sscanf($xml_obj->author->attrs->date, '%d. %d. %d', $date_obj->y, $date_obj->m, $date_obj->d); - $module_info->date = sprintf('%04d%02d%02d', $date_obj->y, $date_obj->m, $date_obj->d); - $author_obj = new stdClass(); - $author_obj->name = $xml_obj->author->name->body; - $author_obj->email_address = $xml_obj->author->attrs->email_address; - $author_obj->homepage = $xml_obj->author->attrs->link; - $module_info->author[] = $author_obj; - } - // Add admin_index by using action information - $action_info = self::getModuleActionXml($module); - $module_info->admin_index_act = $action_info->admin_index_act; - $module_info->default_index_act = $action_info->default_index_act; - $module_info->setup_index_act = $action_info->setup_index_act; - $module_info->simple_setup_index_act = $action_info->simple_setup_index_act; - - return $module_info; + + return $info; } /** @@ -763,242 +714,23 @@ class moduleModel extends module */ public static function getModuleActionXml($module) { - // Get a path of the requested module. Return if not exists. - $class_path = ModuleHandler::getModulePath($module); - if(!$class_path) return; - - // Check if module.xml exists in the path. Return if not exist - $xml_file = sprintf("%sconf/module.xml", $class_path); - if(!file_exists($xml_file)) return; - - // Check if cached file exists - $cache_file = sprintf(_XE_PATH_ . "files/cache/module_info/%s.%s.%s.php", $module, Context::getLangType(), __XE_VERSION__); - - // Update if no cache file exists or it is older than xml file - if(!file_exists($cache_file) || filemtime($cache_file) < filemtime($xml_file) || $re_cache) + // Check the path and XML file name. + $module_path = ModuleHandler::getModulePath($module); + if (!$module_path) return; + $xml_file = $module_path . 'conf/module.xml'; + if (!file_exists($xml_file)) return; + + // Load the XML file and cache the definition. + $mtim1 = filemtime($xml_file); + $cache_key = sprintf('site_and_module:module_info_xml:%s:%d', $module, $mtime); + $info = Rhymix\Framework\Cache::get($cache_key); + if($info === null) { - $info = new stdClass(); - $buff = array(); // /< Set buff variable to use in the cache file - $buff[] = 'default_index_act = \'%s\';'; - $buff['setup_index_act'] = '$info->setup_index_act=\'%s\';'; - $buff['simple_setup_index_act'] = '$info->simple_setup_index_act=\'%s\';'; - $buff['admin_index_act'] = '$info->admin_index_act = \'%s\';'; - - $xml_obj = XmlParser::loadXmlFile($xml_file); // /< Read xml file and convert it to xml object - - if(!countobj($xml_obj->module)) return; // /< Error occurs if module tag doesn't included in the xml - - $grants = $xml_obj->module->grants->grant; // /< Permission information - $permissions = $xml_obj->module->permissions->permission; // /< Acting permission - $menus = $xml_obj->module->menus->menu; - $actions = $xml_obj->module->actions->action; // /< Action list (required) - - $default_index = $admin_index = ''; - - // Arrange permission information - if($grants) - { - if(is_array($grants)) $grant_list = $grants; - else $grant_list[] = $grants; - - $info->grant = new stdClass(); - $buff[] = '$info->grant = new stdClass;'; - foreach($grant_list as $grant) - { - $name = $grant->attrs->name; - $default = $grant->attrs->default?$grant->attrs->default:'guest'; - $title = $grant->title->body; - - $info->grant->{$name} = new stdClass(); - $info->grant->{$name}->title = $title; - $info->grant->{$name}->default = $default; - - $buff[] = sprintf('$info->grant->%s = new stdClass;', $name); - $buff[] = sprintf('$info->grant->%s->title=\'%s\';', $name, $title); - $buff[] = sprintf('$info->grant->%s->default=\'%s\';', $name, $default); - } - } - // Permissions to grant - if($permissions) - { - if(is_array($permissions)) $permission_list = $permissions; - else $permission_list[] = $permissions; - - $buff[] = '$info->permission = new stdClass;'; - $buff[] = '$info->permission_check = new stdClass;'; - - $info->permission = new stdClass; - $info->permission_check = new stdClass; - - foreach($permission_list as $permission) - { - $action = $permission->attrs->action; - $target = $permission->attrs->target; - - $info->permission->$action = $target; - - $buff[] = sprintf('$info->permission->%s = \'%s\';', $action, $target); - - $info->permission_check->$action = new stdClass; - $info->permission_check->$action->key = $permission->attrs->check_var ?: ''; - $info->permission_check->$action->type = $permission->attrs->check_type ?: ''; - - $buff[] = sprintf('$info->permission_check->%s = new stdClass;', $action); - $buff[] = sprintf('$info->permission_check->%s->key = \'%s\';', $action, $info->permission_check->$action->key); - $buff[] = sprintf('$info->permission_check->%s->type = \'%s\';', $action, $info->permission_check->$action->type); - } - } - // for admin menus - if($menus) - { - if(is_array($menus)) $menu_list = $menus; - else $menu_list[] = $menus; - - $buff[] = '$info->menu = new stdClass;'; - $info->menu = new stdClass(); - - foreach($menu_list as $menu) - { - $menu_name = $menu->attrs->name; - $menu_title = is_array($menu->title) ? $menu->title[0]->body : $menu->title->body; - $menu_type = $menu->attrs->type; - - $info->menu->{$menu_name} = new stdClass(); - $info->menu->{$menu_name}->title = $menu_title; - $info->menu->{$menu_name}->acts = array(); - $info->menu->{$menu_name}->type = $menu_type; - - $buff[] = sprintf('$info->menu->%s = new stdClass;', $menu_name); - $buff[] = sprintf('$info->menu->%s->title=\'%s\';', $menu_name, $menu_title); - $buff[] = sprintf('$info->menu->%s->type=\'%s\';', $menu_name, $menu_type); - } - } - - // actions - if($actions) - { - if(is_array($actions)) $action_list = $actions; - else $action_list[] = $actions; - - if(!isset($info->permission)) - { - $buff[] = '$info->permission = new stdClass;'; - $buff[] = '$info->permission_check = new stdClass;'; - - $info->permission = new stdClass; - $info->permission_check = new stdClass; - } - - $buff[] = '$info->action = new stdClass;'; - $info->action = new stdClass(); - - foreach($action_list as $action) - { - $name = $action->attrs->name; - - // - if($action->attrs->permission) - { - $info->permission->$name = $action->attrs->permission; - - $buff[] = sprintf('$info->permission->%s = \'%s\';', $name, $info->permission->$name); - - $info->permission_check->$name = new stdClass; - $info->permission_check->$name->key = $action->attrs->check_var ?: ''; - $info->permission_check->$name->type = $action->attrs->check_type ?: ''; - - $buff[] = sprintf('$info->permission_check->%s = new stdClass;', $name); - $buff[] = sprintf('$info->permission_check->%s->key = \'%s\';', $name, $info->permission_check->$name->key); - $buff[] = sprintf('$info->permission_check->%s->type = \'%s\';', $name, $info->permission_check->$name->type); - } - - $type = $action->attrs->type; - $grant = $action->attrs->grant?$action->attrs->grant:'guest'; - $standalone = $action->attrs->standalone=='false'?'false':'true'; - $ruleset = $action->attrs->ruleset?$action->attrs->ruleset:''; - $method = $action->attrs->method?$action->attrs->method:''; - $check_csrf = $action->attrs->check_csrf=='false'?'false':'true'; - $meta_noindex = $action->attrs->{'meta-noindex'} === 'true' ? 'true' : 'false'; - - $index = $action->attrs->index; - $admin_index = $action->attrs->admin_index; - $setup_index = $action->attrs->setup_index; - $simple_setup_index = $action->attrs->simple_setup_index; - $menu_index = $action->attrs->menu_index; - - $info->action->{$name} = new stdClass(); - $info->action->{$name}->type = $type; - $info->action->{$name}->grant = $grant; - $info->action->{$name}->standalone = $standalone; - $info->action->{$name}->ruleset = $ruleset; - $info->action->{$name}->method = $method; - $info->action->{$name}->check_csrf = $check_csrf; - $info->action->{$name}->meta_noindex = $meta_noindex; - if($action->attrs->menu_name) - { - $info->menu->{$action->attrs->menu_name} = new stdClass(); - if($menu_index == 'true') - { - $info->menu->{$action->attrs->menu_name}->index = $name; - $buff[] = sprintf('$info->menu->%s->index=\'%s\';', $action->attrs->menu_name, $name); - } - if(is_array($info->menu->{$action->attrs->menu_name}->acts)) - { - $info->menu->{$action->attrs->menu_name}->acts[] = $name; - } - - $buff[] = sprintf('$info->menu->%s->acts[]=\'%s\';', $action->attrs->menu_name, $name); - $i++; - } - - $buff[] = sprintf('$info->action->%s = new stdClass;', $name); - $buff[] = sprintf('$info->action->%s->type=\'%s\';', $name, $type); - $buff[] = sprintf('$info->action->%s->grant=\'%s\';', $name, $grant); - $buff[] = sprintf('$info->action->%s->standalone=\'%s\';', $name, $standalone); - $buff[] = sprintf('$info->action->%s->ruleset=\'%s\';', $name, $ruleset); - $buff[] = sprintf('$info->action->%s->method=\'%s\';', $name, $method); - $buff[] = sprintf('$info->action->%s->check_csrf=\'%s\';', $name, $check_csrf); - $buff[] = sprintf('$info->action->%s->meta_noindex=\'%s\';', $name, $meta_noindex); - - if($index=='true') - { - $default_index_act = $name; - $info->default_index_act = $name; - } - if($admin_index=='true') - { - $admin_index_act = $name; - $info->admin_index_act = $name; - } - if($setup_index=='true') - { - $setup_index_act = $name; - $info->setup_index_act = $name; - } - if($simple_setup_index=='true') - { - $simple_setup_index_act = $name; - $info->simple_setup_index_act = $name; - } - } - } - $buff['default_index_act'] = sprintf($buff['default_index_act'], $default_index_act); - $buff['setup_index_act'] = sprintf($buff['setup_index_act'], $setup_index_act); - $buff['simple_setup_index_act'] = sprintf($buff['simple_setup_index_act'], $simple_setup_index_act); - $buff['admin_index_act'] = sprintf($buff['admin_index_act'], $admin_index_act); - - $buff[] = 'return $info;'; - - $buff = implode(PHP_EOL, $buff); - - FileHandler::writeFile($cache_file, $buff); - - return $info; + $info = Rhymix\Framework\Parsers\ModuleActionParser::loadXML($xml_file); + Rhymix\Framework\Cache::set($cache_key, $info); } - - if(file_exists($cache_file)) return include($cache_file); + + return $info; } /** From 2d1a7ec432ad7c92753eebf353ae1dc9fac49667 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 23:19:23 +0900 Subject: [PATCH 06/71] Add ability to parse routes into regexp --- .../framework/parsers/moduleactionparser.php | 84 ++++++++++++++++++- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index 158ae958d..94437ea96 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -7,6 +7,19 @@ namespace Rhymix\Framework\Parsers; */ class ModuleActionParser { + /** + * Shortcuts for route definition. + */ + protected static $_shortcuts = array( + 'int' => '[1-9][0-9]*', + 'float' => '[0-9]+(?:\.[0-9]+)?', + 'number' => '[0-9]+', + 'alpha' => '[a-zA-Z]+', + 'alphanum' => '[a-zA-Z0-9]+', + 'hex' => '[0-9a-f]+', + 'word' => '[a-zA-Z0-9_]+', + ); + /** * Load an XML file. * @@ -31,6 +44,9 @@ class ModuleActionParser $info->default_index_act = ''; $info->setup_index_act = ''; $info->simple_setup_index_act = ''; + $info->route = new \stdClass; + $info->route->GET = []; + $info->route->POST = []; $info->action = new \stdClass; $info->menu = new \stdClass; $info->grant = new \stdClass; @@ -47,7 +63,7 @@ class ModuleActionParser $info->grant->{$grant_name} = $grant_info; } - // Parse permissions. + // Parse permissions not defined in the section. foreach ($xml->permissions->permission as $permission) { $action_name = trim($permission['action']); @@ -75,6 +91,7 @@ class ModuleActionParser // Parse actions. foreach ($xml->actions->action as $action) { + // Parse permissions. $action_name = trim($action['name']); $permission = trim($action['permission']); if ($permission) @@ -88,16 +105,42 @@ class ModuleActionParser $info->permission_check->{$action_name}->type = trim($action['check_type']) ?: trim($action['check-type']); } + // Parse routes. + $route = trim($action['route']); + $method = trim($action['method']); + $route_vars = []; + if ($route) + { + $route_info = self::analyzeRoute($route); + $route_vars = $route_info->vars; + if ($method) + { + $methods = explode('|', strtoupper($method)); + } + else + { + $methods = starts_with('proc', $action_name) ? ['POST'] : ['GET']; + } + foreach ($methods as $method) + { + $info->route->{$method}[$route_info->regexp] = $action_name; + } + } + + // Parse other information about this action. $action_info = new \stdClass; $action_info->type = trim($action['type']); $action_info->grant = trim($action['grant']) ?: 'guest'; - $action_info->standalone = trim($action['standalone']) === 'false' ? 'false' : 'true'; $action_info->ruleset = trim($action['ruleset']); - $action_info->method = trim($action['method']); + $action_info->method = $method; + $action_info->route = $route; + $action_info->route_vars = $route_vars; + $action_info->standalone = trim($action['standalone']) === 'false' ? 'false' : 'true'; $action_info->check_csrf = (trim($action['check_csrf']) ?: trim($action['check-csrf'])) === 'false' ? 'false' : 'true'; $action_info->meta_noindex = (trim($action['meta_noindex']) ?: trim($action['meta-noindex'])) === 'true' ? 'true' : 'false'; $info->action->{$action_name} = $action_info; + // Set the menu name and index settings. $menu_name = trim($action['menu_name']); if ($menu_name) { @@ -129,6 +172,41 @@ class ModuleActionParser return $info; } + /** + * Convert route definition into a regular expression. + * + * @param string $route + * @return object + */ + public static function analyzeRoute(string $route): object + { + // Replace variables in the route definition into appropriate regexp. + $var_regexp = '#\\$([a-z0-9_]+)(?::(' . implode('|', array_keys(self::$_shortcuts)) . '))?#i'; + $vars = array(); + $regexp = preg_replace_callback($var_regexp, function($match) use(&$vars) { + if (isset($match[2]) && isset(self::$_shortcuts[$match[2]])) + { + $var_pattern = self::$_shortcuts[$match[2]]; + } + else + { + $var_pattern = ends_with('_srl', $match[1]) ? '[0-9]+' : '[^/]+'; + } + $named_group = '(?P<' . $match[1] . '>' . $var_pattern . ')'; + $vars[] = $match[1]; + return $named_group; + }, $route); + + // Anchor the regexp at both ends. + $regexp = '#^' . strtr($regexp, ['#' => '\\#']) . '$#u'; + + // Return the regexp and variable list. + $result = new \stdClass; + $result->regexp = $regexp; + $result->vars = $vars; + return $result; + } + /** * Get child elements that match a language. * From b44f9a2c78ab10131b7fc80caa7ddd9b0d9290ae Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 23:20:59 +0900 Subject: [PATCH 07/71] Fix cache key mismatch in getModuleActionXml() --- modules/module/module.model.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/module/module.model.php b/modules/module/module.model.php index de077e463..b533b1446 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -707,10 +707,7 @@ class moduleModel extends module } /** - * @brief Return permisson and action data by conf/module.xml in the module - * Cache it because it takes too long to parse module.xml file - * When caching, add codes so to include it directly - * This is apparently good for performance, but not sure about its side-effects + * @brief Return permisson and action data by conf/module.xml */ public static function getModuleActionXml($module) { @@ -721,7 +718,7 @@ class moduleModel extends module if (!file_exists($xml_file)) return; // Load the XML file and cache the definition. - $mtim1 = filemtime($xml_file); + $mtime = filemtime($xml_file); $cache_key = sprintf('site_and_module:module_info_xml:%s:%d', $module, $mtime); $info = Rhymix\Framework\Cache::get($cache_key); if($info === null) From 17897aef5adb0e4de45d37ff1ae87c7d85cf88d2 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 23:21:25 +0900 Subject: [PATCH 08/71] Add some routes to the board module --- modules/board/conf/module.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/board/conf/module.xml b/modules/board/conf/module.xml index 8bd36c9cd..b539bb813 100644 --- a/modules/board/conf/module.xml +++ b/modules/board/conf/module.xml @@ -57,12 +57,12 @@ - - - - - - + + + + + + From e6c61c4042346e4ac597fa04fa522bcf841b7a0c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 23:38:01 +0900 Subject: [PATCH 09/71] Support module-defined routes --- common/framework/router.php | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 931af4b0e..7d931b0cb 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -8,9 +8,9 @@ namespace Rhymix\Framework; class Router { /** - * List of XE-compatible rewrite rules. + * List of XE-compatible global routes. */ - protected static $_xe_compatible_rules = array( + protected static $_global_routes = array( 'admin' => ['module' => 'admin'], '(?rss|atom)' => [], '(?[0-9]+)' => [], @@ -49,6 +49,9 @@ class Router */ public static function getRequestArguments(int $rewrite_level): array { + // Get the request method. + $method = $_SERVER['REQUEST_METHOD'] ?: 'GET'; + // Get the local part of the current URL. $url = $_SERVER['REQUEST_URI']; if (starts_with(\RX_BASEURL, $url)) @@ -73,23 +76,36 @@ class Router } // Try to detect the prefix. This might be $mid. - if ($rewrite_level > 0 && preg_match('#^([a-zA-Z0-9_-]+)#', $url, $matches)) + if ($rewrite_level > 1 && preg_match('#^([a-zA-Z0-9_-]+)(?:/(.*))?#s', $url, $matches)) { + // Separate the prefix and the internal part of the URL. $prefix = $matches[1]; + $internal_url = $matches[2] ?? ''; + + // Get the list of routes defined by the module. $module_info = \ModuleModel::getModuleInfoByMid($prefix); if ($module_info && $module_info->module) { - // TODO + $action_info = \ModuleModel::getModuleActionXml($module_info->module); + foreach ($action_info->route->{$method} as $regexp => $action) + { + if (preg_match($regexp, $internal_url, $matches)) + { + $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); + $allargs = array_merge(['mid' => $prefix, 'act' => $action], $matches, $args); + return $allargs; + } + } } } - // Try XE-compatible rules. - foreach (self::$_xe_compatible_rules as $regexp => $additional_args) + // Try XE-compatible global routes. + foreach (self::$_global_routes as $regexp => $additional_args) { if (preg_match('#^' . $regexp . '$#', $url, $matches)) { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge($additional_args ?: [], $matches, $args ?: []); + $allargs = array_merge($additional_args ?: [], $matches, $args); return $allargs; } } From eb2c9d0aed29147ad6296d0054541f251f9e5e00 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 23:49:33 +0900 Subject: [PATCH 10/71] Spaces to tabs --- .../framework/parsers/moduleactionparser.php | 424 +++++++++--------- common/framework/parsers/moduleinfoparser.php | 186 ++++---- 2 files changed, 305 insertions(+), 305 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index 94437ea96..8fd6912a2 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -7,233 +7,233 @@ namespace Rhymix\Framework\Parsers; */ class ModuleActionParser { - /** - * Shortcuts for route definition. - */ - protected static $_shortcuts = array( - 'int' => '[1-9][0-9]*', - 'float' => '[0-9]+(?:\.[0-9]+)?', - 'number' => '[0-9]+', - 'alpha' => '[a-zA-Z]+', - 'alphanum' => '[a-zA-Z0-9]+', - 'hex' => '[0-9a-f]+', - 'word' => '[a-zA-Z0-9_]+', - ); - + /** + * Shortcuts for route definition. + */ + protected static $_shortcuts = array( + 'int' => '[1-9][0-9]*', + 'float' => '[0-9]+(?:\.[0-9]+)?', + 'number' => '[0-9]+', + 'alpha' => '[a-zA-Z]+', + 'alphanum' => '[a-zA-Z0-9]+', + 'hex' => '[0-9a-f]+', + 'word' => '[a-zA-Z0-9_]+', + ); + /** * Load an XML file. * * @param string $filename - * @return object|null + * @return object */ - public static function loadXML(string $filename): ?object + public static function loadXML(string $filename): object { - // Get the current language. - $lang = \Context::getLangType(); - - // Load the XML file. + // Load the XML file. $xml = simplexml_load_file($filename); if ($xml === false) { - return null; + return new \stdClass; } - + + // Get the current language. + $lang = \Context::getLangType(); + // Initialize the module definition. $info = new \stdClass; $info->admin_index_act = ''; $info->default_index_act = ''; $info->setup_index_act = ''; - $info->simple_setup_index_act = ''; - $info->route = new \stdClass; - $info->route->GET = []; - $info->route->POST = []; - $info->action = new \stdClass; - $info->menu = new \stdClass; - $info->grant = new \stdClass; - $info->permission = new \stdClass; - $info->permission_check = new \stdClass; - - // Parse grants. - foreach ($xml->grants->grant as $grant) - { - $grant_info = new \stdClass; - $grant_info->title = self::_getElementsByLang($grant, 'title', $lang); - $grant_info->default = trim($grant['default']); - $grant_name = trim($grant['name']); - $info->grant->{$grant_name} = $grant_info; - } - - // Parse permissions not defined in the section. - foreach ($xml->permissions->permission as $permission) - { - $action_name = trim($permission['action']); - $permission = trim($permission['target']); - $info->permission->{$action_name} = $permission; - - $check = new \stdClass; - $check->key = trim($permission['check_var']) ?: trim($permission['check-var']); - $check->type = trim($permission['check_type']) ?: trim($permission['check-type']); - $info->permission_check->{$action_name} = $check; - } - - // Parse menus. - foreach ($xml->menus->menu as $menu) - { - $menu_info = new \stdClass; - $menu_info->title = self::_getElementsByLang($menu, 'title', $lang); - $menu_info->index = null; - $menu_info->acts = array(); - $menu_info->type = trim($menu['type']); - $menu_name = trim($menu['name']); - $info->menu->{$menu_name} = $menu_info; - } - - // Parse actions. - foreach ($xml->actions->action as $action) - { - // Parse permissions. - $action_name = trim($action['name']); - $permission = trim($action['permission']); - if ($permission) - { - $info->permission->{$action_name} = $permission; - if (isset($info->permission_check->{$action_name})) - { - $info->permission_check->{$action_name} = new \stdClass; - } - $info->permission_check->{$action_name}->key = trim($action['check_var']) ?: trim($action['check-var']); - $info->permission_check->{$action_name}->type = trim($action['check_type']) ?: trim($action['check-type']); - } - - // Parse routes. - $route = trim($action['route']); - $method = trim($action['method']); - $route_vars = []; - if ($route) - { - $route_info = self::analyzeRoute($route); - $route_vars = $route_info->vars; - if ($method) - { - $methods = explode('|', strtoupper($method)); - } - else - { - $methods = starts_with('proc', $action_name) ? ['POST'] : ['GET']; - } - foreach ($methods as $method) - { - $info->route->{$method}[$route_info->regexp] = $action_name; - } - } - - // Parse other information about this action. - $action_info = new \stdClass; - $action_info->type = trim($action['type']); - $action_info->grant = trim($action['grant']) ?: 'guest'; - $action_info->ruleset = trim($action['ruleset']); - $action_info->method = $method; - $action_info->route = $route; - $action_info->route_vars = $route_vars; - $action_info->standalone = trim($action['standalone']) === 'false' ? 'false' : 'true'; - $action_info->check_csrf = (trim($action['check_csrf']) ?: trim($action['check-csrf'])) === 'false' ? 'false' : 'true'; - $action_info->meta_noindex = (trim($action['meta_noindex']) ?: trim($action['meta-noindex'])) === 'true' ? 'true' : 'false'; - $info->action->{$action_name} = $action_info; - - // Set the menu name and index settings. - $menu_name = trim($action['menu_name']); - if ($menu_name) - { - $info->menu->{$menu_name}->acts[] = $action_name; - if (toBool($action['menu_index'])) - { - $info->menu->{$menu_name}->index = $action_name; - } - } - if (toBool($action['index'])) - { - $info->default_index_act = $action_name; - } - if (toBool($action['admin_index'])) - { - $info->admin_index_act = $action_name; - } - if (toBool($action['setup_index'])) - { - $info->setup_index_act = $action_name; - } - if (toBool($action['simple_setup_index'])) - { - $info->simple_setup_index_act = $action_name; - } - } - + $info->simple_setup_index_act = ''; + $info->route = new \stdClass; + $info->route->GET = []; + $info->route->POST = []; + $info->action = new \stdClass; + $info->menu = new \stdClass; + $info->grant = new \stdClass; + $info->permission = new \stdClass; + $info->permission_check = new \stdClass; + + // Parse grants. + foreach ($xml->grants->grant as $grant) + { + $grant_info = new \stdClass; + $grant_info->title = self::_getElementsByLang($grant, 'title', $lang); + $grant_info->default = trim($grant['default']); + $grant_name = trim($grant['name']); + $info->grant->{$grant_name} = $grant_info; + } + + // Parse permissions not defined in the section. + foreach ($xml->permissions->permission as $permission) + { + $action_name = trim($permission['action']); + $permission = trim($permission['target']); + $info->permission->{$action_name} = $permission; + + $check = new \stdClass; + $check->key = trim($permission['check_var']) ?: trim($permission['check-var']); + $check->type = trim($permission['check_type']) ?: trim($permission['check-type']); + $info->permission_check->{$action_name} = $check; + } + + // Parse menus. + foreach ($xml->menus->menu as $menu) + { + $menu_info = new \stdClass; + $menu_info->title = self::_getElementsByLang($menu, 'title', $lang); + $menu_info->index = null; + $menu_info->acts = array(); + $menu_info->type = trim($menu['type']); + $menu_name = trim($menu['name']); + $info->menu->{$menu_name} = $menu_info; + } + + // Parse actions. + foreach ($xml->actions->action as $action) + { + // Parse permissions. + $action_name = trim($action['name']); + $permission = trim($action['permission']); + if ($permission) + { + $info->permission->{$action_name} = $permission; + if (isset($info->permission_check->{$action_name})) + { + $info->permission_check->{$action_name} = new \stdClass; + } + $info->permission_check->{$action_name}->key = trim($action['check_var']) ?: trim($action['check-var']); + $info->permission_check->{$action_name}->type = trim($action['check_type']) ?: trim($action['check-type']); + } + + // Parse routes. + $route = trim($action['route']); + $method = trim($action['method']); + $route_vars = []; + if ($route) + { + $route_info = self::analyzeRoute($route); + $route_vars = $route_info->vars; + if ($method) + { + $methods = explode('|', strtoupper($method)); + } + else + { + $methods = starts_with('proc', $action_name) ? ['POST'] : ['GET']; + } + foreach ($methods as $method) + { + $info->route->{$method}[$route_info->regexp] = $action_name; + } + } + + // Parse other information about this action. + $action_info = new \stdClass; + $action_info->type = trim($action['type']); + $action_info->grant = trim($action['grant']) ?: 'guest'; + $action_info->ruleset = trim($action['ruleset']); + $action_info->method = $method; + $action_info->route = $route; + $action_info->route_vars = $route_vars; + $action_info->standalone = trim($action['standalone']) === 'false' ? 'false' : 'true'; + $action_info->check_csrf = (trim($action['check_csrf']) ?: trim($action['check-csrf'])) === 'false' ? 'false' : 'true'; + $action_info->meta_noindex = (trim($action['meta_noindex']) ?: trim($action['meta-noindex'])) === 'true' ? 'true' : 'false'; + $info->action->{$action_name} = $action_info; + + // Set the menu name and index settings. + $menu_name = trim($action['menu_name']); + if ($menu_name) + { + $info->menu->{$menu_name}->acts[] = $action_name; + if (toBool($action['menu_index'])) + { + $info->menu->{$menu_name}->index = $action_name; + } + } + if (toBool($action['index'])) + { + $info->default_index_act = $action_name; + } + if (toBool($action['admin_index'])) + { + $info->admin_index_act = $action_name; + } + if (toBool($action['setup_index'])) + { + $info->setup_index_act = $action_name; + } + if (toBool($action['simple_setup_index'])) + { + $info->simple_setup_index_act = $action_name; + } + } + // Return the complete result. return $info; - } - - /** - * Convert route definition into a regular expression. - * - * @param string $route - * @return object - */ - public static function analyzeRoute(string $route): object - { - // Replace variables in the route definition into appropriate regexp. - $var_regexp = '#\\$([a-z0-9_]+)(?::(' . implode('|', array_keys(self::$_shortcuts)) . '))?#i'; - $vars = array(); - $regexp = preg_replace_callback($var_regexp, function($match) use(&$vars) { - if (isset($match[2]) && isset(self::$_shortcuts[$match[2]])) - { - $var_pattern = self::$_shortcuts[$match[2]]; - } - else - { - $var_pattern = ends_with('_srl', $match[1]) ? '[0-9]+' : '[^/]+'; - } - $named_group = '(?P<' . $match[1] . '>' . $var_pattern . ')'; - $vars[] = $match[1]; - return $named_group; - }, $route); - - // Anchor the regexp at both ends. - $regexp = '#^' . strtr($regexp, ['#' => '\\#']) . '$#u'; - - // Return the regexp and variable list. - $result = new \stdClass; - $result->regexp = $regexp; - $result->vars = $vars; - return $result; - } - - /** - * Get child elements that match a language. - * - * @param SimpleXMLElement $parent - * @param string $tag_name - * @param string $lang - * @return string|null - */ - protected static function _getElementsByLang(\SimpleXMLElement $parent, string $tag_name, string $lang): ?string - { - // If there is a child element that matches the language, return it. - foreach ($parent->{$tag_name} as $child) - { - $attribs = $child->attributes('xml', true); - if (strval($attribs['lang']) === $lang) - { - return trim($child); - } - } - - // Otherwise, return the first child element. - foreach ($parent->{$tag_name} as $child) - { - return trim($child); - } - - // If there are no child elements, return null. - return null; - } + } + + /** + * Convert route definition into a regular expression. + * + * @param string $route + * @return object + */ + public static function analyzeRoute(string $route): object + { + // Replace variables in the route definition into appropriate regexp. + $var_regexp = '#\\$([a-z0-9_]+)(?::(' . implode('|', array_keys(self::$_shortcuts)) . '))?#i'; + $vars = array(); + $regexp = preg_replace_callback($var_regexp, function($match) use(&$vars) { + if (isset($match[2]) && isset(self::$_shortcuts[$match[2]])) + { + $var_pattern = self::$_shortcuts[$match[2]]; + } + else + { + $var_pattern = ends_with('_srl', $match[1]) ? '[0-9]+' : '[^/]+'; + } + $named_group = '(?P<' . $match[1] . '>' . $var_pattern . ')'; + $vars[] = $match[1]; + return $named_group; + }, $route); + + // Anchor the regexp at both ends. + $regexp = '#^' . strtr($regexp, ['#' => '\\#']) . '$#u'; + + // Return the regexp and variable list. + $result = new \stdClass; + $result->regexp = $regexp; + $result->vars = $vars; + return $result; + } + + /** + * Get child elements that match a language. + * + * @param SimpleXMLElement $parent + * @param string $tag_name + * @param string $lang + * @return string + */ + protected static function _getElementsByLang(\SimpleXMLElement $parent, string $tag_name, string $lang): string + { + // If there is a child element that matches the language, return it. + foreach ($parent->{$tag_name} as $child) + { + $attribs = $child->attributes('xml', true); + if (strval($attribs['lang']) === $lang) + { + return trim($child); + } + } + + // Otherwise, return the first child element. + foreach ($parent->{$tag_name} as $child) + { + return trim($child); + } + + // If there are no child elements, return an empty string. + return ''; + } } diff --git a/common/framework/parsers/moduleinfoparser.php b/common/framework/parsers/moduleinfoparser.php index 22144e6cf..618f30e5d 100644 --- a/common/framework/parsers/moduleinfoparser.php +++ b/common/framework/parsers/moduleinfoparser.php @@ -11,110 +11,110 @@ class ModuleInfoParser * Load an XML file. * * @param string $filename - * @return object|null + * @return object */ - public static function loadXML(string $filename): ?object + public static function loadXML(string $filename): object { - // Get the current language. - $lang = \Context::getLangType(); - - // Initialize the module definition. - $info = new \stdClass; - - // Load the XML file. + // Load the XML file. $xml = simplexml_load_file($filename); if ($xml === false) { - return null; + return new \stdClass; } - - // Get the XML schema version. - $version = strval($xml['version']) ?: '0.1'; - - // Parse version 0.2 - if ($version === '0.2') - { - $info->title = self::_getElementsByLang($xml, 'title', $lang); - $info->description = self::_getElementsByLang($xml, 'description', $lang); - $info->version = trim($xml->version); - $info->homepage = trim($xml->homepage); - $info->category = trim($xml->category) ?: 'service'; - $info->date = date('Ymd', strtotime($xml->date . 'T12:00:00Z')); - $info->license = trim($xml->license); - $info->license_link = trim($xml->license['link']); - $info->author = array(); - - foreach ($xml->author as $author) - { - $author_info = new \stdClass; - $author_info->name = self::_getElementsByLang($author, 'name', $lang); - $author_info->email_address = trim($author['email_address']); - $author_info->homepage = trim($author['link']); - $info->author[] = $author_info; - } + + // Get the current language. + $lang = \Context::getLangType(); + + // Initialize the module definition. + $info = new \stdClass; + + // Get the XML schema version. + $version = strval($xml['version']) ?: '0.1'; + + // Parse version 0.2 + if ($version === '0.2') + { + $info->title = self::_getElementsByLang($xml, 'title', $lang); + $info->description = self::_getElementsByLang($xml, 'description', $lang); + $info->version = trim($xml->version); + $info->homepage = trim($xml->homepage); + $info->category = trim($xml->category) ?: 'service'; + $info->date = date('Ymd', strtotime($xml->date . 'T12:00:00Z')); + $info->license = trim($xml->license); + $info->license_link = trim($xml->license['link']); + $info->author = array(); + + foreach ($xml->author as $author) + { + $author_info = new \stdClass; + $author_info->name = self::_getElementsByLang($author, 'name', $lang); + $author_info->email_address = trim($author['email_address']); + $author_info->homepage = trim($author['link']); + $info->author[] = $author_info; + } + } + + // Parse version 0.1 + else + { + $info->title = self::_getElementsByLang($xml, 'title', $lang); + $info->description = self::_getElementsByLang($xml->author, 'description', $lang); + $info->version = trim($xml['version']); + $info->homepage = trim($xml->homepage); + $info->category = trim($xml['category']) ?: 'service'; + $info->date = date('Ymd', strtotime($xml->author['date'] . 'T12:00:00Z')); + $info->license = trim($xml->license); + $info->license_link = trim($xml->license['link']); + $info->author = array(); + + foreach ($xml->author as $author) + { + $author_info = new \stdClass; + $author_info->name = self::_getElementsByLang($author, 'name', $lang); + $author_info->email_address = trim($author['email_address']); + $author_info->homepage = trim($author['link']); + $info->author[] = $author_info; + } } - - // Parse version 0.1 - else - { - $info->title = self::_getElementsByLang($xml, 'title', $lang); - $info->description = self::_getElementsByLang($xml->author, 'description', $lang); - $info->version = trim($xml['version']); - $info->homepage = trim($xml->homepage); - $info->category = trim($xml['category']) ?: 'service'; - $info->date = date('Ymd', strtotime($xml->author['date'] . 'T12:00:00Z')); - $info->license = trim($xml->license); - $info->license_link = trim($xml->license['link']); - $info->author = array(); - - foreach ($xml->author as $author) - { - $author_info = new \stdClass; - $author_info->name = self::_getElementsByLang($author, 'name', $lang); - $author_info->email_address = trim($author['email_address']); - $author_info->homepage = trim($author['link']); - $info->author[] = $author_info; - } - } - // Add information about actions. - $action_info = ModuleActionParser::loadXML(strtr($filename, ['info.xml' => 'module.xml'])); + // Add information about actions. + $action_info = ModuleActionParser::loadXML(strtr($filename, ['info.xml' => 'module.xml'])); $info->admin_index_act = $action_info->admin_index_act; $info->default_index_act = $action_info->default_index_act; $info->setup_index_act = $action_info->setup_index_act; - $info->simple_setup_index_act = $action_info->simple_setup_index_act; - + $info->simple_setup_index_act = $action_info->simple_setup_index_act; + // Return the complete result. return $info; - } - - /** - * Get child elements that match a language. - * - * @param SimpleXMLElement $parent - * @param string $tag_name - * @param string $lang - * @return string|null - */ - protected static function _getElementsByLang(\SimpleXMLElement $parent, string $tag_name, string $lang): ?string - { - // If there is a child element that matches the language, return it. - foreach ($parent->{$tag_name} as $child) - { - $attribs = $child->attributes('xml', true); - if (strval($attribs['lang']) === $lang) - { - return trim($child); - } - } - - // Otherwise, return the first child element. - foreach ($parent->{$tag_name} as $child) - { - return trim($child); - } - - // If there are no child elements, return null. - return null; - } + } + + /** + * Get child elements that match a language. + * + * @param SimpleXMLElement $parent + * @param string $tag_name + * @param string $lang + * @return string + */ + protected static function _getElementsByLang(\SimpleXMLElement $parent, string $tag_name, string $lang): string + { + // If there is a child element that matches the language, return it. + foreach ($parent->{$tag_name} as $child) + { + $attribs = $child->attributes('xml', true); + if (strval($attribs['lang']) === $lang) + { + return trim($child); + } + } + + // Otherwise, return the first child element. + foreach ($parent->{$tag_name} as $child) + { + return trim($child); + } + + // If there are no child elements, return an empty string. + return ''; + } } From f02c4aa4c4cf778ea72c822c1075abbb629f3cb6 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Jun 2020 23:53:09 +0900 Subject: [PATCH 11/71] Remove object return type declaration for compatibility with PHP < 7.2 --- common/framework/parsers/moduleactionparser.php | 8 ++++---- common/framework/parsers/moduleinfoparser.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index 8fd6912a2..98e520658 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -24,15 +24,15 @@ class ModuleActionParser * Load an XML file. * * @param string $filename - * @return object + * @return object|false */ - public static function loadXML(string $filename): object + public static function loadXML(string $filename) { // Load the XML file. $xml = simplexml_load_file($filename); if ($xml === false) { - return new \stdClass; + return false; } // Get the current language. @@ -178,7 +178,7 @@ class ModuleActionParser * @param string $route * @return object */ - public static function analyzeRoute(string $route): object + public static function analyzeRoute(string $route) { // Replace variables in the route definition into appropriate regexp. $var_regexp = '#\\$([a-z0-9_]+)(?::(' . implode('|', array_keys(self::$_shortcuts)) . '))?#i'; diff --git a/common/framework/parsers/moduleinfoparser.php b/common/framework/parsers/moduleinfoparser.php index 618f30e5d..c8cfd018e 100644 --- a/common/framework/parsers/moduleinfoparser.php +++ b/common/framework/parsers/moduleinfoparser.php @@ -11,15 +11,15 @@ class ModuleInfoParser * Load an XML file. * * @param string $filename - * @return object + * @return object|false */ - public static function loadXML(string $filename): object + public static function loadXML(string $filename) { // Load the XML file. $xml = simplexml_load_file($filename); if ($xml === false) { - return new \stdClass; + return false; } // Get the current language. From b706f2f6ab1c249e21b4f1bdab42592f3085d51c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 13 Jun 2020 00:27:23 +0900 Subject: [PATCH 12/71] Add support for the generic mid/act URL pattern --- common/framework/router.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/common/framework/router.php b/common/framework/router.php index 7d931b0cb..f0bfe6e31 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -82,11 +82,14 @@ class Router $prefix = $matches[1]; $internal_url = $matches[2] ?? ''; - // Get the list of routes defined by the module. + // Find the module associated with this prefix. $module_info = \ModuleModel::getModuleInfoByMid($prefix); if ($module_info && $module_info->module) { + // Get module actions. $action_info = \ModuleModel::getModuleActionXml($module_info->module); + + // Try the list of routes defined by the module. foreach ($action_info->route->{$method} as $regexp => $action) { if (preg_match($regexp, $internal_url, $matches)) @@ -96,6 +99,13 @@ class Router return $allargs; } } + + // Try the generic mid/act pattern. + if (preg_match('#^[a-zA-Z0-9_]+$#', $internal_url) && isset($action_info->action->{$internal_url}) && !$action_info->action->{$internal_url}->route) + { + $allargs = array_merge(['mid' => $prefix, 'act' => $internal_url], $args); + return $allargs; + } } } From 1bf5b4c2405a10b170dc9ba562508f9f42b824f9 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 13 Jun 2020 00:37:59 +0900 Subject: [PATCH 13/71] Change syntax for specifying multiple routes for the same action --- .../framework/parsers/moduleactionparser.php | 26 ++++++++----------- modules/board/conf/module.xml | 2 +- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index 98e520658..c33267387 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -108,22 +108,19 @@ class ModuleActionParser // Parse routes. $route = trim($action['route']); $method = trim($action['method']); - $route_vars = []; + $route_arg = []; if ($route) { - $route_info = self::analyzeRoute($route); - $route_vars = $route_info->vars; - if ($method) + $methods = $method ? explode('|', strtoupper($method)) : (starts_with('proc', $action_name) ? ['POST'] : ['GET']); + $routes = explode_with_escape('|', $route); + foreach ($routes as $route) { - $methods = explode('|', strtoupper($method)); - } - else - { - $methods = starts_with('proc', $action_name) ? ['POST'] : ['GET']; - } - foreach ($methods as $method) - { - $info->route->{$method}[$route_info->regexp] = $action_name; + $route_info = self::analyzeRoute($route); + $route_arg[$route] = $route_info->vars; + foreach ($methods as $method) + { + $info->route->{$method}[$route_info->regexp] = $action_name; + } } } @@ -133,8 +130,7 @@ class ModuleActionParser $action_info->grant = trim($action['grant']) ?: 'guest'; $action_info->ruleset = trim($action['ruleset']); $action_info->method = $method; - $action_info->route = $route; - $action_info->route_vars = $route_vars; + $action_info->route = $route_arg; $action_info->standalone = trim($action['standalone']) === 'false' ? 'false' : 'true'; $action_info->check_csrf = (trim($action['check_csrf']) ?: trim($action['check-csrf'])) === 'false' ? 'false' : 'true'; $action_info->meta_noindex = (trim($action['meta_noindex']) ?: trim($action['meta-noindex'])) === 'true' ? 'true' : 'false'; diff --git a/modules/board/conf/module.xml b/modules/board/conf/module.xml index b539bb813..63fe0b78f 100644 --- a/modules/board/conf/module.xml +++ b/modules/board/conf/module.xml @@ -57,7 +57,7 @@ - + From 4a5159b255ef95a5c98f33647f2caa8de7594857 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 13 Jun 2020 01:04:21 +0900 Subject: [PATCH 14/71] Always cache module and action info --- modules/module/module.model.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/module/module.model.php b/modules/module/module.model.php index b533b1446..a2108437b 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -700,7 +700,7 @@ class moduleModel extends module if($info === null) { $info = Rhymix\Framework\Parsers\ModuleInfoParser::loadXML($xml_file); - Rhymix\Framework\Cache::set($cache_key, $info); + Rhymix\Framework\Cache::set($cache_key, $info, 0, true); } return $info; @@ -724,7 +724,7 @@ class moduleModel extends module if($info === null) { $info = Rhymix\Framework\Parsers\ModuleActionParser::loadXML($xml_file); - Rhymix\Framework\Cache::set($cache_key, $info); + Rhymix\Framework\Cache::set($cache_key, $info, 0, true); } return $info; From 522e3af8c1652dc335754a3b3e1f60c7b741f215 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 13 Jun 2020 01:38:23 +0900 Subject: [PATCH 15/71] Remove unnecessary 'i' modifier in variable regexp --- common/framework/parsers/moduleactionparser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index c33267387..5a3ef5f58 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -177,7 +177,7 @@ class ModuleActionParser public static function analyzeRoute(string $route) { // Replace variables in the route definition into appropriate regexp. - $var_regexp = '#\\$([a-z0-9_]+)(?::(' . implode('|', array_keys(self::$_shortcuts)) . '))?#i'; + $var_regexp = '#\\$([a-zA-Z0-9_]+)(?::(' . implode('|', array_keys(self::$_shortcuts)) . '))?#'; $vars = array(); $regexp = preg_replace_callback($var_regexp, function($match) use(&$vars) { if (isset($match[2]) && isset(self::$_shortcuts[$match[2]])) From 607de73a76069471680148dcb80ea89486c47866 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 13 Jun 2020 01:38:45 +0900 Subject: [PATCH 16/71] Implement reverse generation of URLs --- common/framework/router.php | 142 ++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 5 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index f0bfe6e31..52ed9713b 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -22,6 +22,12 @@ class Router 'files/download/(?[0-9]+)/(?[a-zA-Z0-9_-]+)/(?[^/]+)' => ['act' => 'procFileOutput'], ); + /** + * Internal cache for module actions. + */ + protected static $_action_cache_prefix = array(); + protected static $_action_cache_module = array(); + /** * Return the currently configured rewrite level. * @@ -83,12 +89,9 @@ class Router $internal_url = $matches[2] ?? ''; // Find the module associated with this prefix. - $module_info = \ModuleModel::getModuleInfoByMid($prefix); - if ($module_info && $module_info->module) + $action_info = self::_getModuleActionInfo($prefix); + if ($action_info) { - // Get module actions. - $action_info = \ModuleModel::getModuleActionXml($module_info->module); - // Try the list of routes defined by the module. foreach ($action_info->route->{$method} as $regexp => $action) { @@ -123,4 +126,133 @@ class Router // If no pattern matches, return an empty array. return array(); } + + /** + * Create a URL for the given set of arguments. + * + * @param array $args + * @param int $rewrite_level + * @return string + */ + public static function getURLFromArguments(array $args, int $rewrite_level): string + { + // If rewrite is turned off, just create a query string. + if ($rewrite_level == 0) + { + return 'index.php?' . http_build_query($args); + } + + // Cache the number of arguments and their keys. + $count = count($args); + $keys = array_keys($args); + + // If there are no arguments, return the URL of the main page. + if ($count == 0) + { + return ''; + } + + // If there is only one argument, try either $mid or $document_srl. + if ($count == 1 && ($keys[0] === 'mid' || $keys[0] === 'document_srl')) + { + return urlencode($args[$keys[0]]); + } + + // If $mid and $act exist, try routes defined in the module. + if (isset($args['mid']) && isset($args['act'])) + { + // Check if $act has any routes defined. + $action_info = self::_getModuleActionInfo($args['mid']); + $action = $action_info->action->{$args['act']}; + if ($action->route) + { + // Remove $mid and $act from arguments and work with the remainder. + $remaining_args = array_diff_key($args, ['mid' => 'mid', 'act' => 'act']); + + // If the action only has one route, select it. + if (count($action->route) == 1) + { + $selected_route = key($action->route); + } + + // If the action has multiple routes, select the one that matches the most arguments. + else + { + // Order the routes by the number of matched arguments. + $reordered_routes = array(); + foreach ($action->route as $route => $route_vars) + { + $matched_arguments = array_intersect_key(array_combine($route_vars, $route_vars), $remaining_args); + if (count($matched_arguments) === count($route_vars)) + { + $reordered_routes[$route] = count($matched_arguments); + } + } + arsort($reordered_routes); + $selected_route = array_first_key($reordered_routes); + } + + // Replace variable placeholders with actual variable values. + $replaced_route = preg_replace_callback('#\\$([a-zA-Z0-9_]+)(?::[a-z]+)?#i', function($match) use(&$remaining_args) { + if (isset($remaining_args[$match[1]])) + { + $replacement = urlencode($remaining_args[$match[1]]); + unset($remaining_args[$match[1]]); + return $replacement; + } + else + { + return $match[0]; + } + }, $selected_route); + + // Add a query string for the remaining arguments. + return $replaced_route . (count($remaining_args) ? ('?' . http_build_query($remaining_args)) : ''); + } + } + + // If no route matches, just create a query string. + return 'index.php?' . http_build_query($args); + } + + /** + * Load and cache module action info. + * + * @param string $prefix + * @return object + */ + protected static function _getModuleActionInfo($prefix) + { + if (isset(self::$_action_cache_prefix[$prefix])) + { + if (self::$_action_cache_prefix[$prefix] && isset(self::$_action_cache_module[self::$_action_cache_prefix[$prefix]])) + { + return self::$_action_cache_module[self::$_action_cache_prefix[$prefix]]; + } + else + { + return false; + } + } + + $module_info = \ModuleModel::getModuleInfoByMid($prefix); + if ($module_info && $module_info->module) + { + self::$_action_cache_prefix[$prefix] = $module_info->module; + if (isset(self::$_action_cache_module[$module_info->module])) + { + return self::$_action_cache_module[$module_info->module]; + } + else + { + $action_info = \ModuleModel::getModuleActionXml($module_info->module); + return self::$_action_cache_module[$module_info->module] = $action_info; + } + } + else + { + self::$_action_cache_prefix[$prefix] = false; + return false; + } + } } From 7fc3d8388818318d5c84393d09abe973548b39dc Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 13 Jun 2020 01:45:33 +0900 Subject: [PATCH 17/71] Generate mid/act URLs, too --- common/framework/router.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 52ed9713b..0aa01c1c3 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -159,16 +159,16 @@ class Router } // If $mid and $act exist, try routes defined in the module. - if (isset($args['mid']) && isset($args['act'])) + if (isset($args['mid']) && isset($args['act']) && $rewrite_level == 2) { + // Remove $mid and $act from arguments and work with the remainder. + $remaining_args = array_diff_key($args, ['mid' => 'mid', 'act' => 'act']); + // Check if $act has any routes defined. $action_info = self::_getModuleActionInfo($args['mid']); $action = $action_info->action->{$args['act']}; if ($action->route) { - // Remove $mid and $act from arguments and work with the remainder. - $remaining_args = array_diff_key($args, ['mid' => 'mid', 'act' => 'act']); - // If the action only has one route, select it. if (count($action->route) == 1) { @@ -209,6 +209,9 @@ class Router // Add a query string for the remaining arguments. return $replaced_route . (count($remaining_args) ? ('?' . http_build_query($remaining_args)) : ''); } + + // Otherwise, try the generic mid/act pattern. + return $args['mid'] . '/' . $args['act'] . (count($remaining_args) ? ('?' . http_build_query($remaining_args)) : ''); } // If no route matches, just create a query string. From bf8d2c8b09fce8a6c818339d429c3aab5dc4575c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 13 Jun 2020 11:48:58 +0900 Subject: [PATCH 18/71] Improve handling of multiple routes per action --- .../framework/parsers/moduleactionparser.php | 23 +++++++++++++------ common/framework/router.php | 15 ++++++++---- modules/board/conf/module.xml | 20 ++++++++++------ 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index 5a3ef5f58..caa02a983 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -18,6 +18,7 @@ class ModuleActionParser 'alphanum' => '[a-zA-Z0-9]+', 'hex' => '[0-9a-f]+', 'word' => '[a-zA-Z0-9_]+', + 'any' => '[^/]+', ); /** @@ -106,17 +107,22 @@ class ModuleActionParser } // Parse routes. - $route = trim($action['route']); + $route_attr = trim($action['route']); + $route_tags = $action->route; $method = trim($action['method']); $route_arg = []; - if ($route) + if ($route_attr || count($route_tags)) { $methods = $method ? explode('|', strtoupper($method)) : (starts_with('proc', $action_name) ? ['POST'] : ['GET']); - $routes = explode_with_escape('|', $route); + $routes = $route_attr ? explode_with_escape('|', $route_attr) : array(); + foreach ($route_tags as $route_tag) + { + $routes[] = trim($route_tag['route']); + } foreach ($routes as $route) { $route_info = self::analyzeRoute($route); - $route_arg[$route] = $route_info->vars; + $route_arg[$route_info->route] = $route_info->vars; foreach ($methods as $method) { $info->route->{$method}[$route_info->regexp] = $action_name; @@ -180,16 +186,18 @@ class ModuleActionParser $var_regexp = '#\\$([a-zA-Z0-9_]+)(?::(' . implode('|', array_keys(self::$_shortcuts)) . '))?#'; $vars = array(); $regexp = preg_replace_callback($var_regexp, function($match) use(&$vars) { - if (isset($match[2]) && isset(self::$_shortcuts[$match[2]])) + if (isset($match[2])) { + $var_type = $match[2]; $var_pattern = self::$_shortcuts[$match[2]]; } else { - $var_pattern = ends_with('_srl', $match[1]) ? '[0-9]+' : '[^/]+'; + $var_type = ends_with('_srl', $match[1]) ? 'number' : 'any'; + $var_pattern = self::$_shortcuts[$var_type]; } $named_group = '(?P<' . $match[1] . '>' . $var_pattern . ')'; - $vars[] = $match[1]; + $vars[$match[1]] = $var_type; return $named_group; }, $route); @@ -198,6 +206,7 @@ class ModuleActionParser // Return the regexp and variable list. $result = new \stdClass; + $result->route = preg_replace($var_regexp, '\\$$1', $route); $result->regexp = $regexp; $result->vars = $vars; return $result; diff --git a/common/framework/router.php b/common/framework/router.php index 0aa01c1c3..10edae3ab 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -158,15 +158,20 @@ class Router return urlencode($args[$keys[0]]); } - // If $mid and $act exist, try routes defined in the module. + // If $mid exists, try routes defined in the module. if (isset($args['mid']) && isset($args['act']) && $rewrite_level == 2) { // Remove $mid and $act from arguments and work with the remainder. $remaining_args = array_diff_key($args, ['mid' => 'mid', 'act' => 'act']); - // Check if $act has any routes defined. + // Get module action info. $action_info = self::_getModuleActionInfo($args['mid']); - $action = $action_info->action->{$args['act']}; + + // If there is no $act, use the default action. + $act = isset($args['act']) ? $args['act'] : $action_info->default_index_act; + + // Check if $act has any routes defined. + $action = $action_info->action->{$act}; if ($action->route) { // If the action only has one route, select it. @@ -182,7 +187,7 @@ class Router $reordered_routes = array(); foreach ($action->route as $route => $route_vars) { - $matched_arguments = array_intersect_key(array_combine($route_vars, $route_vars), $remaining_args); + $matched_arguments = array_intersect_key($route_vars, $remaining_args); if (count($matched_arguments) === count($route_vars)) { $reordered_routes[$route] = count($matched_arguments); @@ -214,6 +219,8 @@ class Router return $args['mid'] . '/' . $args['act'] . (count($remaining_args) ? ('?' . http_build_query($remaining_args)) : ''); } + // Try XE-compatible global routes. + // If no route matches, just create a query string. return 'index.php?' . http_build_query($args); } diff --git a/modules/board/conf/module.xml b/modules/board/conf/module.xml index 63fe0b78f..d7962d6b2 100644 --- a/modules/board/conf/module.xml +++ b/modules/board/conf/module.xml @@ -56,13 +56,19 @@ - - - - - - - + + + + + + + + + + + + + From df003ec7e8f53d1b2ed9561dd0a059c764abefa4 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 14 Jun 2020 19:31:33 +0900 Subject: [PATCH 19/71] Improve handling of XE-compatible global routes --- common/framework/router.php | 235 ++++++++++++++++++++++++------------ 1 file changed, 161 insertions(+), 74 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 10edae3ab..2be4ce083 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -11,15 +11,44 @@ class Router * List of XE-compatible global routes. */ protected static $_global_routes = array( - 'admin' => ['module' => 'admin'], - '(?rss|atom)' => [], - '(?[0-9]+)' => [], - '(?[a-zA-Z0-9_-]+)/?' => [], - '(?[a-zA-Z0-9_-]+)/(?[0-9]+)' => [], - '(?[a-zA-Z0-9_-]+)/category/(?[0-9]+)' => [], - '(?[a-zA-Z0-9_-]+)/entry/(?[^/]+)' => [], - '(?[a-zA-Z0-9_-]+)/(?rss|atom|api)' => [], - 'files/download/(?[0-9]+)/(?[a-zA-Z0-9_-]+)/(?[^/]+)' => ['act' => 'procFileOutput'], + '$act' => array( + 'regexp' => '#^(?rss|atom)$#', + 'vars' => ['act' => 'word'], + ), + '$document_srl' => array( + 'regexp' => '#^(?[0-9]+)$#', + 'vars' => ['document_srl' => 'int'], + ), + '$mid' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/?$#', + 'vars' => ['mid' => 'any'], + ), + '$mid/$document_srl' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?[0-9]+)$#', + 'vars' => ['mid' => 'any', 'document_srl' => 'int'], + ), + '$mid/category/$category' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/category/(?[0-9]+)$#', + 'vars' => ['mid' => 'any', 'category' => 'int'], + ), + '$mid/entry/$entry' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/entry/(?[^/]+)$#', + 'vars' => ['mid' => 'any', 'entry' => 'any'], + ), + '$mid/$act' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?rss|atom|api)$#', + 'vars' => ['mid' => 'any', 'act' => 'word'], + ), + 'files/download/$file_srl/$file_key/$filename' => array( + 'regexp' => '#^files/download/(?[0-9]+)/(?[a-zA-Z0-9_-]+)/(?[^/]+)$#', + 'vars' => ['file_srl' => 'int', 'file_key' => 'any', 'filename' => 'any'], + 'extra_vars' => ['act' => 'procFileOutput'], + ), + 'admin' => array( + 'regexp' => '#^admin$#', + 'vars' => [], + 'extra_vars' => ['module' => 'admin'], + ), ); /** @@ -27,6 +56,7 @@ class Router */ protected static $_action_cache_prefix = array(); protected static $_action_cache_module = array(); + protected static $_rearranged_global_routes = array(); /** * Return the currently configured rewrite level. @@ -89,7 +119,7 @@ class Router $internal_url = $matches[2] ?? ''; // Find the module associated with this prefix. - $action_info = self::_getModuleActionInfo($prefix); + $action_info = self::_getActionInfoByPrefix($prefix); if ($action_info) { // Try the list of routes defined by the module. @@ -113,12 +143,12 @@ class Router } // Try XE-compatible global routes. - foreach (self::$_global_routes as $regexp => $additional_args) + foreach (self::$_global_routes as $route_info) { - if (preg_match('#^' . $regexp . '$#', $url, $matches)) + if (preg_match($route_info['regexp'], $url, $matches)) { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge($additional_args ?: [], $matches, $args); + $allargs = array_merge($route_info['extra_vars'] ?? [], $matches, $args); return $allargs; } } @@ -159,64 +189,40 @@ class Router } // If $mid exists, try routes defined in the module. - if (isset($args['mid']) && isset($args['act']) && $rewrite_level == 2) + if (isset($args['mid']) && $rewrite_level == 2) { - // Remove $mid and $act from arguments and work with the remainder. - $remaining_args = array_diff_key($args, ['mid' => 'mid', 'act' => 'act']); + // Remove $mid from arguments and work with the remainder. + $args2 = $args; unset($args2['mid'], $args2['act']); // Get module action info. - $action_info = self::_getModuleActionInfo($args['mid']); + $action_info = self::_getActionInfoByPrefix($args['mid']); // If there is no $act, use the default action. $act = isset($args['act']) ? $args['act'] : $action_info->default_index_act; // Check if $act has any routes defined. - $action = $action_info->action->{$act}; + $action = $action_info->action->{$act} ?? null; if ($action->route) { - // If the action only has one route, select it. - if (count($action->route) == 1) + $result = self::_insertRouteVars($action->route, $args2); + if ($result !== false) { - $selected_route = key($action->route); + return $args['mid'] . '/' . $result; } - - // If the action has multiple routes, select the one that matches the most arguments. - else + } + + // Check XE-compatible routes that start with $mid and contain no $act. + if (!isset($args['act'])) + { + $result = self::_insertRouteVars(self::_getRearrangedGlobalRoutes(), $args2); + if ($result !== false) { - // Order the routes by the number of matched arguments. - $reordered_routes = array(); - foreach ($action->route as $route => $route_vars) - { - $matched_arguments = array_intersect_key($route_vars, $remaining_args); - if (count($matched_arguments) === count($route_vars)) - { - $reordered_routes[$route] = count($matched_arguments); - } - } - arsort($reordered_routes); - $selected_route = array_first_key($reordered_routes); + return $result; } - - // Replace variable placeholders with actual variable values. - $replaced_route = preg_replace_callback('#\\$([a-zA-Z0-9_]+)(?::[a-z]+)?#i', function($match) use(&$remaining_args) { - if (isset($remaining_args[$match[1]])) - { - $replacement = urlencode($remaining_args[$match[1]]); - unset($remaining_args[$match[1]]); - return $replacement; - } - else - { - return $match[0]; - } - }, $selected_route); - - // Add a query string for the remaining arguments. - return $replaced_route . (count($remaining_args) ? ('?' . http_build_query($remaining_args)) : ''); } // Otherwise, try the generic mid/act pattern. - return $args['mid'] . '/' . $args['act'] . (count($remaining_args) ? ('?' . http_build_query($remaining_args)) : ''); + return $args['mid'] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); } // Try XE-compatible global routes. @@ -231,38 +237,119 @@ class Router * @param string $prefix * @return object */ - protected static function _getModuleActionInfo($prefix) + protected static function _getActionInfoByPrefix(string $prefix) { if (isset(self::$_action_cache_prefix[$prefix])) { - if (self::$_action_cache_prefix[$prefix] && isset(self::$_action_cache_module[self::$_action_cache_prefix[$prefix]])) - { - return self::$_action_cache_module[self::$_action_cache_prefix[$prefix]]; - } - else - { - return false; - } + return self::_getActionInfoByModule(self::$_action_cache_prefix[$prefix]) ?: false; } $module_info = \ModuleModel::getModuleInfoByMid($prefix); if ($module_info && $module_info->module) { self::$_action_cache_prefix[$prefix] = $module_info->module; - if (isset(self::$_action_cache_module[$module_info->module])) - { - return self::$_action_cache_module[$module_info->module]; - } - else - { - $action_info = \ModuleModel::getModuleActionXml($module_info->module); - return self::$_action_cache_module[$module_info->module] = $action_info; - } + return self::_getActionInfoByModule(self::$_action_cache_prefix[$prefix]) ?: false; } else { - self::$_action_cache_prefix[$prefix] = false; - return false; + return self::$_action_cache_prefix[$prefix] = false; } } + + /** + * Load and cache module action info. + * + * @param string $prefix + * @return object + */ + protected static function _getActionInfoByModule(string $module) + { + if (isset(self::$_action_cache_module[$module])) + { + return self::$_action_cache_module[$module]; + } + + $action_info = \ModuleModel::getModuleActionXml($module); + return self::$_action_cache_module[$module] = $action_info ?: false; + } + + /** + * Get rearranged global routes for argument matching. + * + * @return array + */ + protected static function _getRearrangedGlobalRoutes(): array + { + if (!self::$_rearranged_global_routes) + { + foreach (self::$_global_routes as $route_name => $route_info) + { + if (!strncmp($route_name, '$mid/', 5)) + { + self::$_rearranged_global_routes[$route_name] = $route_info['vars']; + } + } + } + + return self::$_rearranged_global_routes; + } + + /** + * Insert variables into a route. + * + * @param array $routes + * @param array $vars + * @return string|false + */ + protected static function _insertRouteVars(array $routes, array $vars) + { + // If the action only has one route, select it. + if (count($routes) == 1) + { + $selected_route = key($routes); + $matched_arguments = array_intersect_key($routes[$selected_route], $vars); + if (count($matched_arguments) !== count($routes[$selected_route])) + { + return false; + } + } + + // If the action has multiple routes, select the one that matches the most arguments. + else + { + // Order the routes by the number of matched arguments. + $reordered_routes = array(); + foreach ($routes as $route => $route_vars) + { + $matched_arguments = array_intersect_key($route_vars, $vars); + if (count($matched_arguments) === count($route_vars)) + { + $reordered_routes[$route] = count($matched_arguments); + } + } + if (!count($reordered_routes)) + { + return false; + } + arsort($reordered_routes); + $selected_route = array_first_key($reordered_routes); + } + + // Replace variable placeholders with actual variable values. + $composed_url = preg_replace_callback('#\\$([a-zA-Z0-9_]+)(?::[a-z]+)?#i', function($match) use(&$vars) { + if (isset($vars[$match[1]])) + { + $replacement = urlencode($vars[$match[1]]); + unset($vars[$match[1]]); + return $replacement; + } + else + { + return $match[0]; + } + }, $selected_route); + + // Add a query string for the remaining arguments. + return $composed_url . (count($vars) ? ('?' . http_build_query($vars)) : ''); + } } From d582b712db1b3677eec764ab03e2eebdc68ceeb5 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 14 Jun 2020 20:08:56 +0900 Subject: [PATCH 20/71] Minor cleanup --- common/framework/router.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 2be4ce083..8c46fdd82 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -202,7 +202,7 @@ class Router // Check if $act has any routes defined. $action = $action_info->action->{$act} ?? null; - if ($action->route) + if ($action && $action->route) { $result = self::_insertRouteVars($action->route, $args2); if ($result !== false) @@ -221,7 +221,7 @@ class Router } } - // Otherwise, try the generic mid/act pattern. + // Try the generic mid/act pattern. return $args['mid'] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); } From d0b0790bebd2dd3faf26da21dd1f5cb0bc16ebd7 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 14 Jun 2020 20:24:31 +0900 Subject: [PATCH 21/71] Reorganize rules and conditions --- common/framework/router.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 8c46fdd82..6eecb4439 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -11,6 +11,10 @@ class Router * List of XE-compatible global routes. */ protected static $_global_routes = array( + '$mid' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/?$#', + 'vars' => ['mid' => 'any'], + ), '$act' => array( 'regexp' => '#^(?rss|atom)$#', 'vars' => ['act' => 'word'], @@ -19,10 +23,6 @@ class Router 'regexp' => '#^(?[0-9]+)$#', 'vars' => ['document_srl' => 'int'], ), - '$mid' => array( - 'regexp' => '#^(?[a-zA-Z0-9_-]+)/?$#', - 'vars' => ['mid' => 'any'], - ), '$mid/$document_srl' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?[0-9]+)$#', 'vars' => ['mid' => 'any', 'document_srl' => 'int'], @@ -44,11 +44,6 @@ class Router 'vars' => ['file_srl' => 'int', 'file_key' => 'any', 'filename' => 'any'], 'extra_vars' => ['act' => 'procFileOutput'], ), - 'admin' => array( - 'regexp' => '#^admin$#', - 'vars' => [], - 'extra_vars' => ['module' => 'admin'], - ), ); /** @@ -183,13 +178,13 @@ class Router } // If there is only one argument, try either $mid or $document_srl. - if ($count == 1 && ($keys[0] === 'mid' || $keys[0] === 'document_srl')) + if ($rewrite_level >= 1 && $count == 1 && ($keys[0] === 'mid' || $keys[0] === 'document_srl')) { return urlencode($args[$keys[0]]); } // If $mid exists, try routes defined in the module. - if (isset($args['mid']) && $rewrite_level == 2) + if ($rewrite_level >= 2 && isset($args['mid'])) { // Remove $mid from arguments and work with the remainder. $args2 = $args; unset($args2['mid'], $args2['act']); @@ -212,7 +207,7 @@ class Router } // Check XE-compatible routes that start with $mid and contain no $act. - if (!isset($args['act'])) + if (!isset($args['act']) || ($args['act'] === 'rss' || $args['act'] === 'atom' || $args['act'] === 'api')) { $result = self::_insertRouteVars(self::_getRearrangedGlobalRoutes(), $args2); if ($result !== false) @@ -226,6 +221,14 @@ class Router } // Try XE-compatible global routes. + if ($rewrite_level >= 1) + { + $result = self::_insertRouteVars(self::_getRearrangedGlobalRoutes(), $args); + if ($result !== false) + { + return $result; + } + } // If no route matches, just create a query string. return 'index.php?' . http_build_query($args); @@ -284,10 +287,7 @@ class Router { foreach (self::$_global_routes as $route_name => $route_info) { - if (!strncmp($route_name, '$mid/', 5)) - { - self::$_rearranged_global_routes[$route_name] = $route_info['vars']; - } + self::$_rearranged_global_routes[$route_name] = $route_info['vars']; } } From 5869201023b8b44a637bb43a900a2b5da4f703fa Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 14 Jun 2020 20:31:41 +0900 Subject: [PATCH 22/71] More fixes to XE compatible routes --- common/framework/router.php | 9 ++++++--- modules/board/conf/module.xml | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 6eecb4439..69c21eb6c 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -223,10 +223,13 @@ class Router // Try XE-compatible global routes. if ($rewrite_level >= 1) { - $result = self::_insertRouteVars(self::_getRearrangedGlobalRoutes(), $args); - if ($result !== false) + if (!isset($args['act']) || ($args['act'] === 'rss' || $args['act'] === 'atom')) { - return $result; + $result = self::_insertRouteVars(self::_getRearrangedGlobalRoutes(), $args); + if ($result !== false) + { + return $result; + } } } diff --git a/modules/board/conf/module.xml b/modules/board/conf/module.xml index d7962d6b2..80f85709f 100644 --- a/modules/board/conf/module.xml +++ b/modules/board/conf/module.xml @@ -57,6 +57,7 @@ + From e83e82f8d32e0ccc606301adccc48f6290901f4a Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 14 Jun 2020 20:31:57 +0900 Subject: [PATCH 23/71] Update Context::getUrl() to use Router --- classes/context/Context.class.php | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index 2bf76bda4..5c0c397cc 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -1725,35 +1725,7 @@ class Context $query = ''; if(count($get_vars) > 0) { - // if using rewrite mod - if($rewrite_level) - { - $var_keys = array_keys($get_vars); - sort($var_keys); - $target = join('.', $var_keys); - $act = $get_vars['act']; - $mid = $get_vars['mid']; - $key = $get_vars['key']; - $srl = $get_vars['document_srl']; - $tmpArray = array('rss' => 1, 'atom' => 1, 'api' => 1); - $is_feed = isset($tmpArray[$act]); - $target_map = array( - 'mid' => $mid, - 'category.mid' => "$mid/category/" . $get_vars['category'], - 'entry.mid' => "$mid/entry/" . $get_vars['entry'], - 'document_srl' => $srl, - 'document_srl.mid' => "$mid/$srl", - 'act' => ($is_feed && $act !== 'api') ? $act : '', - 'act.mid' => $is_feed ? "$mid/$act" : '', - 'act.document_srl.key' => ($act == 'trackback') ? "$srl/$key/$act" : '', - 'act.document_srl.key.mid' => ($act == 'trackback') ? "$mid/$srl/$key/$act" : '', - ); - $query = $target_map[$target]; - } - if(!$query && count($get_vars) > 0) - { - $query = 'index.php?' . http_build_query($get_vars); - } + $query = Rhymix\Framework\Router::getURLFromArguments($get_vars, $rewrite_level); } // If using SSL always From f227617fa9d77eac3bc82ba767998f75fc6c2519 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 14 Jun 2020 20:46:01 +0900 Subject: [PATCH 24/71] Fix actions from other modules not being recognized --- common/framework/router.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/framework/router.php b/common/framework/router.php index 69c21eb6c..8d2ba5b06 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -129,7 +129,7 @@ class Router } // Try the generic mid/act pattern. - if (preg_match('#^[a-zA-Z0-9_]+$#', $internal_url) && isset($action_info->action->{$internal_url}) && !$action_info->action->{$internal_url}->route) + if (preg_match('#^[a-zA-Z0-9_]+$#', $internal_url)) { $allargs = array_merge(['mid' => $prefix, 'act' => $internal_url], $args); return $allargs; From ca183c89a0296c403b345178fb81eabac82fcc25 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Tue, 16 Jun 2020 20:55:06 +0900 Subject: [PATCH 25/71] Fix missing variables --- classes/context/Context.class.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index 5c0c397cc..d7d55ff9b 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -246,7 +246,15 @@ class Context // Set information about the current request. self::_checkGlobalVars(); self::setRequestMethod(); - self::setRequestArguments(Rhymix\Framework\Router::getRequestArguments(Rhymix\Framework\Router::getRewriteLevel())); + if (in_array(self::$_instance->request_method, array('GET', 'POST'))) + { + $args = Rhymix\Framework\Router::getRequestArguments(Rhymix\Framework\Router::getRewriteLevel()); + } + else + { + $args = array(); + } + self::setRequestArguments($args); self::setUploadInfo(); // If Rhymix is installed, get virtual site information. @@ -1173,9 +1181,10 @@ class Context /** * handle request arguments for GET/POST * + * @param array $router_args * @return void */ - public static function setRequestArguments($router_args = []) + public static function setRequestArguments(array $router_args = []) { foreach($router_args ?: $_REQUEST as $key => $val) { @@ -1186,7 +1195,7 @@ class Context $key = escape($key); $val = self::_filterRequestVar($key, $val); - $set_to_vars = false; + $set_to_vars = $router_args ? true : false; if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET[$key])) { From a2bd36173721c39e4d45218f4b411e31cbca0c4d Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Tue, 16 Jun 2020 21:33:26 +0900 Subject: [PATCH 26/71] Implement route priority --- .../framework/parsers/moduleactionparser.php | 27 +++++++------- common/framework/router.php | 35 ++++++------------- modules/board/conf/module.xml | 14 +++++--- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index caa02a983..4a3587333 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -55,7 +55,7 @@ class ModuleActionParser $info->permission_check = new \stdClass; // Parse grants. - foreach ($xml->grants->grant as $grant) + foreach ($xml->grants->grant ?: [] as $grant) { $grant_info = new \stdClass; $grant_info->title = self::_getElementsByLang($grant, 'title', $lang); @@ -65,7 +65,7 @@ class ModuleActionParser } // Parse permissions not defined in the section. - foreach ($xml->permissions->permission as $permission) + foreach ($xml->permissions->permission ?: [] as $permission) { $action_name = trim($permission['action']); $permission = trim($permission['target']); @@ -78,7 +78,7 @@ class ModuleActionParser } // Parse menus. - foreach ($xml->menus->menu as $menu) + foreach ($xml->menus->menu ?: [] as $menu) { $menu_info = new \stdClass; $menu_info->title = self::_getElementsByLang($menu, 'title', $lang); @@ -90,7 +90,7 @@ class ModuleActionParser } // Parse actions. - foreach ($xml->actions->action as $action) + foreach ($xml->actions->action ?: [] as $action) { // Parse permissions. $action_name = trim($action['name']); @@ -108,21 +108,23 @@ class ModuleActionParser // Parse routes. $route_attr = trim($action['route']); - $route_tags = $action->route; + $route_tags = $action->route ?: []; $method = trim($action['method']); $route_arg = []; if ($route_attr || count($route_tags)) { $methods = $method ? explode('|', strtoupper($method)) : (starts_with('proc', $action_name) ? ['POST'] : ['GET']); - $routes = $route_attr ? explode_with_escape('|', $route_attr) : array(); + $routes = $route_attr ? array_map(function($route) { + return ['route' => trim($route), 'priority' => 0]; + }, explode_with_escape('|', $route_attr)) : array(); foreach ($route_tags as $route_tag) { - $routes[] = trim($route_tag['route']); + $routes[] = ['route' => trim($route_tag['route']), 'priority' => intval($route_tag['priority'] ?: 0)]; } foreach ($routes as $route) { $route_info = self::analyzeRoute($route); - $route_arg[$route_info->route] = $route_info->vars; + $route_arg[$route_info->route] = ['priority' => intval($route_info->priority), 'vars' => $route_info->vars]; foreach ($methods as $method) { $info->route->{$method}[$route_info->regexp] = $action_name; @@ -177,10 +179,10 @@ class ModuleActionParser /** * Convert route definition into a regular expression. * - * @param string $route + * @param array $route * @return object */ - public static function analyzeRoute(string $route) + public static function analyzeRoute(array $route) { // Replace variables in the route definition into appropriate regexp. $var_regexp = '#\\$([a-zA-Z0-9_]+)(?::(' . implode('|', array_keys(self::$_shortcuts)) . '))?#'; @@ -199,14 +201,15 @@ class ModuleActionParser $named_group = '(?P<' . $match[1] . '>' . $var_pattern . ')'; $vars[$match[1]] = $var_type; return $named_group; - }, $route); + }, $route['route']); // Anchor the regexp at both ends. $regexp = '#^' . strtr($regexp, ['#' => '\\#']) . '$#u'; // Return the regexp and variable list. $result = new \stdClass; - $result->route = preg_replace($var_regexp, '\\$$1', $route); + $result->route = preg_replace($var_regexp, '\\$$1', $route['route']); + $result->priority = $route['priority'] ?: 0; $result->regexp = $regexp; $result->vars = $vars; return $result; diff --git a/common/framework/router.php b/common/framework/router.php index 8d2ba5b06..43388ad01 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -26,10 +26,12 @@ class Router '$mid/$document_srl' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?[0-9]+)$#', 'vars' => ['mid' => 'any', 'document_srl' => 'int'], + 'priority' => 30, ), '$mid/category/$category' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/category/(?[0-9]+)$#', 'vars' => ['mid' => 'any', 'category' => 'int'], + 'priority' => 10, ), '$mid/entry/$entry' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/entry/(?[^/]+)$#', @@ -38,6 +40,7 @@ class Router '$mid/$act' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?rss|atom|api)$#', 'vars' => ['mid' => 'any', 'act' => 'word'], + 'priority' => 20, ), 'files/download/$file_srl/$file_key/$filename' => array( 'regexp' => '#^files/download/(?[0-9]+)/(?[a-zA-Z0-9_-]+)/(?[^/]+)$#', @@ -209,7 +212,7 @@ class Router // Check XE-compatible routes that start with $mid and contain no $act. if (!isset($args['act']) || ($args['act'] === 'rss' || $args['act'] === 'atom' || $args['act'] === 'api')) { - $result = self::_insertRouteVars(self::_getRearrangedGlobalRoutes(), $args2); + $result = self::_insertRouteVars(self::$_global_routes, $args2); if ($result !== false) { return $result; @@ -225,7 +228,7 @@ class Router { if (!isset($args['act']) || ($args['act'] === 'rss' || $args['act'] === 'atom')) { - $result = self::_insertRouteVars(self::_getRearrangedGlobalRoutes(), $args); + $result = self::_insertRouteVars(self::$_global_routes, $args); if ($result !== false) { return $result; @@ -279,24 +282,6 @@ class Router return self::$_action_cache_module[$module] = $action_info ?: false; } - /** - * Get rearranged global routes for argument matching. - * - * @return array - */ - protected static function _getRearrangedGlobalRoutes(): array - { - if (!self::$_rearranged_global_routes) - { - foreach (self::$_global_routes as $route_name => $route_info) - { - self::$_rearranged_global_routes[$route_name] = $route_info['vars']; - } - } - - return self::$_rearranged_global_routes; - } - /** * Insert variables into a route. * @@ -310,8 +295,8 @@ class Router if (count($routes) == 1) { $selected_route = key($routes); - $matched_arguments = array_intersect_key($routes[$selected_route], $vars); - if (count($matched_arguments) !== count($routes[$selected_route])) + $matched_arguments = array_intersect_key($routes[$selected_route]['vars'], $vars); + if (count($matched_arguments) !== count($routes[$selected_route]['vars'])) { return false; } @@ -324,10 +309,10 @@ class Router $reordered_routes = array(); foreach ($routes as $route => $route_vars) { - $matched_arguments = array_intersect_key($route_vars, $vars); - if (count($matched_arguments) === count($route_vars)) + $matched_arguments = array_intersect_key($route_vars['vars'], $vars); + if (count($matched_arguments) === count($route_vars['vars'])) { - $reordered_routes[$route] = count($matched_arguments); + $reordered_routes[$route] = $route_vars['priority'] ?? count($matched_arguments); } } if (!count($reordered_routes)) diff --git a/modules/board/conf/module.xml b/modules/board/conf/module.xml index 80f85709f..9330af666 100644 --- a/modules/board/conf/module.xml +++ b/modules/board/conf/module.xml @@ -57,13 +57,19 @@ - - - + + + + + + + + + - + From 1f11dbe41f61c267acfae27ae73c6412b08ae10f Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Tue, 16 Jun 2020 21:36:54 +0900 Subject: [PATCH 27/71] Fix priority detection when priority is 0 --- common/framework/router.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/framework/router.php b/common/framework/router.php index 43388ad01..5cb6805d1 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -14,14 +14,17 @@ class Router '$mid' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/?$#', 'vars' => ['mid' => 'any'], + 'priority' => 0, ), '$act' => array( 'regexp' => '#^(?rss|atom)$#', 'vars' => ['act' => 'word'], + 'priority' => 0, ), '$document_srl' => array( 'regexp' => '#^(?[0-9]+)$#', 'vars' => ['document_srl' => 'int'], + 'priority' => 0, ), '$mid/$document_srl' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?[0-9]+)$#', @@ -36,6 +39,7 @@ class Router '$mid/entry/$entry' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/entry/(?[^/]+)$#', 'vars' => ['mid' => 'any', 'entry' => 'any'], + 'priority' => 0, ), '$mid/$act' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?rss|atom|api)$#', @@ -46,6 +50,7 @@ class Router 'regexp' => '#^files/download/(?[0-9]+)/(?[a-zA-Z0-9_-]+)/(?[^/]+)$#', 'vars' => ['file_srl' => 'int', 'file_key' => 'any', 'filename' => 'any'], 'extra_vars' => ['act' => 'procFileOutput'], + 'priority' => 0, ), ); @@ -312,7 +317,7 @@ class Router $matched_arguments = array_intersect_key($route_vars['vars'], $vars); if (count($matched_arguments) === count($route_vars['vars'])) { - $reordered_routes[$route] = $route_vars['priority'] ?? count($matched_arguments); + $reordered_routes[$route] = $route_vars['priority'] ?: count($matched_arguments); } } if (!count($reordered_routes)) From 7ac27fffce9a2657a9a1f6038610fc9a5f8a1e63 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Tue, 16 Jun 2020 23:24:47 +0900 Subject: [PATCH 28/71] Reorganize type shortcuts --- common/framework/parsers/moduleactionparser.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index 4a3587333..e091ca1c0 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -11,11 +11,10 @@ class ModuleActionParser * Shortcuts for route definition. */ protected static $_shortcuts = array( - 'int' => '[1-9][0-9]*', + 'int' => '[0-9]+', 'float' => '[0-9]+(?:\.[0-9]+)?', - 'number' => '[0-9]+', 'alpha' => '[a-zA-Z]+', - 'alphanum' => '[a-zA-Z0-9]+', + 'alnum' => '[a-zA-Z0-9]+', 'hex' => '[0-9a-f]+', 'word' => '[a-zA-Z0-9_]+', 'any' => '[^/]+', From c0c11a42ff417a7bbadb22965a46d0a34284fef4 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Tue, 16 Jun 2020 23:30:37 +0900 Subject: [PATCH 29/71] Separate _getBestMatchingRoute() from _insertRouteVars() --- common/framework/router.php | 44 +++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 5cb6805d1..2b1a836ed 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -207,20 +207,20 @@ class Router $action = $action_info->action->{$act} ?? null; if ($action && $action->route) { - $result = self::_insertRouteVars($action->route, $args2); + $result = self::_getBestMatchingRoute($action->route, $args2); if ($result !== false) { - return $args['mid'] . '/' . $result; + return $args['mid'] . '/' . self::_insertRouteVars($result, $args2); } } // Check XE-compatible routes that start with $mid and contain no $act. if (!isset($args['act']) || ($args['act'] === 'rss' || $args['act'] === 'atom' || $args['act'] === 'api')) { - $result = self::_insertRouteVars(self::$_global_routes, $args2); + $result = self::_getBestMatchingRoute(self::$_global_routes, $args2); if ($result !== false) { - return $result; + return self::_insertRouteVars($result, $args2); } } @@ -233,10 +233,10 @@ class Router { if (!isset($args['act']) || ($args['act'] === 'rss' || $args['act'] === 'atom')) { - $result = self::_insertRouteVars(self::$_global_routes, $args); + $result = self::_getBestMatchingRoute(self::$_global_routes, $args); if ($result !== false) { - return $result; + return self::_insertRouteVars($result, $args); } } } @@ -288,23 +288,24 @@ class Router } /** - * Insert variables into a route. + * Find the best matching route for an array of variables. * * @param array $routes * @param array $vars * @return string|false */ - protected static function _insertRouteVars(array $routes, array $vars) + protected static function _getBestMatchingRoute(array $routes, array $vars) { // If the action only has one route, select it. if (count($routes) == 1) { - $selected_route = key($routes); - $matched_arguments = array_intersect_key($routes[$selected_route]['vars'], $vars); - if (count($matched_arguments) !== count($routes[$selected_route]['vars'])) + $only_route = key($routes); + $matched_arguments = array_intersect_key($routes[$only_route]['vars'], $vars); + if (count($matched_arguments) !== count($routes[$only_route]['vars'])) { return false; } + return $only_route; } // If the action has multiple routes, select the one that matches the most arguments. @@ -325,11 +326,22 @@ class Router return false; } arsort($reordered_routes); - $selected_route = array_first_key($reordered_routes); + $best_route = array_first_key($reordered_routes); + return $best_route; } - + } + + /** + * Insert variables into a route. + * + * @param string $route + * @param array $vars + * @return string + */ + protected static function _insertRouteVars(string $route, array $vars): string + { // Replace variable placeholders with actual variable values. - $composed_url = preg_replace_callback('#\\$([a-zA-Z0-9_]+)(?::[a-z]+)?#i', function($match) use(&$vars) { + $route = preg_replace_callback('#\\$([a-zA-Z0-9_]+)(?::[a-z]+)?#i', function($match) use(&$vars) { if (isset($vars[$match[1]])) { $replacement = urlencode($vars[$match[1]]); @@ -340,9 +352,9 @@ class Router { return $match[0]; } - }, $selected_route); + }, $route); // Add a query string for the remaining arguments. - return $composed_url . (count($vars) ? ('?' . http_build_query($vars)) : ''); + return $route . (count($vars) ? ('?' . http_build_query($vars)) : ''); } } From 2feba015f48423ccb5bb4d6ae7ef63eb1749dc21 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Tue, 16 Jun 2020 23:52:13 +0900 Subject: [PATCH 30/71] Store found routes in cache for fast lookup of similar arguments --- common/framework/router.php | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 2b1a836ed..2a453483e 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -55,11 +55,11 @@ class Router ); /** - * Internal cache for module actions. + * Internal cache for module and route information. */ protected static $_action_cache_prefix = array(); protected static $_action_cache_module = array(); - protected static $_rearranged_global_routes = array(); + protected static $_route_cache = array(); /** * Return the currently configured rewrite level. @@ -191,6 +191,14 @@ class Router return urlencode($args[$keys[0]]); } + // If the list of keys is already cached, return the corresponding route. + $keys_sorted = $keys; sort($keys_sorted); + $keys_string = implode('.', $keys_sorted) . ':' . ($args['mid'] ?? '') . ':' . ($args['act'] ?? ''); + if (isset(self::$_route_cache[$keys_string])) + { + return self::_insertRouteVars(self::$_route_cache[$keys_string], $args); + } + // If $mid exists, try routes defined in the module. if ($rewrite_level >= 2 && isset($args['mid'])) { @@ -210,6 +218,7 @@ class Router $result = self::_getBestMatchingRoute($action->route, $args2); if ($result !== false) { + self::$_route_cache[$keys_string] = '$mid/' . $result . '$act:delete'; return $args['mid'] . '/' . self::_insertRouteVars($result, $args2); } } @@ -220,11 +229,13 @@ class Router $result = self::_getBestMatchingRoute(self::$_global_routes, $args2); if ($result !== false) { + self::$_route_cache[$keys_string] = $result; return self::_insertRouteVars($result, $args2); } } // Try the generic mid/act pattern. + self::$_route_cache[$keys_string] = '$mid/$act'; return $args['mid'] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); } @@ -236,12 +247,14 @@ class Router $result = self::_getBestMatchingRoute(self::$_global_routes, $args); if ($result !== false) { + self::$_route_cache[$keys_string] = $result; return self::_insertRouteVars($result, $args); } } } // If no route matches, just create a query string. + self::$_route_cache[$keys_string] = 'index.php'; return 'index.php?' . http_build_query($args); } @@ -341,12 +354,12 @@ class Router protected static function _insertRouteVars(string $route, array $vars): string { // Replace variable placeholders with actual variable values. - $route = preg_replace_callback('#\\$([a-zA-Z0-9_]+)(?::[a-z]+)?#i', function($match) use(&$vars) { + $route = preg_replace_callback('#\\$([a-zA-Z0-9_]+)(:[a-z]+)?#i', function($match) use(&$vars) { if (isset($vars[$match[1]])) { $replacement = urlencode($vars[$match[1]]); unset($vars[$match[1]]); - return $replacement; + return (isset($match[2]) && $match[2] === ':delete') ? '' : $replacement; } else { From bb3d1f08a1ac8fae43f3cd2947451024a4190881 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 00:18:10 +0900 Subject: [PATCH 31/71] Allow standalone routes from other modules to be used --- common/framework/router.php | 41 +++++++++++++++++++++++++++++++++ modules/member/conf/module.xml | 16 ++++++------- modules/module/module.model.php | 25 +++++++++++++++++++- 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 2a453483e..e18a891a0 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -136,6 +136,18 @@ class Router } } + // Check other modules. + $all_routes = self::_getAllCachedRoutes(); + foreach ($all_routes->{$method} as $regexp => $action) + { + if (preg_match($regexp, $internal_url, $matches)) + { + $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); + $allargs = array_merge(['mid' => $prefix, 'act' => $action[1]], $matches, $args); + return $allargs; + } + } + // Try the generic mid/act pattern. if (preg_match('#^[a-zA-Z0-9_]+$#', $internal_url)) { @@ -223,6 +235,18 @@ class Router } } + // Check other modules for $act. + $all_routes = self::_getAllCachedRoutes(); + if (isset($all_routes->reverse[$act])) + { + $result = self::_getBestMatchingRoute($all_routes->reverse[$act], $args2); + if ($result !== false) + { + self::$_route_cache[$keys_string] = '$mid/' . $result . '$act:delete'; + return $args['mid'] . '/' . self::_insertRouteVars($result, $args2); + } + } + // Check XE-compatible routes that start with $mid and contain no $act. if (!isset($args['act']) || ($args['act'] === 'rss' || $args['act'] === 'atom' || $args['act'] === 'api')) { @@ -300,6 +324,23 @@ class Router return self::$_action_cache_module[$module] = $action_info ?: false; } + /** + * Get the list of all cached routes from all modules. + * + * @return object + */ + protected static function _getAllCachedRoutes() + { + $cache_key = 'site_and_module:action_with_routes'; + $result = Cache::get($cache_key); + if ($result === null) + { + $result = (object)array('GET' => [], 'POST' => [], 'reverse' => []); + Cache::set($cache_key, $result, 0, true); + } + return $result; + } + /** * Find the best matching route for an array of variables. * diff --git a/modules/member/conf/module.xml b/modules/member/conf/module.xml index 446984c48..ab494595a 100644 --- a/modules/member/conf/module.xml +++ b/modules/member/conf/module.xml @@ -2,20 +2,20 @@ - - + + - + - - - - - + + + + + diff --git a/modules/module/module.model.php b/modules/module/module.model.php index a2108437b..260983caa 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -723,13 +723,36 @@ class moduleModel extends module $info = Rhymix\Framework\Cache::get($cache_key); if($info === null) { + // Load the XML file. $info = Rhymix\Framework\Parsers\ModuleActionParser::loadXML($xml_file); + + // Add all routes from the module to a global list. + $action_cache_key = 'site_and_module:action_with_routes'; + $action_with_routes = Rhymix\Framework\Cache::get($action_cache_key) ?: (object)array('GET' => [], 'POST' => [], 'reverse' => []); + foreach ($info->route->GET as $regexp => $action) + { + $action_with_routes->GET[$regexp] = [$module, $action]; + } + foreach ($info->route->POST as $regexp => $action) + { + $action_with_routes->POST[$regexp] = [$module, $action]; + } + foreach ($info->action as $action_name => $action_info) + { + if (count($action_info->route) && $action_info->standalone !== 'false') + { + $action_with_routes->reverse[$action_name] = $action_info->route; + } + } + + // Set cache entries. + Rhymix\Framework\Cache::set($action_cache_key, $action_with_routes, 0, true); Rhymix\Framework\Cache::set($cache_key, $info, 0, true); } return $info; } - + /** * Get a skin list for js API. * return void From a814b4e33452664f02ee5ccfa1edffcd5e2b2b3b Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 12:33:21 +0900 Subject: [PATCH 32/71] Improve parsing of route definitions and deleted vars --- .../framework/parsers/moduleactionparser.php | 7 ++-- common/framework/router.php | 3 +- modules/board/conf/module.xml | 34 ++++++++++++------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index e091ca1c0..96b2acb1d 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -18,6 +18,7 @@ class ModuleActionParser 'hex' => '[0-9a-f]+', 'word' => '[a-zA-Z0-9_]+', 'any' => '[^/]+', + 'delete' => '[^/]+', ); /** @@ -194,7 +195,7 @@ class ModuleActionParser } else { - $var_type = ends_with('_srl', $match[1]) ? 'number' : 'any'; + $var_type = ends_with('_srl', $match[1]) ? 'int' : 'any'; $var_pattern = self::$_shortcuts[$var_type]; } $named_group = '(?P<' . $match[1] . '>' . $var_pattern . ')'; @@ -207,7 +208,9 @@ class ModuleActionParser // Return the regexp and variable list. $result = new \stdClass; - $result->route = preg_replace($var_regexp, '\\$$1', $route['route']); + $result->route = preg_replace_callback($var_regexp, function($match) { + return '$' . ((isset($match[2]) && $match[2] === 'delete') ? ($match[1] . ':' . $match[2]) : $match[1]); + }, $route['route']); $result->priority = $route['priority'] ?: 0; $result->regexp = $regexp; $result->vars = $vars; diff --git a/common/framework/router.php b/common/framework/router.php index e18a891a0..e3edcd78a 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -120,7 +120,6 @@ class Router // Separate the prefix and the internal part of the URL. $prefix = $matches[1]; $internal_url = $matches[2] ?? ''; - // Find the module associated with this prefix. $action_info = self::_getActionInfoByPrefix($prefix); if ($action_info) @@ -372,7 +371,7 @@ class Router $matched_arguments = array_intersect_key($route_vars['vars'], $vars); if (count($matched_arguments) === count($route_vars['vars'])) { - $reordered_routes[$route] = $route_vars['priority'] ?: count($matched_arguments); + $reordered_routes[$route] = ($route_vars['priority'] * 1000) + count($matched_arguments); } } if (!count($reordered_routes)) diff --git a/modules/board/conf/module.xml b/modules/board/conf/module.xml index 9330af666..6c14d204c 100644 --- a/modules/board/conf/module.xml +++ b/modules/board/conf/module.xml @@ -57,25 +57,35 @@ - - - - - - - - + + + + + + + + + - + - - - + + + + + + + + + + + + From a08ec31f2bf77bb453f9c7c7fcba4b7defed4f25 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 12:37:19 +0900 Subject: [PATCH 33/71] Remove unnecessary variables from route --- common/framework/router.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/framework/router.php b/common/framework/router.php index e3edcd78a..6261195bf 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -403,7 +403,7 @@ class Router } else { - return $match[0]; + return ''; } }, $route); From 5dcb0d332bf28a8e2c49fe72488eb1cf0b04a24e Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 15:52:06 +0900 Subject: [PATCH 34/71] Extend action_forward feature to include route info --- modules/module/module.class.php | 19 +++++++++++++++++++ modules/module/module.controller.php | 6 ++++-- .../module/queries/insertActionForward.xml | 3 +++ modules/module/schemas/action_forward.xml | 5 ++++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/modules/module/module.class.php b/modules/module/module.class.php index 392db552c..248df4c92 100644 --- a/modules/module/module.class.php +++ b/modules/module/module.class.php @@ -131,6 +131,11 @@ class module extends ModuleObject { return true; } + + // check route columns in action_forward table + if(!$oDB->isColumnExists('action_forward', 'route_method')) return true; + if(!$oDB->isColumnExists('action_forward', 'route_regexp')) return true; + if(!$oDB->isColumnExists('action_forward', 'route_config')) return true; } /** @@ -448,6 +453,20 @@ class module extends ModuleObject $oDB->addIndex('module_part_config', 'unique_module_part_config', array('module', 'module_srl'), false); } } + + // check route columns in action_forward table + if(!$oDB->isColumnExists('action_forward', 'route_method')) + { + $oDB->addColumn('action_forward', 'route_method', 'varchar', 30); + } + if(!$oDB->isColumnExists('action_forward', 'route_regexp')) + { + $oDB->addColumn('action_forward', 'route_regexp', 'varchar', 180); + } + if(!$oDB->isColumnExists('action_forward', 'route_config')) + { + $oDB->addColumn('action_forward', 'route_config', 'text'); + } } /** diff --git a/modules/module/module.controller.php b/modules/module/module.controller.php index 3b2132044..8e196c11c 100644 --- a/modules/module/module.controller.php +++ b/modules/module/module.controller.php @@ -19,13 +19,15 @@ class moduleController extends module * Action forward finds and forwards if an action is not in the requested module * This is used when installing a module */ - function insertActionForward($module, $type, $act) + function insertActionForward($module, $type, $act, $route_method = null, $route_regexp = null, $route_config = null) { $args = new stdClass(); $args->module = $module; $args->type = $type; $args->act = $act; - + $args->route_method = $route_method; + $args->route_regexp = $route_regexp; + $args->route_config = $route_config; $output = executeQuery('module.insertActionForward', $args); Rhymix\Framework\Cache::delete('action_forward'); diff --git a/modules/module/queries/insertActionForward.xml b/modules/module/queries/insertActionForward.xml index ef48a8e2b..20d4d52ec 100644 --- a/modules/module/queries/insertActionForward.xml +++ b/modules/module/queries/insertActionForward.xml @@ -6,5 +6,8 @@ + + + diff --git a/modules/module/schemas/action_forward.xml b/modules/module/schemas/action_forward.xml index 608bae8c8..c14824218 100644 --- a/modules/module/schemas/action_forward.xml +++ b/modules/module/schemas/action_forward.xml @@ -1,5 +1,8 @@ - + + + +
From f92fc9c98083091534ab646402a364afd8c793b3 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 16:56:42 +0900 Subject: [PATCH 35/71] Use action_forward to register standalone routes from all modules --- common/framework/router.php | 41 ++++++---- modules/install/install.admin.controller.php | 15 +++- modules/module/module.class.php | 7 +- modules/module/module.controller.php | 75 +++++++++++++++++- modules/module/module.model.php | 79 +++++++++++-------- .../module/queries/insertActionForward.xml | 1 - modules/module/schemas/action_forward.xml | 3 +- 7 files changed, 162 insertions(+), 59 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 6261195bf..fb977fd74 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -59,6 +59,7 @@ class Router */ protected static $_action_cache_prefix = array(); protected static $_action_cache_module = array(); + protected static $_forwarded_cache = array(); protected static $_route_cache = array(); /** @@ -136,8 +137,8 @@ class Router } // Check other modules. - $all_routes = self::_getAllCachedRoutes(); - foreach ($all_routes->{$method} as $regexp => $action) + $forwarded_routes = self::_getForwardedRoutes(); + foreach ($forwarded_routes[$method] ?: [] as $regexp => $action) { if (preg_match($regexp, $internal_url, $matches)) { @@ -235,10 +236,10 @@ class Router } // Check other modules for $act. - $all_routes = self::_getAllCachedRoutes(); - if (isset($all_routes->reverse[$act])) + $forwarded_routes = self::_getForwardedRoutes(); + if (isset($forwarded_routes['reverse'][$act])) { - $result = self::_getBestMatchingRoute($all_routes->reverse[$act], $args2); + $result = self::_getBestMatchingRoute($forwarded_routes['reverse'][$act], $args2); if ($result !== false) { self::$_route_cache[$keys_string] = '$mid/' . $result . '$act:delete'; @@ -324,20 +325,30 @@ class Router } /** - * Get the list of all cached routes from all modules. + * Get the list of routes that are registered for action-forward. * * @return object */ - protected static function _getAllCachedRoutes() + protected static function _getForwardedRoutes() { - $cache_key = 'site_and_module:action_with_routes'; - $result = Cache::get($cache_key); - if ($result === null) - { - $result = (object)array('GET' => [], 'POST' => [], 'reverse' => []); - Cache::set($cache_key, $result, 0, true); - } - return $result; + if (count(self::$_forwarded_cache)) + { + return self::$_forwarded_cache; + } + + $action_forward = \ModuleModel::getActionForward(); + foreach ($action_forward as $action_name => $action_info) + { + if ($action_info->route_regexp) + { + foreach ($action_info->route_regexp as $regexp_info) + { + self::$_forwarded_cache[$regexp_info[0]][$regexp_info[1]] = [$action_info->module, $action_name]; + } + self::$_forwarded_cache['reverse'][$action_name] = $action_info->route_config; + } + } + return self::$_forwarded_cache; } /** diff --git a/modules/install/install.admin.controller.php b/modules/install/install.admin.controller.php index 931933410..0f757520a 100644 --- a/modules/install/install.admin.controller.php +++ b/modules/install/install.admin.controller.php @@ -24,7 +24,8 @@ class installAdminController extends install $oInstallController = getController('install'); $oInstallController->installModule($module_name, './modules/'.$module_name); - + $oModuleController = getController('module'); + $oModuleController->registerActionForwardRoutes($module_name); $this->setMessage('success_installed'); } @@ -41,13 +42,23 @@ class installAdminController extends install if(!$oModule) throw new Rhymix\Framework\Exceptions\InvalidRequest; Rhymix\Framework\Session::close(); + $output = $oModule->moduleUpdate(); - Rhymix\Framework\Session::start(); if($output instanceof BaseObject && !$output->toBool()) { + Rhymix\Framework\Session::start(); return $output; } + $oModuleController = getController('module'); + $oModuleController->registerActionForwardRoutes($module_name); + if($output instanceof BaseObject && !$output->toBool()) + { + Rhymix\Framework\Session::start(); + return $output; + } + + Rhymix\Framework\Session::start(); $this->setMessage('success_updated'); } diff --git a/modules/module/module.class.php b/modules/module/module.class.php index 248df4c92..0b6060562 100644 --- a/modules/module/module.class.php +++ b/modules/module/module.class.php @@ -133,7 +133,6 @@ class module extends ModuleObject } // check route columns in action_forward table - if(!$oDB->isColumnExists('action_forward', 'route_method')) return true; if(!$oDB->isColumnExists('action_forward', 'route_regexp')) return true; if(!$oDB->isColumnExists('action_forward', 'route_config')) return true; } @@ -455,13 +454,9 @@ class module extends ModuleObject } // check route columns in action_forward table - if(!$oDB->isColumnExists('action_forward', 'route_method')) - { - $oDB->addColumn('action_forward', 'route_method', 'varchar', 30); - } if(!$oDB->isColumnExists('action_forward', 'route_regexp')) { - $oDB->addColumn('action_forward', 'route_regexp', 'varchar', 180); + $oDB->addColumn('action_forward', 'route_regexp', 'text'); } if(!$oDB->isColumnExists('action_forward', 'route_config')) { diff --git a/modules/module/module.controller.php b/modules/module/module.controller.php index 8e196c11c..6ab018d5f 100644 --- a/modules/module/module.controller.php +++ b/modules/module/module.controller.php @@ -19,13 +19,12 @@ class moduleController extends module * Action forward finds and forwards if an action is not in the requested module * This is used when installing a module */ - function insertActionForward($module, $type, $act, $route_method = null, $route_regexp = null, $route_config = null) + function insertActionForward($module, $type, $act, $route_regexp = null, $route_config = null) { $args = new stdClass(); $args->module = $module; $args->type = $type; $args->act = $act; - $args->route_method = $route_method; $args->route_regexp = $route_regexp; $args->route_config = $route_config; $output = executeQuery('module.insertActionForward', $args); @@ -1291,6 +1290,78 @@ class moduleController extends module Rhymix\Framework\Cache::clearGroup('site_and_module'); return $output; } + + /** + * Check if all action-forwardable routes are registered. If not, register them. + * + * @param string $module_name + * @return object + */ + public function registerActionForwardRoutes(string $module_name) + { + $action_forward = ModuleModel::getActionForward(); + $module_action_info = ModuleModel::getModuleActionXml($module_name); + + // Get the list of forwardable actions and their routes. + $forwardable_routes = array(); + foreach ($module_action_info->action ?: [] as $action_name => $action_info) + { + if (count($action_info->route) && $action_info->standalone !== 'false') + { + $forwardable_routes[$action_name] = array( + 'type' => $module_action_info->action->{$action_name}->type, + 'regexp' => array(), + 'config' => $action_info->route, + ); + } + } + foreach ($module_action_info->route->GET as $regexp => $action_name) + { + if (isset($forwardable_routes[$action_name])) + { + $forwardable_routes[$action_name]['regexp'][] = ['GET', $regexp]; + } + } + foreach ($module_action_info->route->POST as $regexp => $action_name) + { + if (isset($forwardable_routes[$action_name])) + { + $forwardable_routes[$action_name]['regexp'][] = ['POST', $regexp]; + } + } + + // Insert or delete from the action_forward table. + foreach ($forwardable_routes as $action_name => $route_info) + { + if (!isset($action_forward[$action_name])) + { + $output = $this->insertActionForward($module_name, $route_info['type'], $action_name, + serialize($route_info['regexp']), serialize($route_info['config'])); + if (!$output->toBool()) + { + return $output; + } + } + elseif ($action_forward[$action_name]->route_regexp !== $route_info['regexp'] || + $action_forward[$action_name]->route_config !== $route_info['config']) + { + $output = $this->deleteActionForward($module_name, $route_info['type'], $action_name); + if (!$output->toBool()) + { + return $output; + } + + $output = $this->insertActionForward($module_name, $route_info['type'], $action_name, + serialize($route_info['regexp']), serialize($route_info['config'])); + if (!$output->toBool()) + { + return $output; + } + } + } + + return new BaseObject(); + } } /* End of file module.controller.php */ /* Location: ./modules/module/module.controller.php */ diff --git a/modules/module/module.model.php b/modules/module/module.model.php index 260983caa..e5cf24234 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -567,7 +567,7 @@ class moduleModel extends module /** * @brief Get forward value by the value of act */ - public static function getActionForward($act) + public static function getActionForward($act = null) { $action_forward = Rhymix\Framework\Cache::get('action_forward'); if($action_forward === null) @@ -582,12 +582,18 @@ class moduleModel extends module $action_forward = array(); foreach($output->data as $item) { + if ($item->route_regexp) $item->route_regexp = unserialize($item->route_regexp); + if ($item->route_config) $item->route_config = unserialize($item->route_config); $action_forward[$item->act] = $item; } Rhymix\Framework\Cache::set('action_forward', $action_forward, 0, true); } + if(!isset($act)) + { + return $action_forward; + } if(!isset($action_forward[$act])) { return; @@ -723,30 +729,7 @@ class moduleModel extends module $info = Rhymix\Framework\Cache::get($cache_key); if($info === null) { - // Load the XML file. $info = Rhymix\Framework\Parsers\ModuleActionParser::loadXML($xml_file); - - // Add all routes from the module to a global list. - $action_cache_key = 'site_and_module:action_with_routes'; - $action_with_routes = Rhymix\Framework\Cache::get($action_cache_key) ?: (object)array('GET' => [], 'POST' => [], 'reverse' => []); - foreach ($info->route->GET as $regexp => $action) - { - $action_with_routes->GET[$regexp] = [$module, $action]; - } - foreach ($info->route->POST as $regexp => $action) - { - $action_with_routes->POST[$regexp] = [$module, $action]; - } - foreach ($info->action as $action_name => $action_info) - { - if (count($action_info->route) && $action_info->standalone !== 'false') - { - $action_with_routes->reverse[$action_name] = $action_info->route; - } - } - - // Set cache entries. - Rhymix\Framework\Cache::set($action_cache_key, $action_with_routes, 0, true); Rhymix\Framework\Cache::set($cache_key, $info, 0, true); } @@ -1361,12 +1344,12 @@ class moduleModel extends module $searched_count = count($searched_list); if(!$searched_count) return; + + // Get action forward + $action_forward = self::getActionForward(); - for($i=0;$i<$searched_count;$i++) + foreach ($searched_list as $module_name) { - // module name - $module_name = $searched_list[$i]; - $path = ModuleHandler::getModulePath($module_name); if(!is_dir(FileHandler::getRealPath($path))) continue; @@ -1403,16 +1386,50 @@ class moduleModel extends module { $info->need_install = false; } + // Check if it is upgraded to module.class.php on each module - $oDummy = null; $oDummy = getModule($module_name, 'class'); if($oDummy && method_exists($oDummy, "checkUpdate")) { $info->need_update = $oDummy->checkUpdate(); } - else + unset($oDummy); + + // Check if all action-forwardable routes are registered + $module_action_info = self::getModuleActionXml($module_name); + $forwardable_routes = array(); + foreach ($module_action_info->action ?: [] as $action_name => $action_info) { - continue; + if (count($action_info->route) && $action_info->standalone !== 'false') + { + $forwardable_routes[$action_name] = array( + 'regexp' => array(), + 'config' => $action_info->route, + ); + } + } + foreach ($module_action_info->route->GET as $regexp => $action_name) + { + if (isset($forwardable_routes[$action_name])) + { + $forwardable_routes[$action_name]['regexp'][] = ['GET', $regexp]; + } + } + foreach ($module_action_info->route->POST as $regexp => $action_name) + { + if (isset($forwardable_routes[$action_name])) + { + $forwardable_routes[$action_name]['regexp'][] = ['POST', $regexp]; + } + } + foreach ($forwardable_routes as $action_name => $route_info) + { + if (!isset($action_forward[$action_name]) || + $action_forward[$action_name]->route_regexp !== $route_info['regexp'] || + $action_forward[$action_name]->route_config !== $route_info['config']) + { + $info->need_update = true; + } } } $list[] = $info; diff --git a/modules/module/queries/insertActionForward.xml b/modules/module/queries/insertActionForward.xml index 20d4d52ec..d2cb1bc68 100644 --- a/modules/module/queries/insertActionForward.xml +++ b/modules/module/queries/insertActionForward.xml @@ -6,7 +6,6 @@ - diff --git a/modules/module/schemas/action_forward.xml b/modules/module/schemas/action_forward.xml index c14824218..2a1989a89 100644 --- a/modules/module/schemas/action_forward.xml +++ b/modules/module/schemas/action_forward.xml @@ -2,7 +2,6 @@ - - + From 3bbb175cd11f8272ec15480c67a4b9beb82e5791 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 16:56:55 +0900 Subject: [PATCH 36/71] Fix missing action_forward in integration_search module --- .../integration_search/integration_search.class.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/integration_search/integration_search.class.php b/modules/integration_search/integration_search.class.php index 93adb3d97..3d2ff0c17 100644 --- a/modules/integration_search/integration_search.class.php +++ b/modules/integration_search/integration_search.class.php @@ -38,6 +38,11 @@ class integration_search extends ModuleObject if(is_dir($template_path)) return true; } } + + if (!$oModuleModel->getActionForward('IS')) + { + return true; + } return false; } @@ -65,6 +70,12 @@ class integration_search extends ModuleObject } } } + + if (!$oModuleModel->getActionForward('IS')) + { + $oModuleController = getController('module'); + $oModuleController->insertActionForward('integration_search', 'view', 'IS'); + } } /** From 480635d1315690ea56610e679e8294a8736f94f1 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 18:38:26 +0900 Subject: [PATCH 37/71] Fix relative URLs in form actions and other HTML attributes --- classes/display/HTMLDisplayHandler.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/classes/display/HTMLDisplayHandler.php b/classes/display/HTMLDisplayHandler.php index 8023f74fa..1ac2f19cd 100644 --- a/classes/display/HTMLDisplayHandler.php +++ b/classes/display/HTMLDisplayHandler.php @@ -204,10 +204,13 @@ class HTMLDisplayHandler // handles a relative path generated by using the rewrite module if(Context::isAllowRewrite()) { - $pattern = '/src=("|\'){1}(?:\.\/)?((?:files\/(?:attach|cache|faceOff|member_extra_info|thumbnails)|addons|common|(?:m\.)?layouts|modules|widgets|widgetstyle)\/[^"\']+)("|\'){1}/s'; + $pattern = '/(action|src|href)=(["\'])\.\/([^"\']*)(["\'])/s'; + $output = preg_replace($pattern, '$1=$2' . \RX_BASEURL . '$3$4', $output); + + $pattern = '/src=(["\'])((?:files\/(?:attach|cache|faceOff|member_extra_info|thumbnails)|addons|common|(?:m\.)?layouts|modules|widgets|widgetstyle)\/[^"\']+)(["\'])/s'; $output = preg_replace($pattern, 'src=$1' . \RX_BASEURL . '$2$3', $output); - $pattern = '/href=("|\'){1}(\?[^"\']+)/s'; + $pattern = '/href=(["\'])(\?[^"\']+)/s'; $output = preg_replace($pattern, 'href=$1' . \RX_BASEURL . '$2', $output); if(Context::get('vid')) From b5740052fccfc3032af67a47136fa60373169eaa Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 22:25:40 +0900 Subject: [PATCH 38/71] Move SSL redirection logic to Context::init() --- classes/context/Context.class.php | 70 +++++++------------ classes/module/ModuleHandler.class.php | 16 ----- .../framework/parsers/moduleactionparser.php | 1 + modules/board/board.class.php | 12 ---- modules/board/conf/module.xml | 28 ++++---- modules/document/conf/module.xml | 2 +- modules/install/install.admin.controller.php | 9 ++- modules/member/conf/module.xml | 56 +++++++-------- modules/member/member.class.php | 9 --- modules/module/module.controller.php | 48 +++++++++++++ modules/module/module.model.php | 44 ++++++++++++ .../module/queries/deleteActionSecurity.xml | 8 +++ modules/module/queries/getActionSecurity.xml | 11 +++ .../module/queries/insertActionSecurity.xml | 8 +++ modules/module/schemas/action_security.xml | 3 + 15 files changed, 199 insertions(+), 126 deletions(-) create mode 100644 modules/module/queries/deleteActionSecurity.xml create mode 100644 modules/module/queries/getActionSecurity.xml create mode 100644 modules/module/queries/insertActionSecurity.xml create mode 100644 modules/module/schemas/action_security.xml diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index d7d55ff9b..cc89b35c9 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -133,12 +133,6 @@ class Context */ private static $_oFrontEndFileHandler = null; - /** - * SSL action cache file - * @var array - */ - private static $_ssl_actions_cache_file = 'files/cache/common/ssl_actions.php'; - /** * SSL action cache */ @@ -199,13 +193,6 @@ class Context self::$_oFrontEndFileHandler = self::$_instance->oFrontEndFileHandler = new FrontEndFileHandler(); self::$_get_vars = self::$_get_vars ?: new stdClass; self::$_tpl_vars = self::$_tpl_vars ?: new stdClass; - - // Include SSL action cache file. - self::$_ssl_actions_cache_file = RX_BASEDIR . self::$_ssl_actions_cache_file; - if(Rhymix\Framework\Storage::exists(self::$_ssl_actions_cache_file)) - { - self::$_ssl_actions = (include self::$_ssl_actions_cache_file) ?: array(); - } } return self::$_instance; } @@ -287,9 +274,19 @@ class Context } // Redirect to SSL if the current domain always uses SSL. - if ($site_module_info->security === 'always' && !RX_SSL && PHP_SAPI !== 'cli' && !$site_module_info->is_default_replaced) + if (!RX_SSL && PHP_SAPI !== 'cli' && $site_module_info->security === 'always' && !$site_module_info->is_default_replaced) { - $ssl_url = self::getDefaultUrl($site_module_info) . RX_REQUEST_URL; + $ssl_url = self::getDefaultUrl($site_module_info, true) . RX_REQUEST_URL; + self::setCacheControl(0); + header('Location: ' . $ssl_url, true, 301); + exit; + } + + // Redirect to SSL if the current action requires SSL. + self::$_ssl_actions = $site_module_info->security === 'optional' ? ModuleModel::getActionSecurity() : array(); + if (!RX_SSL && count(self::$_ssl_actions) && self::isExistsSSLAction(self::get('act')) && self::getRequestMethod() === 'GET') + { + $ssl_url = self::getDefaultUrl($site_module_info, true) . RX_REQUEST_URL; self::setCacheControl(0); header('Location: ' . $ssl_url, true, 301); exit; @@ -619,7 +616,7 @@ class Context * * @return object SSL status (Optional - none|always|optional) */ - public static function getSslStatus() + public static function getSSLStatus() { return self::get('_use_ssl'); } @@ -628,9 +625,10 @@ class Context * Return default URL * * @param object $site_module_info (optional) + * @param bool $use_ssl (optional) * @return string Default URL */ - public static function getDefaultUrl($site_module_info = null) + public static function getDefaultUrl($site_module_info = null, $use_ssl = null) { if ($site_module_info === null && ($default_url = self::get('_default_url'))) { @@ -642,9 +640,9 @@ class Context $site_module_info = self::get('site_module_info'); } - $prefix = $site_module_info->security === 'always' ? 'https://' : 'http://'; + $prefix = ($site_module_info->security === 'always' || $use_ssl) ? 'https://' : 'http://'; $hostname = $site_module_info->domain; - $port = $site_module_info->security === 'always' ? $site_module_info->https_port : $site_module_info->http_port; + $port = ($prefix === 'https://') ? $site_module_info->https_port : $site_module_info->http_port; $result = $prefix . $hostname . ($port ? sprintf(':%d', $port) : '') . RX_BASEURL; return $result; } @@ -1993,40 +1991,25 @@ class Context */ public static function addSSLAction($action) { - if(isset(self::$_ssl_actions[$action])) + if (!ModuleModel::getActionSecurity($action)) { - return; + getController('module')->insertActionSecurity($action); } - - self::$_ssl_actions[$action] = 1; - $buff = 'deleteActionSecurity($action); } - unset(self::$_ssl_actions[$action]); - $buff = 'act) && (strlen($this->act) >= 4 && substr_compare($this->act, 'disp', 0, 4) === 0)) - { - if(Context::get('_use_ssl') == 'optional' && Context::isExistsSSLAction($this->act) && !RX_SSL) - { - if(Context::get('_https_port') != null) - { - header('location: https://' . $_SERVER['HTTP_HOST'] . ':' . Context::get('_https_port') . $_SERVER['REQUEST_URI']); - } - else - { - header('location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); - } - return; - } - } - // call a trigger before moduleHandler init self::triggerCall('moduleHandler.init', 'before', $this); diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index 96b2acb1d..a7fa01849 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -142,6 +142,7 @@ class ModuleActionParser $action_info->standalone = trim($action['standalone']) === 'false' ? 'false' : 'true'; $action_info->check_csrf = (trim($action['check_csrf']) ?: trim($action['check-csrf'])) === 'false' ? 'false' : 'true'; $action_info->meta_noindex = (trim($action['meta_noindex']) ?: trim($action['meta-noindex'])) === 'true' ? 'true' : 'false'; + $action_info->use_ssl = (trim($action['use_ssl']) ?: trim($action['use-ssl'])) === 'true' ? 'true' : 'false'; $info->action->{$action_name} = $action_info; // Set the menu name and index settings. diff --git a/modules/board/board.class.php b/modules/board/board.class.php index ee1e7db20..30ef25e63 100644 --- a/modules/board/board.class.php +++ b/modules/board/board.class.php @@ -25,18 +25,6 @@ class board extends ModuleObject */ function __construct() { - if(!Context::isInstalled()) return; - - if(!Context::isExistsSSLAction('dispBoardWrite') && Context::getSslStatus() == 'optional') - { - $ssl_actions = array('dispBoardWrite', 'dispBoardWriteComment', 'dispBoardReplyComment', 'dispBoardModifyComment', 'dispBoardDelete', 'dispBoardDeleteComment', 'procBoardInsertDocument', 'procBoardDeleteDocument', 'procBoardInsertComment', 'procBoardDeleteComment', 'procBoardVerificationPassword'); - Context::addSSLActions($ssl_actions); - } - if(!Context::isExistsSSLAction('dispTempSavedList') && Context::getSslStatus() == 'optional') - { - Context::addSSLAction('dispTempSavedList'); - } - parent::__construct(); } diff --git a/modules/board/conf/module.xml b/modules/board/conf/module.xml index 6c14d204c..e08b47b9b 100644 --- a/modules/board/conf/module.xml +++ b/modules/board/conf/module.xml @@ -68,25 +68,25 @@
- + - - - + + + - + - + - + @@ -102,13 +102,13 @@ - - - - - - - + + + + + + + diff --git a/modules/document/conf/module.xml b/modules/document/conf/module.xml index 9ae70f820..3d5221aee 100644 --- a/modules/document/conf/module.xml +++ b/modules/document/conf/module.xml @@ -4,7 +4,7 @@ - + diff --git a/modules/install/install.admin.controller.php b/modules/install/install.admin.controller.php index 0f757520a..5ebd34302 100644 --- a/modules/install/install.admin.controller.php +++ b/modules/install/install.admin.controller.php @@ -26,6 +26,7 @@ class installAdminController extends install $oInstallController->installModule($module_name, './modules/'.$module_name); $oModuleController = getController('module'); $oModuleController->registerActionForwardRoutes($module_name); + $oModuleController->registerSecureActions($module_name); $this->setMessage('success_installed'); } @@ -51,7 +52,13 @@ class installAdminController extends install } $oModuleController = getController('module'); - $oModuleController->registerActionForwardRoutes($module_name); + $output = $oModuleController->registerActionForwardRoutes($module_name); + if($output instanceof BaseObject && !$output->toBool()) + { + Rhymix\Framework\Session::start(); + return $output; + } + $output = $oModuleController->registerSecureActions($module_name); if($output instanceof BaseObject && !$output->toBool()) { Rhymix\Framework\Session::start(); diff --git a/modules/member/conf/module.xml b/modules/member/conf/module.xml index ab494595a..c80ab9e9b 100644 --- a/modules/member/conf/module.xml +++ b/modules/member/conf/module.xml @@ -2,42 +2,42 @@ - - - - + + + + - - - - - - - - - - + + + + + + + + + + - + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/modules/member/member.class.php b/modules/member/member.class.php index a69fb96be..07ea21682 100644 --- a/modules/member/member.class.php +++ b/modules/member/member.class.php @@ -20,15 +20,6 @@ class member extends ModuleObject { */ function __construct() { - if(!Context::isInstalled()) return; - - // Set to use SSL upon actions related member join/information/password and so on. 2013.02.15 - if(!Context::isExistsSSLAction('dispMemberModifyPassword') && Context::getSslStatus() == 'optional') - { - $ssl_actions = array('dispMemberModifyPassword', 'dispMemberSignUpForm', 'dispMemberModifyInfo', 'dispMemberModifyEmailAddress', 'dispMemberResendAuthMail', 'dispMemberLoginForm', 'dispMemberFindAccount', 'dispMemberLeave', 'procMemberLogin', 'procMemberModifyPassword', 'procMemberInsert', 'procMemberModifyInfo', 'procMemberFindAccount', 'procMemberModifyEmailAddress', 'procMemberResendAuthMail', 'procMemberLeave'/*, 'getMemberMenu'*/, 'procMemberFindAccountByQuestion'); - Context::addSSLActions($ssl_actions); - } - parent::__construct(); } diff --git a/modules/module/module.controller.php b/modules/module/module.controller.php index 6ab018d5f..f930fbe62 100644 --- a/modules/module/module.controller.php +++ b/modules/module/module.controller.php @@ -49,6 +49,32 @@ class moduleController extends module return $output; } + /** + * @brief Add action security + */ + function insertActionSecurity($act) + { + $args = new stdClass(); + $args->act = $act; + $output = executeQuery('module.insertActionSecurity', $args); + + Rhymix\Framework\Cache::delete('action_security'); + return $output; + } + + /** + * @brief Delete action security + */ + function deleteActionSecurity($act) + { + $args = new stdClass(); + $args->act = $act; + $output = executeQuery('module.deleteActionSecurity', $args); + + Rhymix\Framework\Cache::delete('action_security'); + return $output; + } + /** * @brief Add trigger callback function * @@ -1362,6 +1388,28 @@ class moduleController extends module return new BaseObject(); } + + /** + * Check if all secure actions are registered. If not, register them. + * + * @param string $module_name + * @return object + */ + public function registerSecureActions(string $module_name) + { + $action_security = ModuleModel::getActionSecurity(); + $module_action_info = ModuleModel::getModuleActionXml($module_name); + + foreach ($module_action_info->action ?: [] as $action_name => $action_info) + { + if ($action_info->use_ssl === 'true' && !isset($action_security[$action_name])) + { + $output = $this->insertActionSecurity($action_name); + } + } + + return new BaseObject(); + } } /* End of file module.controller.php */ /* Location: ./modules/module/module.controller.php */ diff --git a/modules/module/module.model.php b/modules/module/module.model.php index e5cf24234..a30164a28 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -602,6 +602,38 @@ class moduleModel extends module return $action_forward[$act]; } + /** + * @brief Get SSL action setting + */ + public static function getActionSecurity($act = null) + { + $action_security = Rhymix\Framework\Cache::get('action_security'); + if($action_security === null) + { + $args = new stdClass(); + $output = executeQueryArray('module.getActionSecurity', $args); + if(!$output->toBool()) + { + return; + } + + $action_security = array(); + foreach($output->data as $item) + { + $action_security[$item->act] = true; + } + + Rhymix\Framework\Cache::set('action_security', $action_security, 0, true); + } + + if(!isset($act)) + { + return $action_security; + } + + return isset($action_security[$act]) ? true : false; + } + /** * @brief Get trigger functions */ @@ -1347,6 +1379,9 @@ class moduleModel extends module // Get action forward $action_forward = self::getActionForward(); + + // Get action security + $action_security = self::getActionSecurity(); foreach ($searched_list as $module_name) { @@ -1431,6 +1466,15 @@ class moduleModel extends module $info->need_update = true; } } + + // Check if all secure actions are registered + foreach ($module_action_info->action ?: [] as $action_name => $action_info) + { + if ($action_info->use_ssl === 'true' && !isset($action_security[$action_name])) + { + $info->need_update = true; + } + } } $list[] = $info; } diff --git a/modules/module/queries/deleteActionSecurity.xml b/modules/module/queries/deleteActionSecurity.xml new file mode 100644 index 000000000..bbf0e9b1e --- /dev/null +++ b/modules/module/queries/deleteActionSecurity.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/modules/module/queries/getActionSecurity.xml b/modules/module/queries/getActionSecurity.xml new file mode 100644 index 000000000..2cdd7eed3 --- /dev/null +++ b/modules/module/queries/getActionSecurity.xml @@ -0,0 +1,11 @@ + + +
+ + + + + + + + diff --git a/modules/module/queries/insertActionSecurity.xml b/modules/module/queries/insertActionSecurity.xml new file mode 100644 index 000000000..d1cf772be --- /dev/null +++ b/modules/module/queries/insertActionSecurity.xml @@ -0,0 +1,8 @@ + + +
+ + + + + diff --git a/modules/module/schemas/action_security.xml b/modules/module/schemas/action_security.xml new file mode 100644 index 000000000..5d8bb34fa --- /dev/null +++ b/modules/module/schemas/action_security.xml @@ -0,0 +1,3 @@ +
+ +
From 85c2f87f1373631bc725377a7afec753218a52b2 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 22:31:22 +0900 Subject: [PATCH 39/71] Move some security measures from ModuleHandler to Context --- classes/context/Context.class.php | 10 +++++++++- classes/module/ModuleHandler.class.php | 20 -------------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index cc89b35c9..24f573888 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -1439,7 +1439,15 @@ class Context { $_val = (int)$_val; } - elseif(in_array($key, array('mid', 'vid', 'search_target', 'search_keyword', 'xe_validator_id')) || $_SERVER['REQUEST_METHOD'] === 'GET') + elseif(in_array($key, array('mid', 'vid', 'act', 'module'))) + { + $_val = preg_match('/^[a-zA-Z0-9_-]+$/', $_val) ? $_val : null; + if($_val === null) + { + self::$_instance->security_check = 'DENY ALL'; + } + } + elseif(in_array($key, array('search_target', 'search_keyword', 'xe_validator_id')) || $_SERVER['REQUEST_METHOD'] === 'GET') { $_val = escape($_val, false); if(ends_with('url', $key, false)) diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 4def99202..350476ad0 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -94,26 +94,6 @@ class ModuleHandler extends Handler Context::set('mid', $this->mid = null); } - // Validate variables to prevent XSS - $isInvalid = false; - if($this->module && !preg_match('/^[a-zA-Z0-9_-]+$/', $this->module)) - { - $isInvalid = true; - } - if($this->mid && !preg_match('/^[a-zA-Z0-9_-]+$/', $this->mid)) - { - $isInvalid = true; - } - if($this->act && !preg_match('/^[a-zA-Z0-9_-]+$/', $this->act)) - { - $isInvalid = true; - } - if($isInvalid) - { - $this->error = 'msg_security_violation'; - return; - } - // call a trigger before moduleHandler init self::triggerCall('moduleHandler.init', 'before', $this); From 31c38c03a9df81cdbcf805cc27ec0d1042952782 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 22:40:01 +0900 Subject: [PATCH 40/71] Cleanup unnecessary module instances in ModuleHandler --- classes/module/ModuleHandler.class.php | 36 +++++++++++--------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 350476ad0..72bd11e2d 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -110,13 +110,12 @@ class ModuleHandler extends Handler * */ public function init() { - $oModuleModel = ModuleModel::getInstance(); $site_module_info = Context::get('site_module_info'); // Check unregistered domain action. if (!$site_module_info || !isset($site_module_info->domain_srl) || $site_module_info->is_default_replaced) { - $site_module_info = $oModuleModel->getDefaultDomainInfo(); + $site_module_info = ModuleModel::getDefaultDomainInfo(); if ($site_module_info) { $domain_action = config('url.unregistered_domain_action') ?: 'redirect_301'; @@ -172,8 +171,7 @@ class ModuleHandler extends Handler // Convert document alias (entry) to document_srl if(!$this->document_srl && $this->mid && $this->entry) { - $oDocumentModel = DocumentModel::getInstance(); - $this->document_srl = $oDocumentModel->getDocumentSrlByAlias($this->mid, $this->entry); + $this->document_srl = DocumentModel::getDocumentSrlByAlias($this->mid, $this->entry); if($this->document_srl) { Context::set('document_srl', $this->document_srl); @@ -183,7 +181,7 @@ class ModuleHandler extends Handler // Get module's information based on document_srl, if it's specified if($this->document_srl) { - $module_info = $oModuleModel->getModuleInfoByDocumentSrl($this->document_srl); + $module_info = ModuleModel::getModuleInfoByDocumentSrl($this->document_srl); if($module_info) { // If it exists, compare mid based on the module information @@ -212,8 +210,7 @@ class ModuleHandler extends Handler // Block access to secret or temporary documents. if(Context::getRequestMethod() == 'GET') { - $oDocumentModel = DocumentModel::getInstance(); - $oDocument = $oDocumentModel->getDocument($this->document_srl); + $oDocument = DocumentModel::getDocument($this->document_srl); if($oDocument->isExists() && !$oDocument->isAccessible()) { $this->httpStatusCode = '403'; @@ -224,8 +221,7 @@ class ModuleHandler extends Handler // If module_info is not set yet, and there exists mid information, get module information based on the mid if(!$module_info && $this->mid) { - $module_info = $oModuleModel->getModuleInfoByMid($this->mid, $site_module_info->site_srl); - //if($this->module && $module_info->module != $this->module) unset($module_info); + $module_info = ModuleModel::getModuleInfoByMid($this->mid, $site_module_info->site_srl); } // If module_info is not set still, and $module does not exist, find the default module @@ -276,7 +272,7 @@ class ModuleHandler extends Handler 'page' => Context::get('page') ?: 1, )); - $module_config = $oModuleModel->getModuleConfig('module'); + $module_config = ModuleModel::getModuleConfig('module'); if ($module_info->meta_keywords) { Context::addMetaTag('keywords', $module_info->meta_keywords); @@ -330,7 +326,7 @@ class ModuleHandler extends Handler // reset a layout_srl in module_info. $module_info->{$targetSrl} = $layoutSrl; - $part_config = $oModuleModel->getModulePartConfig('layout', $layoutSrl); + $part_config = ModuleModel::getModulePartConfig('layout', $layoutSrl); Context::addHtmlHeader($part_config->header_script); } @@ -379,7 +375,6 @@ class ModuleHandler extends Handler * */ public function procModule() { - $oModuleModel = ModuleModel::getInstance(); $display_mode = Mobile::isFromMobilePhone() ? 'mobile' : 'view'; // If error occurred while preparation, return a message instance @@ -398,7 +393,7 @@ class ModuleHandler extends Handler } // Get action information with conf/module.xml - $xml_info = $oModuleModel->getModuleActionXml($this->module); + $xml_info = ModuleModel::getModuleActionXml($this->module); // If not installed yet, modify act if($this->module == "install") @@ -551,7 +546,7 @@ class ModuleHandler extends Handler if(preg_match('/^([a-z]+)([A-Z])([a-z0-9\_]+)(.*)$/', $this->act, $matches)) { $module = strtolower($matches[2] . $matches[3]); - $xml_info = $oModuleModel->getModuleActionXml($module); + $xml_info = ModuleModel::getModuleActionXml($module); if($xml_info->action->{$this->act} && ($this->module == 'admin' || $xml_info->action->{$this->act}->standalone != 'false')) { @@ -576,7 +571,7 @@ class ModuleHandler extends Handler if(empty($forward->module)) { - $forward = $oModuleModel->getActionForward($this->act); + $forward = ModuleModel::getActionForward($this->act); } if(!empty($forward->module)) @@ -591,10 +586,10 @@ class ModuleHandler extends Handler Context::addMetaTag('robots', 'noindex'); } - $xml_info = $oModuleModel->getModuleActionXml($forward->module); + $xml_info = ModuleModel::getModuleActionXml($forward->module); // Protect admin action - if(($this->module == 'admin' || $kind == 'admin') && !$oModuleModel->getGrant($forward, $logged_info)->root) + if(($this->module == 'admin' || $kind == 'admin') && !ModuleModel::getGrant($forward, $logged_info)->root) { if($this->module == 'admin' || empty($xml_info->permission->{$this->act})) { @@ -707,7 +702,7 @@ class ModuleHandler extends Handler if(!empty($ruleset)) { $rulesetModule = !empty($forward->module) ? $forward->module : $this->module; - $rulesetFile = $oModuleModel->getValidatorFilePath($rulesetModule, $ruleset, $this->mid); + $rulesetFile = ModuleModel::getValidatorFilePath($rulesetModule, $ruleset, $this->mid); if(!empty($rulesetFile)) { if($_SESSION['XE_VALIDATOR_ERROR_LANG']) @@ -1198,8 +1193,7 @@ class ModuleHandler extends Handler return new BaseObject(); } - $oModuleModel = ModuleModel::getInstance(); - $triggers = $oModuleModel->getTriggers($trigger_name, $called_position); + $triggers = ModuleModel::getTriggers($trigger_name, $called_position); if(!$triggers) { $triggers = array(); @@ -1254,7 +1248,7 @@ class ModuleHandler extends Handler unset($oModule); } - $trigger_functions = $oModuleModel->getTriggerFunctions($trigger_name, $called_position); + $trigger_functions = ModuleModel::getTriggerFunctions($trigger_name, $called_position); foreach($trigger_functions as $item) { try From 221f30484f15f28a9ec40e8e35cf9e44bdd42c9f Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 17 Jun 2020 22:54:22 +0900 Subject: [PATCH 41/71] Fix regexp for detecting valid mid/act/module --- classes/context/Context.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index 24f573888..f6ce288e2 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -1441,7 +1441,7 @@ class Context } elseif(in_array($key, array('mid', 'vid', 'act', 'module'))) { - $_val = preg_match('/^[a-zA-Z0-9_-]+$/', $_val) ? $_val : null; + $_val = preg_match('/^[a-zA-Z0-9_-]*$/', $_val) ? $_val : null; if($_val === null) { self::$_instance->security_check = 'DENY ALL'; From c7949e11db0bb53fd011c9d485fd75fa26c8e4ab Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 10:38:36 +0900 Subject: [PATCH 42/71] Fix 404 error when document_srl is the only part of the URL --- common/framework/router.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index fb977fd74..cab7cff5a 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -11,6 +11,11 @@ class Router * List of XE-compatible global routes. */ protected static $_global_routes = array( + '$document_srl' => array( + 'regexp' => '#^(?[0-9]+)$#', + 'vars' => ['document_srl' => 'int'], + 'priority' => 0, + ), '$mid' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/?$#', 'vars' => ['mid' => 'any'], @@ -21,11 +26,6 @@ class Router 'vars' => ['act' => 'word'], 'priority' => 0, ), - '$document_srl' => array( - 'regexp' => '#^(?[0-9]+)$#', - 'vars' => ['document_srl' => 'int'], - 'priority' => 0, - ), '$mid/$document_srl' => array( 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?[0-9]+)$#', 'vars' => ['mid' => 'any', 'document_srl' => 'int'], From f8ea38c22d7c8d6c7dbdf252993ed0b97046fb71 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 10:38:49 +0900 Subject: [PATCH 43/71] Remove vid handling code from HTMLDisplayHandler --- classes/display/HTMLDisplayHandler.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/classes/display/HTMLDisplayHandler.php b/classes/display/HTMLDisplayHandler.php index 1ac2f19cd..95a947d85 100644 --- a/classes/display/HTMLDisplayHandler.php +++ b/classes/display/HTMLDisplayHandler.php @@ -212,12 +212,6 @@ class HTMLDisplayHandler $pattern = '/href=(["\'])(\?[^"\']+)/s'; $output = preg_replace($pattern, 'href=$1' . \RX_BASEURL . '$2', $output); - - if(Context::get('vid')) - { - $pattern = '/\/' . Context::get('vid') . '\?([^=]+)=/is'; - $output = preg_replace($pattern, '/?$1=', $output); - } } // prevent the 2nd request due to url(none) of the background-image From 4c7e3dea8283acf2471f1e2bb9912d8b90037f78 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 11:23:20 +0900 Subject: [PATCH 44/71] Return more detailed information from router --- classes/context/Context.class.php | 12 ++++++++--- common/framework/router.php | 35 +++++++++++++++++++------------ 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index f6ce288e2..380b88d89 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -235,13 +235,19 @@ class Context self::setRequestMethod(); if (in_array(self::$_instance->request_method, array('GET', 'POST'))) { - $args = Rhymix\Framework\Router::getRequestArguments(Rhymix\Framework\Router::getRewriteLevel()); + $method = $_SERVER['REQUEST_METHOD'] ?: 'GET'; + $url = $_SERVER['REQUEST_URI']; + $route = Rhymix\Framework\Router::getRequestArguments($method, $url, Rhymix\Framework\Router::getRewriteLevel()); + self::setRequestArguments($route->args); + if ($route->status !== 200) + { + + } } else { - $args = array(); + self::setRequestArguments(); } - self::setRequestArguments($args); self::setUploadInfo(); // If Rhymix is installed, get virtual site information. diff --git a/common/framework/router.php b/common/framework/router.php index cab7cff5a..706189f8f 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -84,16 +84,14 @@ class Router /** * Extract request arguments from the current URL. * + * @param string $method + * @param string $url * @param int $rewrite_level - * @return array + * @return object */ - public static function getRequestArguments(int $rewrite_level): array + public static function getRequestArguments(string $method, string $url, int $rewrite_level) { - // Get the request method. - $method = $_SERVER['REQUEST_METHOD'] ?: 'GET'; - // Get the local part of the current URL. - $url = $_SERVER['REQUEST_URI']; if (starts_with(\RX_BASEURL, $url)) { $url = substr($url, strlen(\RX_BASEURL)); @@ -110,9 +108,13 @@ class Router // Decode the URL into plain UTF-8. $url = urldecode($url); - if ($url === '' || (function_exists('mb_check_encoding') && !mb_check_encoding($url, 'UTF-8'))) + if ($url === '') { - return array(); + return (object)['status' => 200, 'url' => $url, 'args' => []]; + } + if (function_exists('mb_check_encoding') && !mb_check_encoding($url, 'UTF-8')) + { + return (object)['status' => 404, 'url' => '', 'args' => []]; } // Try to detect the prefix. This might be $mid. @@ -132,7 +134,7 @@ class Router { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); $allargs = array_merge(['mid' => $prefix, 'act' => $action], $matches, $args); - return $allargs; + return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; } } @@ -144,7 +146,7 @@ class Router { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); $allargs = array_merge(['mid' => $prefix, 'act' => $action[1]], $matches, $args); - return $allargs; + return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; } } @@ -152,7 +154,7 @@ class Router if (preg_match('#^[a-zA-Z0-9_]+$#', $internal_url)) { $allargs = array_merge(['mid' => $prefix, 'act' => $internal_url], $args); - return $allargs; + return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; } } } @@ -164,12 +166,19 @@ class Router { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); $allargs = array_merge($route_info['extra_vars'] ?? [], $matches, $args); - return $allargs; + return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; } } // If no pattern matches, return an empty array. - return array(); + if ($url === '' || $url === 'index.php') + { + return (object)['status' => 200, 'url' => '', 'args' => []]; + } + else + { + return (object)['status' => 404, 'url' => $url, 'args' => []]; + } } /** From 6286fd33973199e6fa4295eb9cfd3fb01a30bbe0 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 13:25:16 +0900 Subject: [PATCH 45/71] Display 404 error if a route cannot be found --- classes/context/Context.class.php | 5 +---- classes/module/ModuleHandler.class.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index 380b88d89..17feb422f 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -239,10 +239,7 @@ class Context $url = $_SERVER['REQUEST_URI']; $route = Rhymix\Framework\Router::getRequestArguments($method, $url, Rhymix\Framework\Router::getRewriteLevel()); self::setRequestArguments($route->args); - if ($route->status !== 200) - { - - } + self::set('route_info', $route); } else { diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 72bd11e2d..b4d6f3a69 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -19,6 +19,7 @@ class ModuleHandler extends Handler var $document_srl = NULL; ///< Document Number var $module_srl = NULL; ///< Module Number var $module_info = NULL; ///< Module Info + var $route = NULL; ///< Router result var $error = NULL; ///< an error code. var $httpStatusCode = NULL; ///< http status code. @@ -84,6 +85,7 @@ class ModuleHandler extends Handler $this->mid = $mid ? $mid : Context::get('mid'); $this->document_srl = $document_srl ? (int) $document_srl : (int) Context::get('document_srl'); $this->module_srl = $module_srl ? (int) $module_srl : (int) Context::get('module_srl'); + $this->route = Context::get('route_info'); if($entry = Context::get('entry')) { $this->entry = Context::convertEncodingStr($entry); @@ -140,6 +142,14 @@ class ModuleHandler extends Handler } } + // If the Router returned an error earlier, show an error here. + if($this->route && $this->route->status > 200) + { + $this->error = 'msg_module_is_not_exists'; + $this->httpStatusCode = '404'; + return true; + } + // if success_return_url and error_return_url is incorrect $urls = array(Context::get('success_return_url'), Context::get('error_return_url')); foreach($urls as $url) From e20c06bf00aab9ca9a4f12595645bda8df2eeb10 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 13:48:41 +0900 Subject: [PATCH 46/71] Replace $_GET in ModuleHandler with router results --- classes/module/ModuleHandler.class.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index b4d6f3a69..9d7b13c51 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -12,7 +12,7 @@ * */ class ModuleHandler extends Handler { - + var $method = 'GET'; var $module = NULL; ///< Module var $act = NULL; ///< action var $mid = NULL; ///< Module ID @@ -80,12 +80,13 @@ class ModuleHandler extends Handler } // Set variables from request arguments + $this->method = Context::getRequestMethod(); $this->module = $module ? $module : Context::get('module'); $this->act = $act ? $act : Context::get('act'); $this->mid = $mid ? $mid : Context::get('mid'); $this->document_srl = $document_srl ? (int) $document_srl : (int) Context::get('document_srl'); $this->module_srl = $module_srl ? (int) $module_srl : (int) Context::get('module_srl'); - $this->route = Context::get('route_info'); + $this->route = Context::get('route_info') ?: new stdClass; if($entry = Context::get('entry')) { $this->entry = Context::convertEncodingStr($entry); @@ -240,19 +241,14 @@ class ModuleHandler extends Handler $module_info = $site_module_info; } - if(!$module_info && !$this->module && $site_module_info->module_site_srl) - { - $module_info = $site_module_info; - } - // Set index document - if($site_module_info->index_document_srl && !$this->module && !$this->mid && !$this->document_srl && Context::getRequestMethod() === 'GET' && !count($_GET)) + if($site_module_info->index_document_srl && !$this->module && !$this->mid && !$this->document_srl && $this->method === 'GET' && !$this->route->args) { Context::set('document_srl', $this->document_srl = $site_module_info->index_document_srl, true); } // redirect, if site start module - if(!$site_module_info->index_document_srl && Context::getRequestMethod() === 'GET' && isset($_GET['mid']) && $_GET['mid'] === $site_module_info->mid && count($_GET) === 1) + if(!$site_module_info->index_document_srl && $this->method === 'GET' && isset($this->route->args['mid']) && $this->route->args['mid'] === $site_module_info->mid && count($this->route->args) === 1) { Context::setCacheControl(0); header('location: ' . getNotEncodedSiteUrl($site_module_info->domain), true, 301); From 5fa72f86290dfc5b46442b44496327d7d249d4a1 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 14:13:23 +0900 Subject: [PATCH 47/71] Add 404 error handler and update permission data structure --- classes/module/ModuleHandler.class.php | 2 +- classes/module/ModuleObject.class.php | 8 +-- .../framework/parsers/moduleactionparser.php | 52 +++++++++++-------- common/framework/parsers/moduleinfoparser.php | 1 + common/framework/router.php | 7 +++ modules/board/board.view.php | 5 ++ modules/board/conf/module.xml | 3 +- 7 files changed, 49 insertions(+), 29 deletions(-) diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 9d7b13c51..7a307f109 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -597,7 +597,7 @@ class ModuleHandler extends Handler // Protect admin action if(($this->module == 'admin' || $kind == 'admin') && !ModuleModel::getGrant($forward, $logged_info)->root) { - if($this->module == 'admin' || empty($xml_info->permission->{$this->act})) + if($this->module == 'admin' || empty($xml_info->action->{$this->act}->permission->target)) { self::_setInputErrorToContext(); $this->error = 'admin.msg_is_not_administrator'; diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index eba49f3cc..d72825afc 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -251,10 +251,10 @@ class ModuleObject extends BaseObject if(Context::get('logged_info')->is_admin !== 'Y') { // Get privileges(granted) information for target module by of module.xml - if(($permission_check = $this->xml_info->permission_check->{$this->act}) && $permission_check->key) + if(($permission = $this->xml_info->action->{$this->act}->permission) && $permission->check_var) { // Check parameter - if(empty($check_module_srl = trim(Context::get($permission_check->key)))) + if(empty($check_module_srl = trim(Context::get($permission->check_var)))) { return false; } @@ -277,7 +277,7 @@ class ModuleObject extends BaseObject foreach($check_module_srl as $target_srl) { // Get privileges(granted) information of current user for target module - if(($grant = ModuleModel::getInstance()->getPrivilegesBySrl($target_srl, $permission_check->type)) === false) + if(($grant = ModuleModel::getInstance()->getPrivilegesBySrl($target_srl, $permission->check_type)) === false) { return false; } @@ -348,7 +348,7 @@ class ModuleObject extends BaseObject } // Get permission types(guest, member, manager, root) of the currently requested action - $permission = $this->xml_info->permission->{$this->act}; + $permission = $this->xml_info->action->{$this->act}->permission->target ?: $this->xml_info->permission->{$this->act}; // If admin action, set default permission if(empty($permission) && stripos($this->act, 'admin') !== false) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index a7fa01849..dd330ad5e 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -49,10 +49,9 @@ class ModuleActionParser $info->route->GET = []; $info->route->POST = []; $info->action = new \stdClass; - $info->menu = new \stdClass; $info->grant = new \stdClass; - $info->permission = new \stdClass; - $info->permission_check = new \stdClass; + $info->menu = new \stdClass; + $info->error_handlers = []; // Parse grants. foreach ($xml->grants->grant ?: [] as $grant) @@ -64,19 +63,6 @@ class ModuleActionParser $info->grant->{$grant_name} = $grant_info; } - // Parse permissions not defined in the section. - foreach ($xml->permissions->permission ?: [] as $permission) - { - $action_name = trim($permission['action']); - $permission = trim($permission['target']); - $info->permission->{$action_name} = $permission; - - $check = new \stdClass; - $check->key = trim($permission['check_var']) ?: trim($permission['check-var']); - $check->type = trim($permission['check_type']) ?: trim($permission['check-type']); - $info->permission_check->{$action_name} = $check; - } - // Parse menus. foreach ($xml->menus->menu ?: [] as $menu) { @@ -95,15 +81,12 @@ class ModuleActionParser // Parse permissions. $action_name = trim($action['name']); $permission = trim($action['permission']); + $permission_info = (object)['target' => '', 'check_var' => '', 'check_type' => '']; if ($permission) { - $info->permission->{$action_name} = $permission; - if (isset($info->permission_check->{$action_name})) - { - $info->permission_check->{$action_name} = new \stdClass; - } - $info->permission_check->{$action_name}->key = trim($action['check_var']) ?: trim($action['check-var']); - $info->permission_check->{$action_name}->type = trim($action['check_type']) ?: trim($action['check-type']); + $permission_info->target = $permission; + $permission_info->check_var = trim($action['check_var']) ?: trim($action['check-var']); + $permission_info->check_type = trim($action['check_type']) ?: trim($action['check-type']); } // Parse routes. @@ -136,6 +119,7 @@ class ModuleActionParser $action_info = new \stdClass; $action_info->type = trim($action['type']); $action_info->grant = trim($action['grant']) ?: 'guest'; + $action_info->permission = $permission_info; $action_info->ruleset = trim($action['ruleset']); $action_info->method = $method; $action_info->route = $route_arg; @@ -171,6 +155,28 @@ class ModuleActionParser { $info->simple_setup_index_act = $action_name; } + + // Set error handler settings. + $error_handlers = explode(',', trim($action['error_handlers']) ?: trim($action['error-handlers'])); + foreach ($error_handlers as $error_handler) + { + if (intval($error_handler) > 200) + { + $info->error_handlers[intval($error_handler)] = $action_name; + } + } + } + + // Parse permissions not defined in the section. + foreach ($xml->permissions->permission ?: [] as $permission) + { + $action_name = trim($permission['action']); + if (isset($info->action->{$action_name})) + { + $info->action->{$action_name}->permission->target = trim($permission['target']); + $info->action->{$action_name}->permission->check_var = trim($permission['check_var']) ?: trim($permission['check-var']); + $info->action->{$action_name}->permission->check_type = trim($permission['check_type']) ?: trim($permission['check-type']); + } } // Return the complete result. diff --git a/common/framework/parsers/moduleinfoparser.php b/common/framework/parsers/moduleinfoparser.php index c8cfd018e..688dfbe15 100644 --- a/common/framework/parsers/moduleinfoparser.php +++ b/common/framework/parsers/moduleinfoparser.php @@ -83,6 +83,7 @@ class ModuleInfoParser $info->default_index_act = $action_info->default_index_act; $info->setup_index_act = $action_info->setup_index_act; $info->simple_setup_index_act = $action_info->simple_setup_index_act; + $info->error_handlers = $action_info->error_handlers ?: []; // Return the complete result. return $info; diff --git a/common/framework/router.php b/common/framework/router.php index 706189f8f..55407cbc6 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -156,6 +156,13 @@ class Router $allargs = array_merge(['mid' => $prefix, 'act' => $internal_url], $args); return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; } + + // If the module defines a 404 error handler, call it. + if ($internal_url && isset($action_info->error_handlers[404])) + { + $allargs = array_merge(['mid' => $prefix, 'act' => $action_info->error_handlers[404]], $args); + return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; + } } } diff --git a/modules/board/board.view.php b/modules/board/board.view.php index 8ffa9c4d4..183ed018e 100644 --- a/modules/board/board.view.php +++ b/modules/board/board.view.php @@ -1364,6 +1364,11 @@ class boardView extends board Context::set('blame_member_info', $blame_member_infos); $this->setTemplateFile('vote_log'); } + + function dispBoardNotFound() + { + $this->alertMessage('msg_not_founded', 404); + } /** * @brief the method for displaying the warning messages diff --git a/modules/board/conf/module.xml b/modules/board/conf/module.xml index e08b47b9b..6b7ab3e2c 100644 --- a/modules/board/conf/module.xml +++ b/modules/board/conf/module.xml @@ -92,6 +92,7 @@ + @@ -125,7 +126,7 @@ - + From 7e47c1cb8ac0e7da38127c4efcb66efc38b34231 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 14:14:29 +0900 Subject: [PATCH 48/71] Fix duplicate cache key --- modules/module/module.model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/module/module.model.php b/modules/module/module.model.php index a30164a28..d6a85d9b3 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -757,7 +757,7 @@ class moduleModel extends module // Load the XML file and cache the definition. $mtime = filemtime($xml_file); - $cache_key = sprintf('site_and_module:module_info_xml:%s:%d', $module, $mtime); + $cache_key = sprintf('site_and_module:module_action_xml:%s:%d', $module, $mtime); $info = Rhymix\Framework\Cache::get($cache_key); if($info === null) { From c8a6b8de791f90e1da81a24828be943ff00e7bc5 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 16:36:27 +0900 Subject: [PATCH 49/71] Support global routes --- .../framework/parsers/moduleactionparser.php | 1 + common/framework/router.php | 98 ++++++++++++++----- modules/integration_search/conf/module.xml | 6 +- .../integration_search.class.php | 10 -- modules/module/module.class.php | 5 + modules/module/module.controller.php | 13 ++- .../module/queries/insertActionForward.xml | 1 + modules/module/schemas/action_forward.xml | 1 + 8 files changed, 94 insertions(+), 41 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index dd330ad5e..b504b4a00 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -126,6 +126,7 @@ class ModuleActionParser $action_info->standalone = trim($action['standalone']) === 'false' ? 'false' : 'true'; $action_info->check_csrf = (trim($action['check_csrf']) ?: trim($action['check-csrf'])) === 'false' ? 'false' : 'true'; $action_info->meta_noindex = (trim($action['meta_noindex']) ?: trim($action['meta-noindex'])) === 'true' ? 'true' : 'false'; + $action_info->global_route = (trim($action['global_route']) ?: trim($action['global-route'])) === 'true' ? 'true' : 'false'; $action_info->use_ssl = (trim($action['use_ssl']) ?: trim($action['use-ssl'])) === 'true' ? 'true' : 'false'; $info->action->{$action_name} = $action_info; diff --git a/common/framework/router.php b/common/framework/router.php index 55407cbc6..fa6ae892d 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -2,6 +2,8 @@ namespace Rhymix\Framework; +use function Complex\sec; + /** * The router class. */ @@ -59,7 +61,8 @@ class Router */ protected static $_action_cache_prefix = array(); protected static $_action_cache_module = array(); - protected static $_forwarded_cache = array(); + protected static $_global_forwarded_cache = array(); + protected static $_internal_forwarded_cache = array(); protected static $_route_cache = array(); /** @@ -118,7 +121,7 @@ class Router } // Try to detect the prefix. This might be $mid. - if ($rewrite_level > 1 && preg_match('#^([a-zA-Z0-9_-]+)(?:/(.*))?#s', $url, $matches)) + if ($rewrite_level >= 2 && preg_match('#^([a-zA-Z0-9_-]+)(?:/(.*))?#s', $url, $matches)) { // Separate the prefix and the internal part of the URL. $prefix = $matches[1]; @@ -139,7 +142,7 @@ class Router } // Check other modules. - $forwarded_routes = self::_getForwardedRoutes(); + $forwarded_routes = self::_getForwardedRoutes('internal'); foreach ($forwarded_routes[$method] ?: [] as $regexp => $action) { if (preg_match($regexp, $internal_url, $matches)) @@ -166,6 +169,21 @@ class Router } } + // Try registered global routes. + if ($rewrite_level >= 2) + { + $global_routes = self::_getForwardedRoutes('global'); + foreach ($global_routes[$method] ?: [] as $regexp => $action) + { + if (preg_match($regexp, $url, $matches)) + { + $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); + $allargs = array_merge(['act' => $action[1]], $matches, $args); + return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; + } + } + } + // Try XE-compatible global routes. foreach (self::$_global_routes as $route_info) { @@ -227,12 +245,12 @@ class Router return self::_insertRouteVars(self::$_route_cache[$keys_string], $args); } + // Remove $mid and $act from arguments and work with the remainder. + $args2 = $args; unset($args2['mid'], $args2['act']); + // If $mid exists, try routes defined in the module. if ($rewrite_level >= 2 && isset($args['mid'])) { - // Remove $mid from arguments and work with the remainder. - $args2 = $args; unset($args2['mid'], $args2['act']); - // Get module action info. $action_info = self::_getActionInfoByPrefix($args['mid']); @@ -252,7 +270,7 @@ class Router } // Check other modules for $act. - $forwarded_routes = self::_getForwardedRoutes(); + $forwarded_routes = self::_getForwardedRoutes('internal'); if (isset($forwarded_routes['reverse'][$act])) { $result = self::_getBestMatchingRoute($forwarded_routes['reverse'][$act], $args2); @@ -263,22 +281,26 @@ class Router } } - // Check XE-compatible routes that start with $mid and contain no $act. - if (!isset($args['act']) || ($args['act'] === 'rss' || $args['act'] === 'atom' || $args['act'] === 'api')) - { - $result = self::_getBestMatchingRoute(self::$_global_routes, $args2); - if ($result !== false) - { - self::$_route_cache[$keys_string] = $result; - return self::_insertRouteVars($result, $args2); - } - } - // Try the generic mid/act pattern. self::$_route_cache[$keys_string] = '$mid/$act'; return $args['mid'] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); } + // Try registered global routes. + if ($rewrite_level >= 2 && isset($args['act'])) + { + $global_routes = self::_getForwardedRoutes('global'); + if (isset($global_routes['reverse'][$args['act']])) + { + $result = self::_getBestMatchingRoute($global_routes['reverse'][$args['act']], $args2); + if ($result !== false) + { + self::$_route_cache[$keys_string] = $result . '$act:delete'; + return self::_insertRouteVars($result, $args2); + } + } + } + // Try XE-compatible global routes. if ($rewrite_level >= 1) { @@ -343,14 +365,26 @@ class Router /** * Get the list of routes that are registered for action-forward. * - * @return object + * @param string $type + * @return array */ - protected static function _getForwardedRoutes() + protected static function _getForwardedRoutes(string $type): array { - if (count(self::$_forwarded_cache)) + if ($type === 'internal' && count(self::$_internal_forwarded_cache)) { - return self::$_forwarded_cache; + return self::$_internal_forwarded_cache; } + if ($type === 'global' && count(self::$_global_forwarded_cache)) + { + return self::$_global_forwarded_cache; + } + + self::$_global_forwarded_cache['GET'] = array(); + self::$_global_forwarded_cache['POST'] = array(); + self::$_global_forwarded_cache['reverse'] = array(); + self::$_internal_forwarded_cache['GET'] = array(); + self::$_internal_forwarded_cache['POST'] = array(); + self::$_internal_forwarded_cache['reverse'] = array(); $action_forward = \ModuleModel::getActionForward(); foreach ($action_forward as $action_name => $action_info) @@ -359,12 +393,26 @@ class Router { foreach ($action_info->route_regexp as $regexp_info) { - self::$_forwarded_cache[$regexp_info[0]][$regexp_info[1]] = [$action_info->module, $action_name]; + if ($action_info->global_route === 'Y') + { + self::$_global_forwarded_cache[$regexp_info[0]][$regexp_info[1]] = [$action_info->module, $action_name]; + } + else + { + self::$_internal_forwarded_cache[$regexp_info[0]][$regexp_info[1]] = [$action_info->module, $action_name]; + } + } + if ($action_info->global_route === 'Y') + { + self::$_global_forwarded_cache['reverse'][$action_name] = $action_info->route_config; + } + else + { + self::$_internal_forwarded_cache['reverse'][$action_name] = $action_info->route_config; } - self::$_forwarded_cache['reverse'][$action_name] = $action_info->route_config; } } - return self::$_forwarded_cache; + return $type === 'internal' ? self::$_internal_forwarded_cache : self::$_global_forwarded_cache; } /** diff --git a/modules/integration_search/conf/module.xml b/modules/integration_search/conf/module.xml index 89e20d10a..a3f3e86fe 100644 --- a/modules/integration_search/conf/module.xml +++ b/modules/integration_search/conf/module.xml @@ -2,7 +2,11 @@ - + + + + + diff --git a/modules/integration_search/integration_search.class.php b/modules/integration_search/integration_search.class.php index 3d2ff0c17..a2cbc7c34 100644 --- a/modules/integration_search/integration_search.class.php +++ b/modules/integration_search/integration_search.class.php @@ -39,10 +39,6 @@ class integration_search extends ModuleObject } } - if (!$oModuleModel->getActionForward('IS')) - { - return true; - } return false; } @@ -70,12 +66,6 @@ class integration_search extends ModuleObject } } } - - if (!$oModuleModel->getActionForward('IS')) - { - $oModuleController = getController('module'); - $oModuleController->insertActionForward('integration_search', 'view', 'IS'); - } } /** diff --git a/modules/module/module.class.php b/modules/module/module.class.php index 0b6060562..a092b2e9f 100644 --- a/modules/module/module.class.php +++ b/modules/module/module.class.php @@ -135,6 +135,7 @@ class module extends ModuleObject // check route columns in action_forward table if(!$oDB->isColumnExists('action_forward', 'route_regexp')) return true; if(!$oDB->isColumnExists('action_forward', 'route_config')) return true; + if(!$oDB->isColumnExists('action_forward', 'global_route')) return true; } /** @@ -462,6 +463,10 @@ class module extends ModuleObject { $oDB->addColumn('action_forward', 'route_config', 'text'); } + if(!$oDB->isColumnExists('action_forward', 'global_route')) + { + $oDB->addColumn('action_forward', 'global_route', 'char', 1, 'N', true); + } } /** diff --git a/modules/module/module.controller.php b/modules/module/module.controller.php index f84415115..bd532eb48 100644 --- a/modules/module/module.controller.php +++ b/modules/module/module.controller.php @@ -19,14 +19,15 @@ class moduleController extends module * Action forward finds and forwards if an action is not in the requested module * This is used when installing a module */ - function insertActionForward($module, $type, $act, $route_regexp = null, $route_config = null) + function insertActionForward($module, $type, $act, $route_regexp = null, $route_config = null, $global_route = 'N') { $args = new stdClass(); $args->module = $module; $args->type = $type; $args->act = $act; - $args->route_regexp = $route_regexp; - $args->route_config = $route_config; + $args->route_regexp = serialize($route_regexp); + $args->route_config = serialize($route_config); + $args->global_route = $global_route === 'Y' ? 'Y' : 'N'; $output = executeQuery('module.insertActionForward', $args); Rhymix\Framework\Cache::delete('action_forward'); @@ -1337,6 +1338,7 @@ class moduleController extends module 'type' => $module_action_info->action->{$action_name}->type, 'regexp' => array(), 'config' => $action_info->route, + 'global_route' => $action_info->global_route ? 'Y' : 'N', ); } } @@ -1368,7 +1370,8 @@ class moduleController extends module } } elseif ($action_forward[$action_name]->route_regexp !== $route_info['regexp'] || - $action_forward[$action_name]->route_config !== $route_info['config']) + $action_forward[$action_name]->route_config !== $route_info['config'] || + $action_forward[$action_name]->global_route !== $route_info['global_route']) { $output = $this->deleteActionForward($module_name, $route_info['type'], $action_name); if (!$output->toBool()) @@ -1377,7 +1380,7 @@ class moduleController extends module } $output = $this->insertActionForward($module_name, $route_info['type'], $action_name, - serialize($route_info['regexp']), serialize($route_info['config'])); + $route_info['regexp'], $route_info['config'], $route_info['global_route']); if (!$output->toBool()) { return $output; diff --git a/modules/module/queries/insertActionForward.xml b/modules/module/queries/insertActionForward.xml index d2cb1bc68..acd9c532a 100644 --- a/modules/module/queries/insertActionForward.xml +++ b/modules/module/queries/insertActionForward.xml @@ -8,5 +8,6 @@ +
diff --git a/modules/module/schemas/action_forward.xml b/modules/module/schemas/action_forward.xml index 2a1989a89..59ad98fa8 100644 --- a/modules/module/schemas/action_forward.xml +++ b/modules/module/schemas/action_forward.xml @@ -4,4 +4,5 @@ + From 85fcc79457f81ab43981dd3788fa18d041cdf935 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 20:06:59 +0900 Subject: [PATCH 50/71] Reorganize data exchange format between Context and Router --- classes/context/Context.class.php | 22 ++++++-- classes/module/ModuleHandler.class.php | 2 +- common/framework/router.php | 77 ++++++++++++++++++++------ modules/module/module.controller.php | 6 +- 4 files changed, 81 insertions(+), 26 deletions(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index 17feb422f..dc00620b9 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -127,6 +127,10 @@ class Context */ private static $_init_called = false; + /** + * Current route information + */ + private static $_route_info = null; /** * object oFrontEndFileHandler() * @var object @@ -237,9 +241,9 @@ class Context { $method = $_SERVER['REQUEST_METHOD'] ?: 'GET'; $url = $_SERVER['REQUEST_URI']; - $route = Rhymix\Framework\Router::getRequestArguments($method, $url, Rhymix\Framework\Router::getRewriteLevel()); - self::setRequestArguments($route->args); - self::set('route_info', $route); + $route_info = Rhymix\Framework\Router::parseURL($method, $url, Rhymix\Framework\Router::getRewriteLevel()); + self::setRequestArguments($route_info->args); + self::$_route_info = $route_info; } else { @@ -614,6 +618,16 @@ class Context return self::$_instance->db_info; } + /** + * Get current route information + * + * @return object + */ + public static function getRouteInfo() + { + return self::$_route_info; + } + /** * Return ssl status * @@ -1743,7 +1757,7 @@ class Context $query = ''; if(count($get_vars) > 0) { - $query = Rhymix\Framework\Router::getURLFromArguments($get_vars, $rewrite_level); + $query = Rhymix\Framework\Router::getURL($get_vars, $rewrite_level); } // If using SSL always diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 7a307f109..d9eae69ea 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -86,7 +86,7 @@ class ModuleHandler extends Handler $this->mid = $mid ? $mid : Context::get('mid'); $this->document_srl = $document_srl ? (int) $document_srl : (int) Context::get('document_srl'); $this->module_srl = $module_srl ? (int) $module_srl : (int) Context::get('module_srl'); - $this->route = Context::get('route_info') ?: new stdClass; + $this->route = Context::getRouteInfo() ?: new stdClass; if($entry = Context::get('entry')) { $this->entry = Context::convertEncodingStr($entry); diff --git a/common/framework/router.php b/common/framework/router.php index fa6ae892d..5b7c9f7ae 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -92,7 +92,7 @@ class Router * @param int $rewrite_level * @return object */ - public static function getRequestArguments(string $method, string $url, int $rewrite_level) + public static function parseURL(string $method, string $url, int $rewrite_level) { // Get the local part of the current URL. if (starts_with(\RX_BASEURL, $url)) @@ -100,6 +100,16 @@ class Router $url = substr($url, strlen(\RX_BASEURL)); } + // Prepare the return object. + $result = new \stdClass; + $result->status = 200; + $result->url = ''; + $result->module = ''; + $result->mid = ''; + $result->act = ''; + $result->forwarded = false; + $result->args = array(); + // Separate additional arguments from the URL. $args = array(); $argstart = strpos($url, '?'); @@ -110,14 +120,15 @@ class Router } // Decode the URL into plain UTF-8. - $url = urldecode($url); + $url = $result->url = urldecode($url); if ($url === '') { - return (object)['status' => 200, 'url' => $url, 'args' => []]; + return $result; } if (function_exists('mb_check_encoding') && !mb_check_encoding($url, 'UTF-8')) { - return (object)['status' => 404, 'url' => '', 'args' => []]; + $result->status = 404; + return $result; } // Try to detect the prefix. This might be $mid. @@ -127,7 +138,7 @@ class Router $prefix = $matches[1]; $internal_url = $matches[2] ?? ''; // Find the module associated with this prefix. - $action_info = self::_getActionInfoByPrefix($prefix); + $action_info = self::_getActionInfoByPrefix($prefix, $module_name = ''); if ($action_info) { // Try the list of routes defined by the module. @@ -137,7 +148,11 @@ class Router { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); $allargs = array_merge(['mid' => $prefix, 'act' => $action], $matches, $args); - return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; + $result->module = $module_name; + $result->mid = $prefix; + $result->act = $action; + $result->args = $allargs; + return $result; } } @@ -149,7 +164,12 @@ class Router { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); $allargs = array_merge(['mid' => $prefix, 'act' => $action[1]], $matches, $args); - return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; + $result->module = $action[0]; + $result->mid = $prefix; + $result->act = $action[1]; + $result->forwarded = true; + $result->args = $allargs; + return $result; } } @@ -157,14 +177,23 @@ class Router if (preg_match('#^[a-zA-Z0-9_]+$#', $internal_url)) { $allargs = array_merge(['mid' => $prefix, 'act' => $internal_url], $args); - return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; + $result->mid = $prefix; + $result->act = $internal_url; + $result->forwarded = true; + $result->args = $allargs; + return $result; } // If the module defines a 404 error handler, call it. if ($internal_url && isset($action_info->error_handlers[404])) { $allargs = array_merge(['mid' => $prefix, 'act' => $action_info->error_handlers[404]], $args); - return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; + $result->module = $module_name; + $result->mid = $prefix; + $result->act = $action_info->error_handlers[404]; + $result->forwarded = false; + $result->args = $allargs; + return $result; } } } @@ -179,8 +208,12 @@ class Router { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); $allargs = array_merge(['act' => $action[1]], $matches, $args); - return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; - } + $result->module = $action[0]; + $result->act = $action[1]; + $result->forwarded = true; + $result->args = $allargs; + return $result; + } } } @@ -191,18 +224,25 @@ class Router { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); $allargs = array_merge($route_info['extra_vars'] ?? [], $matches, $args); - return (object)['status' => 200, 'url' => $url, 'args' => $allargs]; + $result->module = $allargs['module'] ?? ''; + $result->mid = $allargs['mid'] ?: ''; + $result->act = $allargs['act'] ?: ''; + $result->forwarded = false; + $result->args = $allargs; + return $result; } } // If no pattern matches, return an empty array. if ($url === '' || $url === 'index.php') { - return (object)['status' => 200, 'url' => '', 'args' => []]; + $result->url = ''; + return $result; } else { - return (object)['status' => 404, 'url' => $url, 'args' => []]; + $result->status = 404; + return $result; } } @@ -213,7 +253,7 @@ class Router * @param int $rewrite_level * @return string */ - public static function getURLFromArguments(array $args, int $rewrite_level): string + public static function getURL(array $args, int $rewrite_level): string { // If rewrite is turned off, just create a query string. if ($rewrite_level == 0) @@ -252,7 +292,7 @@ class Router if ($rewrite_level >= 2 && isset($args['mid'])) { // Get module action info. - $action_info = self::_getActionInfoByPrefix($args['mid']); + $action_info = self::_getActionInfoByPrefix($args['mid'], $module_name = ''); // If there is no $act, use the default action. $act = isset($args['act']) ? $args['act'] : $action_info->default_index_act; @@ -326,17 +366,18 @@ class Router * @param string $prefix * @return object */ - protected static function _getActionInfoByPrefix(string $prefix) + protected static function _getActionInfoByPrefix(string $prefix, string &$module_name = '') { if (isset(self::$_action_cache_prefix[$prefix])) { + $module_name = self::$_action_cache_prefix[$prefix]; return self::_getActionInfoByModule(self::$_action_cache_prefix[$prefix]) ?: false; } $module_info = \ModuleModel::getModuleInfoByMid($prefix); if ($module_info && $module_info->module) { - self::$_action_cache_prefix[$prefix] = $module_info->module; + $module_name = self::$_action_cache_prefix[$prefix] = $module_info->module; return self::_getActionInfoByModule(self::$_action_cache_prefix[$prefix]) ?: false; } else diff --git a/modules/module/module.controller.php b/modules/module/module.controller.php index bd532eb48..fa987592a 100644 --- a/modules/module/module.controller.php +++ b/modules/module/module.controller.php @@ -25,8 +25,8 @@ class moduleController extends module $args->module = $module; $args->type = $type; $args->act = $act; - $args->route_regexp = serialize($route_regexp); - $args->route_config = serialize($route_config); + $args->route_regexp = is_scalar($route_regexp) ? $route_regexp : serialize($route_regexp); + $args->route_config = is_scalar($route_config) ? $route_config : serialize($route_config); $args->global_route = $global_route === 'Y' ? 'Y' : 'N'; $output = executeQuery('module.insertActionForward', $args); @@ -1363,7 +1363,7 @@ class moduleController extends module if (!isset($action_forward[$action_name])) { $output = $this->insertActionForward($module_name, $route_info['type'], $action_name, - serialize($route_info['regexp']), serialize($route_info['config'])); + $route_info['regexp'], $route_info['config'], $route_info['global_route']); if (!$output->toBool()) { return $output; From e17ddbbdb1cf0fc56aa03381db2c3c19f939d328 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 20:08:30 +0900 Subject: [PATCH 51/71] Remove nonsense import --- common/framework/router.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 5b7c9f7ae..ca667e0f4 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -2,8 +2,6 @@ namespace Rhymix\Framework; -use function Complex\sec; - /** * The router class. */ From 41e02eb89a158eb8ab340d534f931bba3f559ede Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 21:04:44 +0900 Subject: [PATCH 52/71] General cleanup of ModuleHandler init() --- classes/module/ModuleHandler.class.php | 441 +++++++++++-------------- common/lang/en.php | 1 + common/lang/ko.php | 1 + 3 files changed, 204 insertions(+), 239 deletions(-) diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index d9eae69ea..951ed2005 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -13,15 +13,16 @@ class ModuleHandler extends Handler { var $method = 'GET'; - var $module = NULL; ///< Module - var $act = NULL; ///< action - var $mid = NULL; ///< Module ID - var $document_srl = NULL; ///< Document Number - var $module_srl = NULL; ///< Module Number - var $module_info = NULL; ///< Module Info - var $route = NULL; ///< Router result - var $error = NULL; ///< an error code. - var $httpStatusCode = NULL; ///< http status code. + var $module_info = null; + var $module_srl = null; + var $module = null; + var $act = null; + var $mid = null; + var $document_srl = null; + var $route = null; + var $error = null; + var $is_mobile = false; + var $httpStatusCode = 200; /** * Valid types and kinds of module instances. @@ -87,6 +88,7 @@ class ModuleHandler extends Handler $this->document_srl = $document_srl ? (int) $document_srl : (int) Context::get('document_srl'); $this->module_srl = $module_srl ? (int) $module_srl : (int) Context::get('module_srl'); $this->route = Context::getRouteInfo() ?: new stdClass; + $this->is_mobile = Mobile::isFromMobilePhone(); if($entry = Context::get('entry')) { $this->entry = Context::convertEncodingStr($entry); @@ -103,7 +105,7 @@ class ModuleHandler extends Handler // execute addon (before module initialization) $called_position = 'before_module_init'; $oAddonController = AddonController::getInstance(); - $addon_file = $oAddonController->getCacheFilePath(Mobile::isFromMobilePhone() ? 'mobile' : 'pc'); + $addon_file = $oAddonController->getCacheFilePath($this->is_mobile ? 'mobile' : 'pc'); if(file_exists($addon_file)) include($addon_file); } @@ -143,42 +145,25 @@ class ModuleHandler extends Handler } } + // Check success_return_url and error_return_url to prevent dangerous redirects. + $urls = array('success_return_url', 'error_return_url'); + foreach($urls as $key) + { + $url = Context::get($key); + if ($url && !Rhymix\Framework\URL::isInternalURL($url)) + { + Context::set($key, null); + } + } + // If the Router returned an error earlier, show an error here. if($this->route && $this->route->status > 200) { $this->error = 'msg_module_is_not_exists'; - $this->httpStatusCode = '404'; + $this->httpStatusCode = 404; return true; } - // if success_return_url and error_return_url is incorrect - $urls = array(Context::get('success_return_url'), Context::get('error_return_url')); - foreach($urls as $url) - { - if(empty($url)) - { - continue; - } - - if($host = parse_url($url, PHP_URL_HOST)) - { - $defaultHost = parse_url(Context::getDefaultUrl(), PHP_URL_HOST); - if($host !== $defaultHost) - { - $siteModuleHost = $site_module_info->domain; - if(strpos($siteModuleHost, '/') !== false) - { - $siteModuleHost = parse_url($siteModuleHost, PHP_URL_HOST); - } - if($host !== $siteModuleHost) - { - Context::set('success_return_url', null); - Context::set('error_return_url', null); - } - } - } - } - // Convert document alias (entry) to document_srl if(!$this->document_srl && $this->mid && $this->entry) { @@ -189,65 +174,35 @@ class ModuleHandler extends Handler } } - // Get module's information based on document_srl, if it's specified + // Get module info from document_srl. if($this->document_srl) { - $module_info = ModuleModel::getModuleInfoByDocumentSrl($this->document_srl); - if($module_info) + $module_info = $this->_checkDocumentSrl(); + if ($module_info === false) { - // If it exists, compare mid based on the module information - // if mids are not matching, set it as the document's mid - if(!$this->mid || ($this->mid != $module_info->mid)) - { - if(Context::getRequestMethod() == 'GET') - { - Context::setCacheControl(0); - header('location: ' . getNotEncodedSiteUrl($site_module_info->domain, 'mid', $module_info->mid, 'document_srl', $this->document_srl), true, 301); - return false; - } - else - { - $this->mid = $module_info->mid; - Context::set('mid', $this->mid); - } - } - // if requested module is different from one of the document, remove the module information retrieved based on the document number - if($this->module && $module_info->module != $this->module) - { - unset($module_info); - } - } - - // Block access to secret or temporary documents. - if(Context::getRequestMethod() == 'GET') - { - $oDocument = DocumentModel::getDocument($this->document_srl); - if($oDocument->isExists() && !$oDocument->isAccessible()) - { - $this->httpStatusCode = '403'; - } + return false; } } - - // If module_info is not set yet, and there exists mid information, get module information based on the mid + + // Get module info from mid. if(!$module_info && $this->mid) { - $module_info = ModuleModel::getModuleInfoByMid($this->mid, $site_module_info->site_srl); + $module_info = ModuleModel::getModuleInfoByMid($this->mid); } - // If module_info is not set still, and $module does not exist, find the default module + // Set module info as the default module for the domain. if(!$module_info && !$this->module && !$this->mid) { $module_info = $site_module_info; } - // Set index document + // Set the index document. if($site_module_info->index_document_srl && !$this->module && !$this->mid && !$this->document_srl && $this->method === 'GET' && !$this->route->args) { Context::set('document_srl', $this->document_srl = $site_module_info->index_document_srl, true); } - // redirect, if site start module + // Redirect if the index module was requested. if(!$site_module_info->index_document_srl && $this->method === 'GET' && isset($this->route->args['mid']) && $this->route->args['mid'] === $site_module_info->mid && count($this->route->args) === 1) { Context::setCacheControl(0); @@ -258,57 +213,18 @@ class ModuleHandler extends Handler // If module info was set, retrieve variables from the module information if($module_info) { + // Set instance variables and SEO info. $this->module = $module_info->module; $this->mid = $module_info->mid; $this->module_info = $module_info; - if ($module_info->mid == $site_module_info->mid) - { - $seo_title = config('seo.main_title') ?: '$SITE_TITLE - $SITE_SUBTITLE'; - } - else - { - $seo_title = config('seo.subpage_title') ?: '$SITE_TITLE - $SUBPAGE_TITLE'; - } + $this->_setModuleSEOInfo($module_info, $site_module_info); - ModuleController::getInstance()->replaceDefinedLangCode($seo_title); - Context::setBrowserTitle($seo_title, array( - 'site_title' => Context::getSiteTitle(), - 'site_subtitle' => Context::getSiteSubtitle(), - 'subpage_title' => $module_info->browser_title, - 'page' => Context::get('page') ?: 1, - )); - - $module_config = ModuleModel::getModuleConfig('module'); - if ($module_info->meta_keywords) - { - Context::addMetaTag('keywords', $module_info->meta_keywords); - } - elseif ($site_module_info->settings->meta_keywords) - { - Context::addMetaTag('keywords', $site_module_info->settings->meta_keywords); - } - elseif ($module_config->meta_keywords) - { - Context::addMetaTag('keywords', $module_config->meta_keywords); - } - - if ($module_info->meta_description) - { - Context::addMetaTag('description', $module_info->meta_description); - } - elseif ($site_module_info->settings->meta_description) - { - Context::addMetaTag('description', $site_module_info->settings->meta_description); - } - elseif($module_config->meta_description) - { - Context::addMetaTag('description', $module_config->meta_description); - } - - $viewType = (Mobile::isFromMobilePhone()) ? 'M' : 'P'; + // Check if the current request is from a mobile device. + $this->is_mobile = Mobile::isFromMobilePhone(); + $viewType = $this->is_mobile ? 'M' : 'P'; $targetSrl = $viewType === 'M' ? 'mlayout_srl' : 'layout_srl'; - // use the site default layout. + // Apply default layouts. if($module_info->{$targetSrl} == -1) { $oLayoutAdminModel = getAdminModel('layout'); @@ -329,29 +245,28 @@ class ModuleHandler extends Handler $layoutSrl = $module_info->{$targetSrl}; } - // reset a layout_srl in module_info. + // Reset layout_srl in module_info. $module_info->{$targetSrl} = $layoutSrl; - + + // Add layout header script. $part_config = ModuleModel::getModulePartConfig('layout', $layoutSrl); Context::addHtmlHeader($part_config->header_script); } - - // Set module and mid into module_info - if(!isset($this->module_info)) + else { - $this->module_info = new stdClass(); + $this->module_info = new stdClass; + $this->module_info->module = $this->module; + $this->module_info->mid = $this->mid; } - $this->module_info->module = $this->module; - $this->module_info->mid = $this->mid; - // Set site_srl add 2011 08 09 + // Always overwrite site_srl (deprecated) $this->module_info->site_srl = $site_module_info->site_srl; // Still no module? it's an error if(!$this->module) { $this->error = 'msg_module_is_not_exists'; - $this->httpStatusCode = '404'; + $this->httpStatusCode = 404; return true; } @@ -381,21 +296,13 @@ class ModuleHandler extends Handler * */ public function procModule() { - $display_mode = Mobile::isFromMobilePhone() ? 'mobile' : 'view'; + // Set the display mode for the current device type. + $this->is_mobile = Mobile::isFromMobilePhone(); // If error occurred while preparation, return a message instance if($this->error) { - self::_setInputErrorToContext(); - $oMessageObject = self::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(); - if($this->httpStatusCode) - { - $oMessageObject->setHttpStatusCode($this->httpStatusCode); - } - return $oMessageObject; + return self::_createErrorMessage(-1, $this->error, $this->httpStatusCode); } // Get action information with conf/module.xml @@ -419,19 +326,7 @@ class ModuleHandler extends Handler // still no act means error if(!$this->act) { - $this->error = 'msg_module_is_not_exists'; - $this->httpStatusCode = '404'; - - self::_setInputErrorToContext(); - $oMessageObject = self::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(); - if($this->httpStatusCode) - { - $oMessageObject->setHttpStatusCode($this->httpStatusCode); - } - return $oMessageObject; + return self::_createErrorMessage(-1, 'msg_module_is_not_exists', 404); } // get type, kind @@ -465,12 +360,7 @@ class ModuleHandler extends Handler if(!in_array(strtoupper($_SERVER['REQUEST_METHOD']), $allowedMethodList)) { - $this->error = 'msg_invalid_request'; - $oMessageObject = self::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(); - return $oMessageObject; + return self::_createErrorMessage(-1, 'msg_method_not_allowed', 405); } } @@ -479,13 +369,7 @@ class ModuleHandler extends Handler { if($xml_info->action->{$this->act} && $xml_info->action->{$this->act}->check_csrf !== 'false' && !checkCSRF()) { - $this->_setInputErrorToContext(); - $this->error = 'msg_invalid_request'; - $oMessageObject = ModuleHandler::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(); - return $oMessageObject; + return self::_createErrorMessage(-1, 'msg_security_violation'); } } @@ -497,7 +381,7 @@ class ModuleHandler extends Handler $logged_info = Context::get('logged_info'); // if(type == view, and case for using mobilephone) - if($type == "view" && Mobile::isFromMobilePhone() && Context::isInstalled()) + if($type == "view" && $this->is_mobile && Context::isInstalled()) { $orig_type = "view"; $type = "mobile"; @@ -518,16 +402,7 @@ class ModuleHandler extends Handler if(!is_object($oModule)) { - self::_setInputErrorToContext(); - $oMessageObject = self::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(); - if($this->httpStatusCode) - { - $oMessageObject->setHttpStatusCode($this->httpStatusCode); - } - return $oMessageObject; + return self::_createErrorMessage(-1, $this->error, $this->httpStatusCode); } // If there is no such action in the module object @@ -535,17 +410,7 @@ class ModuleHandler extends Handler { if(!Context::isInstalled()) { - self::_setInputErrorToContext(); - $this->error = 'msg_invalid_request'; - $oMessageObject = self::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(); - if($this->httpStatusCode) - { - $oMessageObject->setHttpStatusCode($this->httpStatusCode); - } - return $oMessageObject; + return self::_createErrorMessage(-1, 'msg_invalid_request'); } // 1. Look for the module with action name @@ -565,13 +430,7 @@ class ModuleHandler extends Handler } else { - $this->error = 'msg_invalid_request'; - $oMessageObject = self::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(); - - return $oMessageObject; + return self::_createErrorMessage(-1, 'msg_invalid_request'); } } @@ -599,13 +458,7 @@ class ModuleHandler extends Handler { if($this->module == 'admin' || empty($xml_info->action->{$this->act}->permission->target)) { - self::_setInputErrorToContext(); - $this->error = 'admin.msg_is_not_administrator'; - $oMessageObject = self::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(); - return $oMessageObject; + return self::_createErrorMessage(-1, 'admin.msg_is_not_administrator'); } } @@ -626,12 +479,7 @@ class ModuleHandler extends Handler if(!in_array(strtoupper($_SERVER['REQUEST_METHOD']), $allowedMethodList)) { - $this->error = 'msg_security_violation'; - $oMessageObject = self::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(); - return $oMessageObject; + return self::_createErrorMessage(-1, 'msg_method_not_allowed', 405); } } @@ -640,17 +488,11 @@ class ModuleHandler extends Handler { if($xml_info->action->{$this->act} && $xml_info->action->{$this->act}->check_csrf !== 'false' && !checkCSRF()) { - $this->_setInputErrorToContext(); - $this->error = 'msg_security_violation'; - $oMessageObject = ModuleHandler::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(); - return $oMessageObject; + return self::_createErrorMessage(-1, 'msg_security_violation'); } } - if($type == "view" && Mobile::isFromMobilePhone()) + if($type == "view" && $this->is_mobile) { $orig_type = "view"; $type = "mobile"; @@ -670,16 +512,7 @@ class ModuleHandler extends Handler if(!is_object($oModule)) { - self::_setInputErrorToContext(); - $oMessageObject = self::getModuleInstance('message', $display_mode); - $oMessageObject->setError(-1); - $oMessageObject->setMessage('msg_module_is_not_exists'); - $oMessageObject->dispMessage(); - if($this->httpStatusCode) - { - $oMessageObject->setHttpStatusCode($this->httpStatusCode); - } - return $oMessageObject; + return self::_createErrorMessage(-1, 'msg_module_is_not_exists', 404); } // Admin page layout @@ -841,6 +674,115 @@ class ModuleHandler extends Handler return $oModule; } + /** + * Check the value of $document_srl. This method is called during init(). + * + * @return object|false + */ + protected function _checkDocumentSrl() + { + // Get the module that the document belongs to. + $module_info = ModuleModel::getModuleInfoByDocumentSrl($this->document_srl); + if($module_info) + { + // Compare the current mid to the module that the document belongs to. + if(!$this->mid || ($this->mid !== $module_info->mid)) + { + // If this is a GET request, redirect to the correct mid. + if(Context::getRequestMethod() === 'GET') + { + Context::setCacheControl(0); + header('Location: ' . getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $this->document_srl), true, 301); + return false; + } + // If this is NOT a GET request, don't redirect. Just overwrite the mid for the current request. + else + { + $this->mid = $module_info->mid; + Context::set('mid', $this->mid); + } + } + + // Remove module info if a different module has already been selected for the current request. + if($this->module && $module_info->module !== $this->module) + { + $module_info = null; + } + } + + // Block access to secret or temporary documents. + if(Context::getRequestMethod() === 'GET') + { + $oDocument = DocumentModel::getDocument($this->document_srl); + if($oDocument->isExists()) + { + $this->httpStatusCode = 404; + } + elseif(!$oDocument->isAccessible()) + { + $this->httpStatusCode = 403; + } + } + + // Return the module info for further processing. + return $module_info; + } + + /** + * Set SEO information to Context. + * + * @param object $module_info + * @param object $site_module_info + */ + protected function _setModuleSEOInfo($module_info, $site_module_info) + { + // Set the browser title. + if ($module_info->mid == $site_module_info->mid) + { + $seo_title = config('seo.main_title') ?: '$SITE_TITLE - $SITE_SUBTITLE'; + } + else + { + $seo_title = config('seo.subpage_title') ?: '$SITE_TITLE - $SUBPAGE_TITLE'; + } + ModuleController::getInstance()->replaceDefinedLangCode($seo_title); + Context::setBrowserTitle($seo_title, array( + 'site_title' => Context::getSiteTitle(), + 'site_subtitle' => Context::getSiteSubtitle(), + 'subpage_title' => $module_info->browser_title, + 'page' => Context::get('page') ?: 1, + )); + + // Set meta keywords. + $module_config = ModuleModel::getModuleConfig('module'); + if ($module_info->meta_keywords) + { + Context::addMetaTag('keywords', $module_info->meta_keywords); + } + elseif ($site_module_info->settings->meta_keywords) + { + Context::addMetaTag('keywords', $site_module_info->settings->meta_keywords); + } + elseif ($module_config->meta_keywords) + { + Context::addMetaTag('keywords', $module_config->meta_keywords); + } + + // Set meta description. + if ($module_info->meta_description) + { + Context::addMetaTag('description', $module_info->meta_description); + } + elseif ($site_module_info->settings->meta_description) + { + Context::addMetaTag('description', $site_module_info->settings->meta_description); + } + elseif($module_config->meta_description) + { + Context::addMetaTag('description', $module_config->meta_description); + } + } + /** * set error message to Session. * @return void @@ -903,6 +845,28 @@ class ModuleHandler extends Handler } } + /** + * Create a message module instance with an error message. + */ + protected static function _createErrorMessage($error, $message, $status_code = 403, $location = null) + { + $display_mode = Mobile::isFromMobilePhone() ? 'mobile' : 'view'; + if (!$location) + { + $backtrace = debug_backtrace(false); + $caller = array_shift($backtrace); + $location = $caller['file'] . ':' . $caller['line']; + } + + self::_setInputErrorToContext(); + $oMessageObject = self::getModuleInstance('message', $display_mode); + $oMessageObject->setError($error); + $oMessageObject->setMessage($message); + $oMessageObject->setHttpStatusCode($status_code ?: 403); + $oMessageObject->dispMessage('', $location); + return $oMessageObject; + } + /** * display contents from executed module * @param ModuleObject $oModule module instance @@ -910,11 +874,14 @@ class ModuleHandler extends Handler * */ public function displayContent($oModule = NULL) { + // Set the display mode for the current device type. + $this->is_mobile = Mobile::isFromMobilePhone(); + // If the module is not set or not an object, set error if(!$oModule || !is_object($oModule)) { $this->error = 'msg_module_is_not_exists'; - $this->httpStatusCode = '404'; + $this->httpStatusCode = 404; } // If connection to DB has a problem even though it's not install module, set error @@ -979,11 +946,7 @@ class ModuleHandler extends Handler if($this->error) { // display content with message module instance - $type = Mobile::isFromMobilePhone() ? 'mobile' : 'view'; - $oMessageObject = self::getModuleInstance('message', $type); - $oMessageObject->setError(-1); - $oMessageObject->setMessage($this->error); - $oMessageObject->dispMessage(null, $oModule->get('rx_error_location')); + $oMessageObject = self::_createErrorMessage(-1, $this->error, $this->httpStatusCode, $oModule->get('rx_error_location')); // display Error Page if(!in_array($oMessageObject->getHttpStatusCode(), array(200, 403))) @@ -997,8 +960,8 @@ class ModuleHandler extends Handler $oModule->setTemplatePath($oMessageObject->getTemplatePath()); $oModule->setTemplateFile($oMessageObject->getTemplateFile()); $oModule->setHttpStatusCode($oMessageObject->getHttpStatusCode()); - // Otherwise, set message instance as the target module } + // Otherwise, set message instance as the target module else { $oModule = $oMessageObject; @@ -1008,7 +971,7 @@ class ModuleHandler extends Handler } // Check if layout_srl exists for the module - $viewType = (Mobile::isFromMobilePhone()) ? 'M' : 'P'; + $viewType = $this->is_mobile ? 'M' : 'P'; if($viewType === 'M') { $layout_srl = $oModule->module_info->mlayout_srl; diff --git a/common/lang/en.php b/common/lang/en.php index 5ec21ffc4..7ed9ed903 100644 --- a/common/lang/en.php +++ b/common/lang/en.php @@ -234,6 +234,7 @@ $lang->msg_invalid_document = 'Invalid Article Number'; $lang->msg_invalid_request = 'Invalid Request'; $lang->msg_invalid_password = 'The password you entered is incorrect.'; $lang->msg_security_violation = 'Security Violation'; +$lang->msg_method_not_allowed = 'This HTTP method is not allowed for this action.'; $lang->msg_feature_disabled = 'This feature is disabled.'; $lang->msg_error_occured = 'An error has occured.'; $lang->msg_not_founded = 'Cannot find the target.'; diff --git a/common/lang/ko.php b/common/lang/ko.php index f6ba251ec..d8074d82e 100644 --- a/common/lang/ko.php +++ b/common/lang/ko.php @@ -236,6 +236,7 @@ $lang->msg_invalid_document = '잘못된 문서번호입니다.'; $lang->msg_invalid_request = '잘못된 요청입니다.'; $lang->msg_invalid_password = '비밀번호가 올바르지 않습니다.'; $lang->msg_security_violation = '보안정책상 허용되지 않습니다.'; +$lang->msg_method_not_allowed = '이 요청에 사용할 수 없는 HTTP 메소드입니다.'; $lang->msg_feature_disabled = '사용할 수 없는 기능입니다.'; $lang->msg_error_occured = '오류가 발생했습니다.'; $lang->msg_not_founded = '대상을 찾을 수 없습니다.'; From 99f36f56a61ff5d1aa7e5910a9491e3b335018b6 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 21:08:16 +0900 Subject: [PATCH 53/71] Change unnecessary ModuleModel instance method calls to static calls in ModuleObject --- classes/module/ModuleObject.class.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index d72825afc..efa543e20 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -335,10 +335,9 @@ class ModuleObject extends BaseObject } // Get privileges(granted) information of the member for current module - $oModuleModel = ModuleModel::getInstance(); if(!$grant) { - $grant = $oModuleModel->getGrant($this->module_info, $member_info, $this->xml_info); + $grant = ModuleModel::getGrant($this->module_info, $member_info, $this->xml_info); } // If an administrator, Pass @@ -381,17 +380,17 @@ class ModuleObject extends BaseObject if(Context::get('is_logged') && isset($type[2])) { // Manager privilege of the member is found by search all modules, Pass - if($type[2] == 'all' && $oModuleModel->findManagerPrivilege($member_info) !== false) + if($type[2] == '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' && $oModuleModel->findManagerPrivilege($member_info, $this->module) !== false) + elseif($type[2] == '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($oModuleModel->findManagerPrivilege($member_info, $type[2]) !== false) + elseif(ModuleModel::findManagerPrivilege($member_info, $type[2]) !== false) { return true; } @@ -650,7 +649,6 @@ class ModuleObject extends BaseObject // Set module skin if(isset($this->module_info->skin) && $this->module_info->module === $this->module && strpos($this->act, 'Admin') === false) { - $oModuleModel = ModuleModel::getInstance(); $skin_type = $is_mobile ? 'M' : 'P'; $skin_key = $is_mobile ? 'mskin' : 'skin'; $skin_dir = $is_mobile ? 'm.skins' : 'skins'; @@ -662,7 +660,7 @@ class ModuleObject extends BaseObject { if($module_skin === '/USE_DEFAULT/') { - $module_skin = $oModuleModel->getModuleDefaultSkin($this->module, $skin_type); + $module_skin = ModuleModel::getModuleDefaultSkin($this->module, $skin_type); $this->module_info->{$skin_key} = $module_skin; } if($module_skin === '/USE_RESPONSIVE/') @@ -671,7 +669,7 @@ class ModuleObject extends BaseObject $module_skin = $this->module_info->skin ?: '/USE_DEFAULT/'; if($module_skin === '/USE_DEFAULT/') { - $module_skin = $oModuleModel->getModuleDefaultSkin($this->module, 'P'); + $module_skin = ModuleModel::getModuleDefaultSkin($this->module, 'P'); } } if(!is_dir(sprintf('%s%s/%s', $this->module_path, $skin_dir, $module_skin))) @@ -682,7 +680,7 @@ class ModuleObject extends BaseObject } // Set skin variable - $oModuleModel->syncSkinInfoToModuleInfo($this->module_info); + ModuleModel::syncSkinInfoToModuleInfo($this->module_info); Context::set('module_info', $this->module_info); } From 9372ee4cceb3e66f29f5b35bf46d428c56247ffd Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 18 Jun 2020 21:20:23 +0900 Subject: [PATCH 54/71] Change some internal methods to protected in ModuleHandler --- classes/module/ModuleHandler.class.php | 38 ++++++++++++++------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 951ed2005..bce2365ae 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -784,10 +784,26 @@ class ModuleHandler extends Handler } /** - * set error message to Session. + * Save input values to session so that they can be recovered after returning to the previous form. + * * @return void - * */ - public static function _setInputErrorToContext() + */ + protected static function _setInputValueToSession() + { + $requestVars = getDestroyXeVars(Context::getRequestVars()); + unset($requestVars->act, $requestVars->mid, $requestVars->vid); + foreach($requestVars as $key => $value) + { + $_SESSION['INPUT_ERROR'][$key] = $value; + } + } + + /** + * Get previous error information and restore it to Context so that it is available to templates. + * + * @return void + */ + protected static function _setInputErrorToContext() { if($_SESSION['XE_VALIDATOR_ERROR'] && !Context::get('XE_VALIDATOR_ERROR')) { @@ -821,7 +837,7 @@ class ModuleHandler extends Handler * clear error message to Session. * @return void * */ - public static function _clearErrorSession() + protected static function _clearErrorSession() { unset($_SESSION['XE_VALIDATOR_ERROR']); unset($_SESSION['XE_VALIDATOR_MESSAGE']); @@ -831,20 +847,6 @@ class ModuleHandler extends Handler unset($_SESSION['INPUT_ERROR']); } - /** - * occured error when, set input values to session. - * @return void - * */ - public static function _setInputValueToSession() - { - $requestVars = Context::getRequestVars(); - unset($requestVars->act, $requestVars->mid, $requestVars->vid, $requestVars->success_return_url, $requestVars->error_return_url, $requestVars->xe_validator_id); - foreach($requestVars AS $key => $value) - { - $_SESSION['INPUT_ERROR'][$key] = $value; - } - } - /** * Create a message module instance with an error message. */ From 358832922ecbeeba4015d034d6e9f16f4f08276b Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 19 Jun 2020 11:14:10 +0900 Subject: [PATCH 55/71] Fix incorrect global route detection --- modules/module/module.controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/module/module.controller.php b/modules/module/module.controller.php index fa987592a..90116d661 100644 --- a/modules/module/module.controller.php +++ b/modules/module/module.controller.php @@ -1338,7 +1338,7 @@ class moduleController extends module 'type' => $module_action_info->action->{$action_name}->type, 'regexp' => array(), 'config' => $action_info->route, - 'global_route' => $action_info->global_route ? 'Y' : 'N', + 'global_route' => $action_info->global_route === 'true' ? 'Y' : 'N', ); } } From a6318436c20a1b038cb256c569814400dc529835 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 19 Jun 2020 11:33:21 +0900 Subject: [PATCH 56/71] Adjust argument priority --- classes/context/Context.class.php | 28 +++++++++++++--------------- common/framework/router.php | 12 ++++++------ 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index dc00620b9..6e356dcf8 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -1201,7 +1201,16 @@ class Context */ public static function setRequestArguments(array $router_args = []) { - foreach($router_args ?: $_REQUEST as $key => $val) + $request_args = $_SERVER['REQUEST_METHOD'] === 'GET' ? $_GET : $_POST; + if (count($router_args)) + { + foreach ($router_args as $key => $val) + { + $request_args[$key] = $val; + } + } + + foreach($request_args as $key => $val) { if($val === '' || isset(self::$_reserved_keys[$key]) || self::get($key)) { @@ -1210,18 +1219,7 @@ class Context $key = escape($key); $val = self::_filterRequestVar($key, $val); - $set_to_vars = $router_args ? true : false; - - if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET[$key])) - { - $set_to_vars = true; - } - elseif($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST[$key])) - { - $set_to_vars = true; - } - - self::set($key, $val, $set_to_vars); + self::set($key, $val, true); } // Set deprecated request parameters. @@ -1245,7 +1243,7 @@ class Context foreach((array)$params as $key => $val) { - if (isset($router_args[$key])) + if (isset($request_args[$key])) { continue; } @@ -1260,7 +1258,7 @@ class Context parse_str($GLOBALS['HTTP_RAW_POST_DATA'], $params); foreach($params as $key => $val) { - if (isset($router_args[$key])) + if (isset($request_args[$key])) { continue; } diff --git a/common/framework/router.php b/common/framework/router.php index ca667e0f4..2ef697aa8 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -145,7 +145,7 @@ class Router if (preg_match($regexp, $internal_url, $matches)) { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge(['mid' => $prefix, 'act' => $action], $matches, $args); + $allargs = array_merge($args, $matches, ['mid' => $prefix, 'act' => $action]); $result->module = $module_name; $result->mid = $prefix; $result->act = $action; @@ -161,7 +161,7 @@ class Router if (preg_match($regexp, $internal_url, $matches)) { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge(['mid' => $prefix, 'act' => $action[1]], $matches, $args); + $allargs = array_merge($args, $matches, ['mid' => $prefix, 'act' => $action[1]]); $result->module = $action[0]; $result->mid = $prefix; $result->act = $action[1]; @@ -174,7 +174,7 @@ class Router // Try the generic mid/act pattern. if (preg_match('#^[a-zA-Z0-9_]+$#', $internal_url)) { - $allargs = array_merge(['mid' => $prefix, 'act' => $internal_url], $args); + $allargs = array_merge($args, ['mid' => $prefix, 'act' => $internal_url]); $result->mid = $prefix; $result->act = $internal_url; $result->forwarded = true; @@ -185,7 +185,7 @@ class Router // If the module defines a 404 error handler, call it. if ($internal_url && isset($action_info->error_handlers[404])) { - $allargs = array_merge(['mid' => $prefix, 'act' => $action_info->error_handlers[404]], $args); + $allargs = array_merge($args, ['mid' => $prefix, 'act' => $action_info->error_handlers[404]]); $result->module = $module_name; $result->mid = $prefix; $result->act = $action_info->error_handlers[404]; @@ -205,7 +205,7 @@ class Router if (preg_match($regexp, $url, $matches)) { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge(['act' => $action[1]], $matches, $args); + $allargs = array_merge($args, $matches, ['act' => $action[1]]); $result->module = $action[0]; $result->act = $action[1]; $result->forwarded = true; @@ -221,7 +221,7 @@ class Router if (preg_match($route_info['regexp'], $url, $matches)) { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge($route_info['extra_vars'] ?? [], $matches, $args); + $allargs = array_merge($args, $matches, $route_info['extra_vars'] ?? []); $result->module = $allargs['module'] ?? ''; $result->mid = $allargs['mid'] ?: ''; $result->act = $allargs['act'] ?: ''; From ba0f29c9b3b33fd49e2b7f1a3337314022089e0c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 19 Jun 2020 12:47:29 +0900 Subject: [PATCH 57/71] Improve module detection and argument handling --- common/framework/router.php | 57 +++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 2ef697aa8..7c9de21de 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -135,8 +135,21 @@ class Router // Separate the prefix and the internal part of the URL. $prefix = $matches[1]; $internal_url = $matches[2] ?? ''; + $prefix_type = 'mid'; + // Find the module associated with this prefix. $action_info = self::_getActionInfoByPrefix($prefix, $module_name = ''); + if ($action_info === false) + { + $action_info = self::_getActionInfoByModule($prefix); + if ($action_info !== false) + { + $module_name = $prefix; + $prefix_type = 'module'; + } + } + + // If a module is found, try its routes. if ($action_info) { // Try the list of routes defined by the module. @@ -145,9 +158,9 @@ class Router if (preg_match($regexp, $internal_url, $matches)) { $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge($args, $matches, ['mid' => $prefix, 'act' => $action]); + $allargs = array_merge($args, $matches, [$prefix_type => $prefix, 'act' => $action]); $result->module = $module_name; - $result->mid = $prefix; + $result->mid = $prefix_type === 'mid' ? $prefix : ''; $result->act = $action; $result->args = $allargs; return $result; @@ -155,27 +168,31 @@ class Router } // Check other modules. - $forwarded_routes = self::_getForwardedRoutes('internal'); - foreach ($forwarded_routes[$method] ?: [] as $regexp => $action) + if ($prefix_type === 'mid') { - if (preg_match($regexp, $internal_url, $matches)) + $forwarded_routes = self::_getForwardedRoutes('internal'); + foreach ($forwarded_routes[$method] ?: [] as $regexp => $action) { - $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge($args, $matches, ['mid' => $prefix, 'act' => $action[1]]); - $result->module = $action[0]; - $result->mid = $prefix; - $result->act = $action[1]; - $result->forwarded = true; - $result->args = $allargs; - return $result; + if (preg_match($regexp, $internal_url, $matches)) + { + $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); + $allargs = array_merge($args, $matches, [$prefix_type => $prefix, 'act' => $action[1]]); + $result->module = $action[0]; + $result->mid = $prefix; + $result->act = $action[1]; + $result->forwarded = true; + $result->args = $allargs; + return $result; + } } } // Try the generic mid/act pattern. if (preg_match('#^[a-zA-Z0-9_]+$#', $internal_url)) { - $allargs = array_merge($args, ['mid' => $prefix, 'act' => $internal_url]); - $result->mid = $prefix; + $allargs = array_merge($args, [$prefix_type => $prefix, 'act' => $internal_url]); + $result->module = $module_name; + $result->mid = $prefix_type === 'mid' ? $prefix : ''; $result->act = $internal_url; $result->forwarded = true; $result->args = $allargs; @@ -185,9 +202,9 @@ class Router // If the module defines a 404 error handler, call it. if ($internal_url && isset($action_info->error_handlers[404])) { - $allargs = array_merge($args, ['mid' => $prefix, 'act' => $action_info->error_handlers[404]]); + $allargs = array_merge($args, [$prefix_type => $prefix, 'act' => $action_info->error_handlers[404]]); $result->module = $module_name; - $result->mid = $prefix; + $result->mid = $prefix_type === 'mid' ? $prefix : ''; $result->act = $action_info->error_handlers[404]; $result->forwarded = false; $result->args = $allargs; @@ -231,7 +248,11 @@ class Router } } - // If no pattern matches, return an empty array. + // If no pattern matches, return either an empty route or a 404 error. + $result->module = isset($args['module']) ? $args['module'] : ''; + $result->mid = isset($args['mid']) ? $args['mid'] : ''; + $result->act = isset($args['act']) ? $args['act'] : ''; + $result->args = $args; if ($url === '' || $url === 'index.php') { $result->url = ''; From f8a47bbabb9351870f2fb30be318f153689b93de Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 19 Jun 2020 13:47:14 +0900 Subject: [PATCH 58/71] Improve error handling when module.xml doesn't exist --- modules/module/module.model.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/module/module.model.php b/modules/module/module.model.php index d6a85d9b3..dc72f05db 100644 --- a/modules/module/module.model.php +++ b/modules/module/module.model.php @@ -732,7 +732,7 @@ class moduleModel extends module // Load the XML file and cache the definition. $mtime1 = filemtime($xml_file); - $mtime2 = filemtime($module_path . 'conf/module.xml'); + $mtime2 = file_exists($module_path . 'conf/module.xml') ? filemtime($module_path . 'conf/module.xml') : 0; $cache_key = sprintf('site_and_module:module_info_xml:%s:%d:%d', $module, $mtime1, $mtime2); $info = Rhymix\Framework\Cache::get($cache_key); if($info === null) @@ -1443,14 +1443,14 @@ class moduleModel extends module ); } } - foreach ($module_action_info->route->GET as $regexp => $action_name) + foreach ($module_action_info->route->GET ?: [] as $regexp => $action_name) { if (isset($forwardable_routes[$action_name])) { $forwardable_routes[$action_name]['regexp'][] = ['GET', $regexp]; } } - foreach ($module_action_info->route->POST as $regexp => $action_name) + foreach ($module_action_info->route->POST ?: [] as $regexp => $action_name) { if (isset($forwardable_routes[$action_name])) { From 4a433fa5367dfcc5604f3241a6c1f339a7395154 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 19 Jun 2020 13:47:57 +0900 Subject: [PATCH 59/71] Support short URLs for module --- common/framework/router.php | 38 ++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 7c9de21de..b531b6157 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -305,13 +305,22 @@ class Router } // Remove $mid and $act from arguments and work with the remainder. - $args2 = $args; unset($args2['mid'], $args2['act']); + $args2 = $args; unset($args2['module'], $args2['mid'], $args2['act']); // If $mid exists, try routes defined in the module. - if ($rewrite_level >= 2 && isset($args['mid'])) + if ($rewrite_level >= 2 && (isset($args['mid']) || isset($args['module']))) { // Get module action info. - $action_info = self::_getActionInfoByPrefix($args['mid'], $module_name = ''); + if (isset($args['mid'])) + { + $action_info = self::_getActionInfoByPrefix($args['mid']); + $prefix_type = 'mid'; + } + elseif (isset($args['module'])) + { + $action_info = self::_getActionInfoByModule($args['module']); + $prefix_type = 'module'; + } // If there is no $act, use the default action. $act = isset($args['act']) ? $args['act'] : $action_info->default_index_act; @@ -323,26 +332,29 @@ class Router $result = self::_getBestMatchingRoute($action->route, $args2); if ($result !== false) { - self::$_route_cache[$keys_string] = '$mid/' . $result . '$act:delete'; - return $args['mid'] . '/' . self::_insertRouteVars($result, $args2); + self::$_route_cache[$keys_string] = '$' . $prefix_type . '/' . $result . '$act:delete'; + return $args[$prefix_type] . '/' . self::_insertRouteVars($result, $args2); } } // Check other modules for $act. - $forwarded_routes = self::_getForwardedRoutes('internal'); - if (isset($forwarded_routes['reverse'][$act])) + if ($prefix_type === 'mid') { - $result = self::_getBestMatchingRoute($forwarded_routes['reverse'][$act], $args2); - if ($result !== false) + $forwarded_routes = self::_getForwardedRoutes('internal'); + if (isset($forwarded_routes['reverse'][$act])) { - self::$_route_cache[$keys_string] = '$mid/' . $result . '$act:delete'; - return $args['mid'] . '/' . self::_insertRouteVars($result, $args2); + $result = self::_getBestMatchingRoute($forwarded_routes['reverse'][$act], $args2); + if ($result !== false) + { + self::$_route_cache[$keys_string] = '$' . $prefix_type . '/' . $result . '$act:delete'; + return $args[$prefix_type] . '/' . self::_insertRouteVars($result, $args2); + } } } // Try the generic mid/act pattern. - self::$_route_cache[$keys_string] = '$mid/$act'; - return $args['mid'] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); + self::$_route_cache[$keys_string] = '$' . $prefix_type . '/$act'; + return $args[$prefix_type] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); } // Try registered global routes. From f82a0417cde15ea227958d5dfb3a58fc0b9337b7 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 19 Jun 2020 13:48:16 +0900 Subject: [PATCH 60/71] Add experimental support for short URLs for POST requests --- modules/member/conf/module.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/member/conf/module.xml b/modules/member/conf/module.xml index c80ab9e9b..9ee23c64e 100644 --- a/modules/member/conf/module.xml +++ b/modules/member/conf/module.xml @@ -23,9 +23,9 @@ - + - + From fda931ede0fac5470c895eb09437af12d722b716 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 19 Jun 2020 13:54:37 +0900 Subject: [PATCH 61/71] Fix unit tests to match expected behavior --- tests/unit/classes/ContextTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/classes/ContextTest.php b/tests/unit/classes/ContextTest.php index 6b798ca6e..e304499d7 100644 --- a/tests/unit/classes/ContextTest.php +++ b/tests/unit/classes/ContextTest.php @@ -96,7 +96,7 @@ class ContextTest extends \Codeception\TestCase\Test Context::setRequestArguments(); $this->assertEquals('POST', Context::getRequestMethod()); $this->assertNull(Context::getRequestVars()->foo); - $this->assertEquals('bazz', Context::get('foo')); // XE Compatibility behavior + $this->assertNull(Context::get('foo')); // This is different from XE behavior $_SERVER['REQUEST_METHOD'] = 'POST'; $_GET = array(); From 3a13b51068bd8218be268491fc7a1bda82cad840 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 19 Jun 2020 14:20:30 +0900 Subject: [PATCH 62/71] Add basic structure of unit test for Router --- tests/unit/framework/RouterTest.php | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/unit/framework/RouterTest.php diff --git a/tests/unit/framework/RouterTest.php b/tests/unit/framework/RouterTest.php new file mode 100644 index 000000000..a0a9b5a9e --- /dev/null +++ b/tests/unit/framework/RouterTest.php @@ -0,0 +1,33 @@ +assertEquals(0, Rhymix\Framework\Router::getRewriteLevel()); + + Rhymix\Framework\Config::set('url.rewrite', 1); + Rhymix\Framework\Config::set('use_rewrite', false); + $this->assertEquals(1, Rhymix\Framework\Router::getRewriteLevel()); + + Rhymix\Framework\Config::set('url.rewrite', 1); + Rhymix\Framework\Config::set('use_rewrite', true); + $this->assertEquals(1, Rhymix\Framework\Router::getRewriteLevel()); + + Rhymix\Framework\Config::set('url.rewrite', 2); + Rhymix\Framework\Config::set('use_rewrite', true); + $this->assertEquals(2, Rhymix\Framework\Router::getRewriteLevel()); + } + + public function testGetURL() + { + + } + + public function testParseURL() + { + + } +} From e9bec851f980ee65976d7ea959eff34b04942826 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 20 Jun 2020 15:50:22 +0900 Subject: [PATCH 63/71] Fix forms with empty actions --- classes/display/HTMLDisplayHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/display/HTMLDisplayHandler.php b/classes/display/HTMLDisplayHandler.php index 95a947d85..11d6848c5 100644 --- a/classes/display/HTMLDisplayHandler.php +++ b/classes/display/HTMLDisplayHandler.php @@ -204,7 +204,7 @@ class HTMLDisplayHandler // handles a relative path generated by using the rewrite module if(Context::isAllowRewrite()) { - $pattern = '/(action|src|href)=(["\'])\.\/([^"\']*)(["\'])/s'; + $pattern = '/(action|src|href)=(["\'])(?:\.\/([^"\']*))?(["\'])/s'; $output = preg_replace($pattern, '$1=$2' . \RX_BASEURL . '$3$4', $output); $pattern = '/src=(["\'])((?:files\/(?:attach|cache|faceOff|member_extra_info|thumbnails)|addons|common|(?:m\.)?layouts|modules|widgets|widgetstyle)\/[^"\']+)(["\'])/s'; From 075f1d935d83b0f7cd6b4ead42adfca7b6cce677 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 21 Jun 2020 17:49:33 +0900 Subject: [PATCH 64/71] Add exclusion list for modules like socialxe --- common/framework/router.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index b531b6157..bd129cb22 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -54,6 +54,13 @@ class Router ), ); + /** + * List of legacy modules whose URLs should not be shortened. + */ + protected static $_except_modules = array( + 'socialxe' => true, + ); + /** * Internal cache for module and route information. */ @@ -353,8 +360,11 @@ class Router } // Try the generic mid/act pattern. - self::$_route_cache[$keys_string] = '$' . $prefix_type . '/$act'; - return $args[$prefix_type] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); + if ($prefix_type !== 'module' || !isset(self::$_except_modules[$args[$prefix_type]])) + { + self::$_route_cache[$keys_string] = '$' . $prefix_type . '/$act'; + return $args[$prefix_type] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); + } } // Try registered global routes. From 2abeaba75dee3b3fa21e67672de2c078b4fcc43f Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 21 Jun 2020 18:11:15 +0900 Subject: [PATCH 65/71] Fix indentation (spaces -> tabs) --- common/framework/router.php | 1120 +++++++++++++++++------------------ 1 file changed, 560 insertions(+), 560 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index bd129cb22..048e494f0 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -7,564 +7,564 @@ namespace Rhymix\Framework; */ class Router { - /** - * List of XE-compatible global routes. - */ - protected static $_global_routes = array( - '$document_srl' => array( - 'regexp' => '#^(?[0-9]+)$#', - 'vars' => ['document_srl' => 'int'], - 'priority' => 0, - ), - '$mid' => array( - 'regexp' => '#^(?[a-zA-Z0-9_-]+)/?$#', - 'vars' => ['mid' => 'any'], - 'priority' => 0, - ), - '$act' => array( - 'regexp' => '#^(?rss|atom)$#', - 'vars' => ['act' => 'word'], - 'priority' => 0, - ), - '$mid/$document_srl' => array( - 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?[0-9]+)$#', - 'vars' => ['mid' => 'any', 'document_srl' => 'int'], - 'priority' => 30, - ), - '$mid/category/$category' => array( - 'regexp' => '#^(?[a-zA-Z0-9_-]+)/category/(?[0-9]+)$#', - 'vars' => ['mid' => 'any', 'category' => 'int'], - 'priority' => 10, - ), - '$mid/entry/$entry' => array( - 'regexp' => '#^(?[a-zA-Z0-9_-]+)/entry/(?[^/]+)$#', - 'vars' => ['mid' => 'any', 'entry' => 'any'], - 'priority' => 0, - ), - '$mid/$act' => array( - 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?rss|atom|api)$#', - 'vars' => ['mid' => 'any', 'act' => 'word'], - 'priority' => 20, - ), - 'files/download/$file_srl/$file_key/$filename' => array( - 'regexp' => '#^files/download/(?[0-9]+)/(?[a-zA-Z0-9_-]+)/(?[^/]+)$#', - 'vars' => ['file_srl' => 'int', 'file_key' => 'any', 'filename' => 'any'], - 'extra_vars' => ['act' => 'procFileOutput'], - 'priority' => 0, - ), - ); - - /** - * List of legacy modules whose URLs should not be shortened. - */ - protected static $_except_modules = array( - 'socialxe' => true, - ); - - /** - * Internal cache for module and route information. - */ - protected static $_action_cache_prefix = array(); - protected static $_action_cache_module = array(); - protected static $_global_forwarded_cache = array(); - protected static $_internal_forwarded_cache = array(); - protected static $_route_cache = array(); - - /** - * Return the currently configured rewrite level. - * - * 0 = None - * 1 = XE-compatible rewrite rules only - * 2 = Full rewrite support - * - * @return int - */ - public static function getRewriteLevel(): int - { - $level = Config::get('url.rewrite'); - if ($level === null) - { - $level = Config::get('use_rewrite') ? 1 : 0; - } - return intval($level); - } - - /** - * Extract request arguments from the current URL. - * - * @param string $method - * @param string $url - * @param int $rewrite_level - * @return object - */ - public static function parseURL(string $method, string $url, int $rewrite_level) - { - // Get the local part of the current URL. - if (starts_with(\RX_BASEURL, $url)) - { - $url = substr($url, strlen(\RX_BASEURL)); - } - - // Prepare the return object. - $result = new \stdClass; - $result->status = 200; - $result->url = ''; - $result->module = ''; - $result->mid = ''; - $result->act = ''; - $result->forwarded = false; - $result->args = array(); - - // Separate additional arguments from the URL. - $args = array(); - $argstart = strpos($url, '?'); - if ($argstart !== false) - { - @parse_str(substr($url, $argstart + 1), $args); - $url = substr($url, 0, $argstart); - } - - // Decode the URL into plain UTF-8. - $url = $result->url = urldecode($url); - if ($url === '') - { - return $result; - } - if (function_exists('mb_check_encoding') && !mb_check_encoding($url, 'UTF-8')) - { - $result->status = 404; - return $result; - } - - // Try to detect the prefix. This might be $mid. - if ($rewrite_level >= 2 && preg_match('#^([a-zA-Z0-9_-]+)(?:/(.*))?#s', $url, $matches)) - { - // Separate the prefix and the internal part of the URL. - $prefix = $matches[1]; - $internal_url = $matches[2] ?? ''; - $prefix_type = 'mid'; - - // Find the module associated with this prefix. - $action_info = self::_getActionInfoByPrefix($prefix, $module_name = ''); - if ($action_info === false) - { - $action_info = self::_getActionInfoByModule($prefix); - if ($action_info !== false) - { - $module_name = $prefix; - $prefix_type = 'module'; - } - } - - // If a module is found, try its routes. - if ($action_info) - { - // Try the list of routes defined by the module. - foreach ($action_info->route->{$method} as $regexp => $action) - { - if (preg_match($regexp, $internal_url, $matches)) - { - $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge($args, $matches, [$prefix_type => $prefix, 'act' => $action]); - $result->module = $module_name; - $result->mid = $prefix_type === 'mid' ? $prefix : ''; - $result->act = $action; - $result->args = $allargs; - return $result; - } - } - - // Check other modules. - if ($prefix_type === 'mid') - { - $forwarded_routes = self::_getForwardedRoutes('internal'); - foreach ($forwarded_routes[$method] ?: [] as $regexp => $action) - { - if (preg_match($regexp, $internal_url, $matches)) - { - $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge($args, $matches, [$prefix_type => $prefix, 'act' => $action[1]]); - $result->module = $action[0]; - $result->mid = $prefix; - $result->act = $action[1]; - $result->forwarded = true; - $result->args = $allargs; - return $result; - } - } - } - - // Try the generic mid/act pattern. - if (preg_match('#^[a-zA-Z0-9_]+$#', $internal_url)) - { - $allargs = array_merge($args, [$prefix_type => $prefix, 'act' => $internal_url]); - $result->module = $module_name; - $result->mid = $prefix_type === 'mid' ? $prefix : ''; - $result->act = $internal_url; - $result->forwarded = true; - $result->args = $allargs; - return $result; - } - - // If the module defines a 404 error handler, call it. - if ($internal_url && isset($action_info->error_handlers[404])) - { - $allargs = array_merge($args, [$prefix_type => $prefix, 'act' => $action_info->error_handlers[404]]); - $result->module = $module_name; - $result->mid = $prefix_type === 'mid' ? $prefix : ''; - $result->act = $action_info->error_handlers[404]; - $result->forwarded = false; - $result->args = $allargs; - return $result; - } - } - } - - // Try registered global routes. - if ($rewrite_level >= 2) - { - $global_routes = self::_getForwardedRoutes('global'); - foreach ($global_routes[$method] ?: [] as $regexp => $action) - { - if (preg_match($regexp, $url, $matches)) - { - $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge($args, $matches, ['act' => $action[1]]); - $result->module = $action[0]; - $result->act = $action[1]; - $result->forwarded = true; - $result->args = $allargs; - return $result; - } - } - } - - // Try XE-compatible global routes. - foreach (self::$_global_routes as $route_info) - { - if (preg_match($route_info['regexp'], $url, $matches)) - { - $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); - $allargs = array_merge($args, $matches, $route_info['extra_vars'] ?? []); - $result->module = $allargs['module'] ?? ''; - $result->mid = $allargs['mid'] ?: ''; - $result->act = $allargs['act'] ?: ''; - $result->forwarded = false; - $result->args = $allargs; - return $result; - } - } - - // If no pattern matches, return either an empty route or a 404 error. - $result->module = isset($args['module']) ? $args['module'] : ''; - $result->mid = isset($args['mid']) ? $args['mid'] : ''; - $result->act = isset($args['act']) ? $args['act'] : ''; - $result->args = $args; - if ($url === '' || $url === 'index.php') - { - $result->url = ''; - return $result; - } - else - { - $result->status = 404; - return $result; - } - } - - /** - * Create a URL for the given set of arguments. - * - * @param array $args - * @param int $rewrite_level - * @return string - */ - public static function getURL(array $args, int $rewrite_level): string - { - // If rewrite is turned off, just create a query string. - if ($rewrite_level == 0) - { - return 'index.php?' . http_build_query($args); - } - - // Cache the number of arguments and their keys. - $count = count($args); - $keys = array_keys($args); - - // If there are no arguments, return the URL of the main page. - if ($count == 0) - { - return ''; - } - - // If there is only one argument, try either $mid or $document_srl. - if ($rewrite_level >= 1 && $count == 1 && ($keys[0] === 'mid' || $keys[0] === 'document_srl')) - { - return urlencode($args[$keys[0]]); - } - - // If the list of keys is already cached, return the corresponding route. - $keys_sorted = $keys; sort($keys_sorted); - $keys_string = implode('.', $keys_sorted) . ':' . ($args['mid'] ?? '') . ':' . ($args['act'] ?? ''); - if (isset(self::$_route_cache[$keys_string])) - { - return self::_insertRouteVars(self::$_route_cache[$keys_string], $args); - } - - // Remove $mid and $act from arguments and work with the remainder. - $args2 = $args; unset($args2['module'], $args2['mid'], $args2['act']); - - // If $mid exists, try routes defined in the module. - if ($rewrite_level >= 2 && (isset($args['mid']) || isset($args['module']))) - { - // Get module action info. - if (isset($args['mid'])) - { - $action_info = self::_getActionInfoByPrefix($args['mid']); - $prefix_type = 'mid'; - } - elseif (isset($args['module'])) - { - $action_info = self::_getActionInfoByModule($args['module']); - $prefix_type = 'module'; - } - - // If there is no $act, use the default action. - $act = isset($args['act']) ? $args['act'] : $action_info->default_index_act; - - // Check if $act has any routes defined. - $action = $action_info->action->{$act} ?? null; - if ($action && $action->route) - { - $result = self::_getBestMatchingRoute($action->route, $args2); - if ($result !== false) - { - self::$_route_cache[$keys_string] = '$' . $prefix_type . '/' . $result . '$act:delete'; - return $args[$prefix_type] . '/' . self::_insertRouteVars($result, $args2); - } - } - - // Check other modules for $act. - if ($prefix_type === 'mid') - { - $forwarded_routes = self::_getForwardedRoutes('internal'); - if (isset($forwarded_routes['reverse'][$act])) - { - $result = self::_getBestMatchingRoute($forwarded_routes['reverse'][$act], $args2); - if ($result !== false) - { - self::$_route_cache[$keys_string] = '$' . $prefix_type . '/' . $result . '$act:delete'; - return $args[$prefix_type] . '/' . self::_insertRouteVars($result, $args2); - } - } - } - - // Try the generic mid/act pattern. - if ($prefix_type !== 'module' || !isset(self::$_except_modules[$args[$prefix_type]])) - { - self::$_route_cache[$keys_string] = '$' . $prefix_type . '/$act'; - return $args[$prefix_type] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); - } - } - - // Try registered global routes. - if ($rewrite_level >= 2 && isset($args['act'])) - { - $global_routes = self::_getForwardedRoutes('global'); - if (isset($global_routes['reverse'][$args['act']])) - { - $result = self::_getBestMatchingRoute($global_routes['reverse'][$args['act']], $args2); - if ($result !== false) - { - self::$_route_cache[$keys_string] = $result . '$act:delete'; - return self::_insertRouteVars($result, $args2); - } - } - } - - // Try XE-compatible global routes. - if ($rewrite_level >= 1) - { - if (!isset($args['act']) || ($args['act'] === 'rss' || $args['act'] === 'atom')) - { - $result = self::_getBestMatchingRoute(self::$_global_routes, $args); - if ($result !== false) - { - self::$_route_cache[$keys_string] = $result; - return self::_insertRouteVars($result, $args); - } - } - } - - // If no route matches, just create a query string. - self::$_route_cache[$keys_string] = 'index.php'; - return 'index.php?' . http_build_query($args); - } - - /** - * Load and cache module action info. - * - * @param string $prefix - * @return object - */ - protected static function _getActionInfoByPrefix(string $prefix, string &$module_name = '') - { - if (isset(self::$_action_cache_prefix[$prefix])) - { - $module_name = self::$_action_cache_prefix[$prefix]; - return self::_getActionInfoByModule(self::$_action_cache_prefix[$prefix]) ?: false; - } - - $module_info = \ModuleModel::getModuleInfoByMid($prefix); - if ($module_info && $module_info->module) - { - $module_name = self::$_action_cache_prefix[$prefix] = $module_info->module; - return self::_getActionInfoByModule(self::$_action_cache_prefix[$prefix]) ?: false; - } - else - { - return self::$_action_cache_prefix[$prefix] = false; - } - } - - /** - * Load and cache module action info. - * - * @param string $prefix - * @return object - */ - protected static function _getActionInfoByModule(string $module) - { - if (isset(self::$_action_cache_module[$module])) - { - return self::$_action_cache_module[$module]; - } - - $action_info = \ModuleModel::getModuleActionXml($module); - return self::$_action_cache_module[$module] = $action_info ?: false; - } - - /** - * Get the list of routes that are registered for action-forward. - * - * @param string $type - * @return array - */ - protected static function _getForwardedRoutes(string $type): array - { - if ($type === 'internal' && count(self::$_internal_forwarded_cache)) - { - return self::$_internal_forwarded_cache; - } - if ($type === 'global' && count(self::$_global_forwarded_cache)) - { - return self::$_global_forwarded_cache; - } - - self::$_global_forwarded_cache['GET'] = array(); - self::$_global_forwarded_cache['POST'] = array(); - self::$_global_forwarded_cache['reverse'] = array(); - self::$_internal_forwarded_cache['GET'] = array(); - self::$_internal_forwarded_cache['POST'] = array(); - self::$_internal_forwarded_cache['reverse'] = array(); - - $action_forward = \ModuleModel::getActionForward(); - foreach ($action_forward as $action_name => $action_info) - { - if ($action_info->route_regexp) - { - foreach ($action_info->route_regexp as $regexp_info) - { - if ($action_info->global_route === 'Y') - { - self::$_global_forwarded_cache[$regexp_info[0]][$regexp_info[1]] = [$action_info->module, $action_name]; - } - else - { - self::$_internal_forwarded_cache[$regexp_info[0]][$regexp_info[1]] = [$action_info->module, $action_name]; - } - } - if ($action_info->global_route === 'Y') - { - self::$_global_forwarded_cache['reverse'][$action_name] = $action_info->route_config; - } - else - { - self::$_internal_forwarded_cache['reverse'][$action_name] = $action_info->route_config; - } - } - } - return $type === 'internal' ? self::$_internal_forwarded_cache : self::$_global_forwarded_cache; - } - - /** - * Find the best matching route for an array of variables. - * - * @param array $routes - * @param array $vars - * @return string|false - */ - protected static function _getBestMatchingRoute(array $routes, array $vars) - { - // If the action only has one route, select it. - if (count($routes) == 1) - { - $only_route = key($routes); - $matched_arguments = array_intersect_key($routes[$only_route]['vars'], $vars); - if (count($matched_arguments) !== count($routes[$only_route]['vars'])) - { - return false; - } - return $only_route; - } - - // If the action has multiple routes, select the one that matches the most arguments. - else - { - // Order the routes by the number of matched arguments. - $reordered_routes = array(); - foreach ($routes as $route => $route_vars) - { - $matched_arguments = array_intersect_key($route_vars['vars'], $vars); - if (count($matched_arguments) === count($route_vars['vars'])) - { - $reordered_routes[$route] = ($route_vars['priority'] * 1000) + count($matched_arguments); - } - } - if (!count($reordered_routes)) - { - return false; - } - arsort($reordered_routes); - $best_route = array_first_key($reordered_routes); - return $best_route; - } - } - - /** - * Insert variables into a route. - * - * @param string $route - * @param array $vars - * @return string - */ - protected static function _insertRouteVars(string $route, array $vars): string - { - // Replace variable placeholders with actual variable values. - $route = preg_replace_callback('#\\$([a-zA-Z0-9_]+)(:[a-z]+)?#i', function($match) use(&$vars) { - if (isset($vars[$match[1]])) - { - $replacement = urlencode($vars[$match[1]]); - unset($vars[$match[1]]); - return (isset($match[2]) && $match[2] === ':delete') ? '' : $replacement; - } - else - { - return ''; - } - }, $route); - - // Add a query string for the remaining arguments. - return $route . (count($vars) ? ('?' . http_build_query($vars)) : ''); - } + /** + * List of XE-compatible global routes. + */ + protected static $_global_routes = array( + '$document_srl' => array( + 'regexp' => '#^(?[0-9]+)$#', + 'vars' => ['document_srl' => 'int'], + 'priority' => 0, + ), + '$mid' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/?$#', + 'vars' => ['mid' => 'any'], + 'priority' => 0, + ), + '$act' => array( + 'regexp' => '#^(?rss|atom)$#', + 'vars' => ['act' => 'word'], + 'priority' => 0, + ), + '$mid/$document_srl' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?[0-9]+)$#', + 'vars' => ['mid' => 'any', 'document_srl' => 'int'], + 'priority' => 30, + ), + '$mid/category/$category' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/category/(?[0-9]+)$#', + 'vars' => ['mid' => 'any', 'category' => 'int'], + 'priority' => 10, + ), + '$mid/entry/$entry' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/entry/(?[^/]+)$#', + 'vars' => ['mid' => 'any', 'entry' => 'any'], + 'priority' => 0, + ), + '$mid/$act' => array( + 'regexp' => '#^(?[a-zA-Z0-9_-]+)/(?rss|atom|api)$#', + 'vars' => ['mid' => 'any', 'act' => 'word'], + 'priority' => 20, + ), + 'files/download/$file_srl/$file_key/$filename' => array( + 'regexp' => '#^files/download/(?[0-9]+)/(?[a-zA-Z0-9_-]+)/(?[^/]+)$#', + 'vars' => ['file_srl' => 'int', 'file_key' => 'any', 'filename' => 'any'], + 'extra_vars' => ['act' => 'procFileOutput'], + 'priority' => 0, + ), + ); + + /** + * List of legacy modules whose URLs should not be shortened. + */ + protected static $_except_modules = array( + 'socialxe' => true, + ); + + /** + * Internal cache for module and route information. + */ + protected static $_action_cache_prefix = array(); + protected static $_action_cache_module = array(); + protected static $_global_forwarded_cache = array(); + protected static $_internal_forwarded_cache = array(); + protected static $_route_cache = array(); + + /** + * Return the currently configured rewrite level. + * + * 0 = None + * 1 = XE-compatible rewrite rules only + * 2 = Full rewrite support + * + * @return int + */ + public static function getRewriteLevel(): int + { + $level = Config::get('url.rewrite'); + if ($level === null) + { + $level = Config::get('use_rewrite') ? 1 : 0; + } + return intval($level); + } + + /** + * Extract request arguments from the current URL. + * + * @param string $method + * @param string $url + * @param int $rewrite_level + * @return object + */ + public static function parseURL(string $method, string $url, int $rewrite_level) + { + // Get the local part of the current URL. + if (starts_with(\RX_BASEURL, $url)) + { + $url = substr($url, strlen(\RX_BASEURL)); + } + + // Prepare the return object. + $result = new \stdClass; + $result->status = 200; + $result->url = ''; + $result->module = ''; + $result->mid = ''; + $result->act = ''; + $result->forwarded = false; + $result->args = array(); + + // Separate additional arguments from the URL. + $args = array(); + $argstart = strpos($url, '?'); + if ($argstart !== false) + { + @parse_str(substr($url, $argstart + 1), $args); + $url = substr($url, 0, $argstart); + } + + // Decode the URL into plain UTF-8. + $url = $result->url = urldecode($url); + if ($url === '') + { + return $result; + } + if (function_exists('mb_check_encoding') && !mb_check_encoding($url, 'UTF-8')) + { + $result->status = 404; + return $result; + } + + // Try to detect the prefix. This might be $mid. + if ($rewrite_level >= 2 && preg_match('#^([a-zA-Z0-9_-]+)(?:/(.*))?#s', $url, $matches)) + { + // Separate the prefix and the internal part of the URL. + $prefix = $matches[1]; + $internal_url = $matches[2] ?? ''; + $prefix_type = 'mid'; + + // Find the module associated with this prefix. + $action_info = self::_getActionInfoByPrefix($prefix, $module_name = ''); + if ($action_info === false) + { + $action_info = self::_getActionInfoByModule($prefix); + if ($action_info !== false) + { + $module_name = $prefix; + $prefix_type = 'module'; + } + } + + // If a module is found, try its routes. + if ($action_info) + { + // Try the list of routes defined by the module. + foreach ($action_info->route->{$method} as $regexp => $action) + { + if (preg_match($regexp, $internal_url, $matches)) + { + $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); + $allargs = array_merge($args, $matches, [$prefix_type => $prefix, 'act' => $action]); + $result->module = $module_name; + $result->mid = $prefix_type === 'mid' ? $prefix : ''; + $result->act = $action; + $result->args = $allargs; + return $result; + } + } + + // Check other modules. + if ($prefix_type === 'mid') + { + $forwarded_routes = self::_getForwardedRoutes('internal'); + foreach ($forwarded_routes[$method] ?: [] as $regexp => $action) + { + if (preg_match($regexp, $internal_url, $matches)) + { + $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); + $allargs = array_merge($args, $matches, [$prefix_type => $prefix, 'act' => $action[1]]); + $result->module = $action[0]; + $result->mid = $prefix; + $result->act = $action[1]; + $result->forwarded = true; + $result->args = $allargs; + return $result; + } + } + } + + // Try the generic mid/act pattern. + if (preg_match('#^[a-zA-Z0-9_]+$#', $internal_url)) + { + $allargs = array_merge($args, [$prefix_type => $prefix, 'act' => $internal_url]); + $result->module = $module_name; + $result->mid = $prefix_type === 'mid' ? $prefix : ''; + $result->act = $internal_url; + $result->forwarded = true; + $result->args = $allargs; + return $result; + } + + // If the module defines a 404 error handler, call it. + if ($internal_url && isset($action_info->error_handlers[404])) + { + $allargs = array_merge($args, [$prefix_type => $prefix, 'act' => $action_info->error_handlers[404]]); + $result->module = $module_name; + $result->mid = $prefix_type === 'mid' ? $prefix : ''; + $result->act = $action_info->error_handlers[404]; + $result->forwarded = false; + $result->args = $allargs; + return $result; + } + } + } + + // Try registered global routes. + if ($rewrite_level >= 2) + { + $global_routes = self::_getForwardedRoutes('global'); + foreach ($global_routes[$method] ?: [] as $regexp => $action) + { + if (preg_match($regexp, $url, $matches)) + { + $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); + $allargs = array_merge($args, $matches, ['act' => $action[1]]); + $result->module = $action[0]; + $result->act = $action[1]; + $result->forwarded = true; + $result->args = $allargs; + return $result; + } + } + } + + // Try XE-compatible global routes. + foreach (self::$_global_routes as $route_info) + { + if (preg_match($route_info['regexp'], $url, $matches)) + { + $matches = array_filter($matches, 'is_string', \ARRAY_FILTER_USE_KEY); + $allargs = array_merge($args, $matches, $route_info['extra_vars'] ?? []); + $result->module = $allargs['module'] ?? ''; + $result->mid = $allargs['mid'] ?: ''; + $result->act = $allargs['act'] ?: ''; + $result->forwarded = false; + $result->args = $allargs; + return $result; + } + } + + // If no pattern matches, return either an empty route or a 404 error. + $result->module = isset($args['module']) ? $args['module'] : ''; + $result->mid = isset($args['mid']) ? $args['mid'] : ''; + $result->act = isset($args['act']) ? $args['act'] : ''; + $result->args = $args; + if ($url === '' || $url === 'index.php') + { + $result->url = ''; + return $result; + } + else + { + $result->status = 404; + return $result; + } + } + + /** + * Create a URL for the given set of arguments. + * + * @param array $args + * @param int $rewrite_level + * @return string + */ + public static function getURL(array $args, int $rewrite_level): string + { + // If rewrite is turned off, just create a query string. + if ($rewrite_level == 0) + { + return 'index.php?' . http_build_query($args); + } + + // Cache the number of arguments and their keys. + $count = count($args); + $keys = array_keys($args); + + // If there are no arguments, return the URL of the main page. + if ($count == 0) + { + return ''; + } + + // If there is only one argument, try either $mid or $document_srl. + if ($rewrite_level >= 1 && $count == 1 && ($keys[0] === 'mid' || $keys[0] === 'document_srl')) + { + return urlencode($args[$keys[0]]); + } + + // If the list of keys is already cached, return the corresponding route. + $keys_sorted = $keys; sort($keys_sorted); + $keys_string = implode('.', $keys_sorted) . ':' . ($args['mid'] ?? '') . ':' . ($args['act'] ?? ''); + if (isset(self::$_route_cache[$keys_string])) + { + return self::_insertRouteVars(self::$_route_cache[$keys_string], $args); + } + + // Remove $mid and $act from arguments and work with the remainder. + $args2 = $args; unset($args2['module'], $args2['mid'], $args2['act']); + + // If $mid exists, try routes defined in the module. + if ($rewrite_level >= 2 && (isset($args['mid']) || isset($args['module']))) + { + // Get module action info. + if (isset($args['mid'])) + { + $action_info = self::_getActionInfoByPrefix($args['mid']); + $prefix_type = 'mid'; + } + elseif (isset($args['module'])) + { + $action_info = self::_getActionInfoByModule($args['module']); + $prefix_type = 'module'; + } + + // If there is no $act, use the default action. + $act = isset($args['act']) ? $args['act'] : $action_info->default_index_act; + + // Check if $act has any routes defined. + $action = $action_info->action->{$act} ?? null; + if ($action && $action->route) + { + $result = self::_getBestMatchingRoute($action->route, $args2); + if ($result !== false) + { + self::$_route_cache[$keys_string] = '$' . $prefix_type . '/' . $result . '$act:delete'; + return $args[$prefix_type] . '/' . self::_insertRouteVars($result, $args2); + } + } + + // Check other modules for $act. + if ($prefix_type === 'mid') + { + $forwarded_routes = self::_getForwardedRoutes('internal'); + if (isset($forwarded_routes['reverse'][$act])) + { + $result = self::_getBestMatchingRoute($forwarded_routes['reverse'][$act], $args2); + if ($result !== false) + { + self::$_route_cache[$keys_string] = '$' . $prefix_type . '/' . $result . '$act:delete'; + return $args[$prefix_type] . '/' . self::_insertRouteVars($result, $args2); + } + } + } + + // Try the generic mid/act pattern. + if ($prefix_type !== 'module' || !isset(self::$_except_modules[$args[$prefix_type]])) + { + self::$_route_cache[$keys_string] = '$' . $prefix_type . '/$act'; + return $args[$prefix_type] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); + } + } + + // Try registered global routes. + if ($rewrite_level >= 2 && isset($args['act'])) + { + $global_routes = self::_getForwardedRoutes('global'); + if (isset($global_routes['reverse'][$args['act']])) + { + $result = self::_getBestMatchingRoute($global_routes['reverse'][$args['act']], $args2); + if ($result !== false) + { + self::$_route_cache[$keys_string] = $result . '$act:delete'; + return self::_insertRouteVars($result, $args2); + } + } + } + + // Try XE-compatible global routes. + if ($rewrite_level >= 1) + { + if (!isset($args['act']) || ($args['act'] === 'rss' || $args['act'] === 'atom')) + { + $result = self::_getBestMatchingRoute(self::$_global_routes, $args); + if ($result !== false) + { + self::$_route_cache[$keys_string] = $result; + return self::_insertRouteVars($result, $args); + } + } + } + + // If no route matches, just create a query string. + self::$_route_cache[$keys_string] = 'index.php'; + return 'index.php?' . http_build_query($args); + } + + /** + * Load and cache module action info. + * + * @param string $prefix + * @return object + */ + protected static function _getActionInfoByPrefix(string $prefix, string &$module_name = '') + { + if (isset(self::$_action_cache_prefix[$prefix])) + { + $module_name = self::$_action_cache_prefix[$prefix]; + return self::_getActionInfoByModule(self::$_action_cache_prefix[$prefix]) ?: false; + } + + $module_info = \ModuleModel::getModuleInfoByMid($prefix); + if ($module_info && $module_info->module) + { + $module_name = self::$_action_cache_prefix[$prefix] = $module_info->module; + return self::_getActionInfoByModule(self::$_action_cache_prefix[$prefix]) ?: false; + } + else + { + return self::$_action_cache_prefix[$prefix] = false; + } + } + + /** + * Load and cache module action info. + * + * @param string $prefix + * @return object + */ + protected static function _getActionInfoByModule(string $module) + { + if (isset(self::$_action_cache_module[$module])) + { + return self::$_action_cache_module[$module]; + } + + $action_info = \ModuleModel::getModuleActionXml($module); + return self::$_action_cache_module[$module] = $action_info ?: false; + } + + /** + * Get the list of routes that are registered for action-forward. + * + * @param string $type + * @return array + */ + protected static function _getForwardedRoutes(string $type): array + { + if ($type === 'internal' && count(self::$_internal_forwarded_cache)) + { + return self::$_internal_forwarded_cache; + } + if ($type === 'global' && count(self::$_global_forwarded_cache)) + { + return self::$_global_forwarded_cache; + } + + self::$_global_forwarded_cache['GET'] = array(); + self::$_global_forwarded_cache['POST'] = array(); + self::$_global_forwarded_cache['reverse'] = array(); + self::$_internal_forwarded_cache['GET'] = array(); + self::$_internal_forwarded_cache['POST'] = array(); + self::$_internal_forwarded_cache['reverse'] = array(); + + $action_forward = \ModuleModel::getActionForward(); + foreach ($action_forward as $action_name => $action_info) + { + if ($action_info->route_regexp) + { + foreach ($action_info->route_regexp as $regexp_info) + { + if ($action_info->global_route === 'Y') + { + self::$_global_forwarded_cache[$regexp_info[0]][$regexp_info[1]] = [$action_info->module, $action_name]; + } + else + { + self::$_internal_forwarded_cache[$regexp_info[0]][$regexp_info[1]] = [$action_info->module, $action_name]; + } + } + if ($action_info->global_route === 'Y') + { + self::$_global_forwarded_cache['reverse'][$action_name] = $action_info->route_config; + } + else + { + self::$_internal_forwarded_cache['reverse'][$action_name] = $action_info->route_config; + } + } + } + return $type === 'internal' ? self::$_internal_forwarded_cache : self::$_global_forwarded_cache; + } + + /** + * Find the best matching route for an array of variables. + * + * @param array $routes + * @param array $vars + * @return string|false + */ + protected static function _getBestMatchingRoute(array $routes, array $vars) + { + // If the action only has one route, select it. + if (count($routes) == 1) + { + $only_route = key($routes); + $matched_arguments = array_intersect_key($routes[$only_route]['vars'], $vars); + if (count($matched_arguments) !== count($routes[$only_route]['vars'])) + { + return false; + } + return $only_route; + } + + // If the action has multiple routes, select the one that matches the most arguments. + else + { + // Order the routes by the number of matched arguments. + $reordered_routes = array(); + foreach ($routes as $route => $route_vars) + { + $matched_arguments = array_intersect_key($route_vars['vars'], $vars); + if (count($matched_arguments) === count($route_vars['vars'])) + { + $reordered_routes[$route] = ($route_vars['priority'] * 1000) + count($matched_arguments); + } + } + if (!count($reordered_routes)) + { + return false; + } + arsort($reordered_routes); + $best_route = array_first_key($reordered_routes); + return $best_route; + } + } + + /** + * Insert variables into a route. + * + * @param string $route + * @param array $vars + * @return string + */ + protected static function _insertRouteVars(string $route, array $vars): string + { + // Replace variable placeholders with actual variable values. + $route = preg_replace_callback('#\\$([a-zA-Z0-9_]+)(:[a-z]+)?#i', function($match) use(&$vars) { + if (isset($vars[$match[1]])) + { + $replacement = urlencode($vars[$match[1]]); + unset($vars[$match[1]]); + return (isset($match[2]) && $match[2] === ':delete') ? '' : $replacement; + } + else + { + return ''; + } + }, $route); + + // Add a query string for the remaining arguments. + return $route . (count($vars) ? ('?' . http_build_query($vars)) : ''); + } } From 67d42170521fac77f63350e2691f741c899de423 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 22 Jun 2020 00:04:08 +0900 Subject: [PATCH 66/71] Set default language if lang_type is not available --- common/framework/parsers/moduleactionparser.php | 2 +- common/framework/parsers/moduleinfoparser.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index b504b4a00..b8e74a806 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -37,7 +37,7 @@ class ModuleActionParser } // Get the current language. - $lang = \Context::getLangType(); + $lang = \Context::getLangType() ?: 'en'; // Initialize the module definition. $info = new \stdClass; diff --git a/common/framework/parsers/moduleinfoparser.php b/common/framework/parsers/moduleinfoparser.php index 688dfbe15..76daf3af4 100644 --- a/common/framework/parsers/moduleinfoparser.php +++ b/common/framework/parsers/moduleinfoparser.php @@ -23,7 +23,7 @@ class ModuleInfoParser } // Get the current language. - $lang = \Context::getLangType(); + $lang = \Context::getLangType() ?: 'en'; // Initialize the module definition. $info = new \stdClass; From 60247f7d5377ad59f7f2ecc0155d5928d01ac1a0 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 22 Jun 2020 00:04:25 +0900 Subject: [PATCH 67/71] Separate route cache by rewrite level --- common/framework/router.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common/framework/router.php b/common/framework/router.php index 048e494f0..75bb2e1ac 100644 --- a/common/framework/router.php +++ b/common/framework/router.php @@ -306,9 +306,9 @@ class Router // If the list of keys is already cached, return the corresponding route. $keys_sorted = $keys; sort($keys_sorted); $keys_string = implode('.', $keys_sorted) . ':' . ($args['mid'] ?? '') . ':' . ($args['act'] ?? ''); - if (isset(self::$_route_cache[$keys_string])) + if (isset(self::$_route_cache[$rewrite_level][$keys_string])) { - return self::_insertRouteVars(self::$_route_cache[$keys_string], $args); + return self::_insertRouteVars(self::$_route_cache[$rewrite_level][$keys_string], $args); } // Remove $mid and $act from arguments and work with the remainder. @@ -339,7 +339,7 @@ class Router $result = self::_getBestMatchingRoute($action->route, $args2); if ($result !== false) { - self::$_route_cache[$keys_string] = '$' . $prefix_type . '/' . $result . '$act:delete'; + self::$_route_cache[$rewrite_level][$keys_string] = '$' . $prefix_type . '/' . $result . '$act:delete'; return $args[$prefix_type] . '/' . self::_insertRouteVars($result, $args2); } } @@ -353,7 +353,7 @@ class Router $result = self::_getBestMatchingRoute($forwarded_routes['reverse'][$act], $args2); if ($result !== false) { - self::$_route_cache[$keys_string] = '$' . $prefix_type . '/' . $result . '$act:delete'; + self::$_route_cache[$rewrite_level][$keys_string] = '$' . $prefix_type . '/' . $result . '$act:delete'; return $args[$prefix_type] . '/' . self::_insertRouteVars($result, $args2); } } @@ -362,7 +362,7 @@ class Router // Try the generic mid/act pattern. if ($prefix_type !== 'module' || !isset(self::$_except_modules[$args[$prefix_type]])) { - self::$_route_cache[$keys_string] = '$' . $prefix_type . '/$act'; + self::$_route_cache[$rewrite_level][$keys_string] = '$' . $prefix_type . '/$act'; return $args[$prefix_type] . '/' . $args['act'] . (count($args2) ? ('?' . http_build_query($args2)) : ''); } } @@ -376,7 +376,7 @@ class Router $result = self::_getBestMatchingRoute($global_routes['reverse'][$args['act']], $args2); if ($result !== false) { - self::$_route_cache[$keys_string] = $result . '$act:delete'; + self::$_route_cache[$rewrite_level][$keys_string] = $result . '$act:delete'; return self::_insertRouteVars($result, $args2); } } @@ -390,14 +390,14 @@ class Router $result = self::_getBestMatchingRoute(self::$_global_routes, $args); if ($result !== false) { - self::$_route_cache[$keys_string] = $result; + self::$_route_cache[$rewrite_level][$keys_string] = $result; return self::_insertRouteVars($result, $args); } } } // If no route matches, just create a query string. - self::$_route_cache[$keys_string] = 'index.php'; + self::$_route_cache[$rewrite_level][$keys_string] = 'index.php'; return 'index.php?' . http_build_query($args); } From 1bb7248e3ee51d3da3631d8f33688ac060e1edc0 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 22 Jun 2020 00:52:51 +0900 Subject: [PATCH 68/71] Add tests for Router::getURL() and Router::parseURL() --- .../framework/parsers/moduleactionparser.php | 2 +- common/framework/parsers/moduleinfoparser.php | 2 +- tests/unit/framework/RouterTest.php | 65 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/common/framework/parsers/moduleactionparser.php b/common/framework/parsers/moduleactionparser.php index b8e74a806..9b5260cfe 100644 --- a/common/framework/parsers/moduleactionparser.php +++ b/common/framework/parsers/moduleactionparser.php @@ -30,7 +30,7 @@ class ModuleActionParser public static function loadXML(string $filename) { // Load the XML file. - $xml = simplexml_load_file($filename); + $xml = simplexml_load_string(file_get_contents($filename)); if ($xml === false) { return false; diff --git a/common/framework/parsers/moduleinfoparser.php b/common/framework/parsers/moduleinfoparser.php index 76daf3af4..4b58745d1 100644 --- a/common/framework/parsers/moduleinfoparser.php +++ b/common/framework/parsers/moduleinfoparser.php @@ -16,7 +16,7 @@ class ModuleInfoParser public static function loadXML(string $filename) { // Load the XML file. - $xml = simplexml_load_file($filename); + $xml = simplexml_load_string(file_get_contents($filename)); if ($xml === false) { return false; diff --git a/tests/unit/framework/RouterTest.php b/tests/unit/framework/RouterTest.php index a0a9b5a9e..e5fb7f2ae 100644 --- a/tests/unit/framework/RouterTest.php +++ b/tests/unit/framework/RouterTest.php @@ -23,11 +23,76 @@ class RouterTest extends \Codeception\TestCase\Test public function testGetURL() { + $info = Rhymix\Framework\Parsers\ModuleActionParser::loadXML(\RX_BASEDIR . 'modules/board/conf/module.xml'); + $this->assertEquals('dispBoardContent', $info->default_index_act); + $this->assertFalse(isset($info->permission)); + $this->assertGreaterThan(0, count($info->route->GET)); + $this->assertGreaterThan(0, count($info->action->dispBoardContent->route)); + $args = array('mid' => 'board', 'document_srl' => 123); + $this->assertEquals('board/123', Rhymix\Framework\Router::getURL($args, 2)); + $this->assertEquals('board/123', Rhymix\Framework\Router::getURL($args, 1)); + $this->assertEquals('index.php?mid=board&document_srl=123', Rhymix\Framework\Router::getURL($args, 0)); + + $args = array('mid' => 'board', 'act' => 'dispBoardWrite', 'document_srl' => 123); + $this->assertEquals('board/123/edit', Rhymix\Framework\Router::getURL($args, 2)); + $this->assertEquals('index.php?mid=board&act=dispBoardWrite&document_srl=123', Rhymix\Framework\Router::getURL($args, 1)); + $this->assertEquals('index.php?mid=board&act=dispBoardWrite&document_srl=123', Rhymix\Framework\Router::getURL($args, 0)); + + $args = array('mid' => 'board', 'act' => 'dispBoardWrite'); + $this->assertEquals('board/write', Rhymix\Framework\Router::getURL($args, 2)); + $this->assertEquals('index.php?mid=board&act=dispBoardWrite', Rhymix\Framework\Router::getURL($args, 1)); + $this->assertEquals('index.php?mid=board&act=dispBoardWrite', Rhymix\Framework\Router::getURL($args, 0)); + + $args = array('mid' => 'board', 'act' => 'dispBoardModifyComment', 'document_srl' => 123, 'comment_srl' => 456, 'extra_param' => 'foo bar'); + $this->assertEquals('board/comment/456/edit?extra_param=foo+bar', Rhymix\Framework\Router::getURL($args, 2)); + $this->assertEquals('index.php?mid=board&act=dispBoardModifyComment&document_srl=123&comment_srl=456&extra_param=foo+bar', Rhymix\Framework\Router::getURL($args, 1)); + $this->assertEquals('index.php?mid=board&act=dispBoardModifyComment&document_srl=123&comment_srl=456&extra_param=foo+bar', Rhymix\Framework\Router::getURL($args, 0)); + + $args = array('mid' => 'board', 'act' => 'dispMemberInfo'); + $this->assertEquals('board/member_info', Rhymix\Framework\Router::getURL($args, 2)); + $this->assertEquals('index.php?mid=board&act=dispMemberInfo', Rhymix\Framework\Router::getURL($args, 1)); + $this->assertEquals('index.php?mid=board&act=dispMemberInfo', Rhymix\Framework\Router::getURL($args, 0)); + + $args = array('module' => 'document', 'act' => 'procDocumentVoteUp'); + $this->assertEquals('document/procDocumentVoteUp', Rhymix\Framework\Router::getURL($args, 2)); + $this->assertEquals('index.php?module=document&act=procDocumentVoteUp', Rhymix\Framework\Router::getURL($args, 1)); + $this->assertEquals('index.php?module=document&act=procDocumentVoteUp', Rhymix\Framework\Router::getURL($args, 0)); } public function testParseURL() { + $args = array('mid' => 'board', 'act' => 'dispBoardContent', 'document_srl' => '123'); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('GET', 'board/123', 2)->args); + $this->assertEquals('board/123', Rhymix\Framework\Router::parseURL('GET', 'board/123', 2)->url); + $this->assertEquals('board', Rhymix\Framework\Router::parseURL('GET', 'board/123', 2)->mid); + $this->assertEquals('dispBoardContent', Rhymix\Framework\Router::parseURL('GET', 'board/123', 2)->act); + $args = array('mid' => 'board', 'document_srl' => '123'); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('GET', 'board/123', 1)->args); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('GET', 'index.php?mid=board&document_srl=123', 0)->args); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('GET', \RX_BASEURL . 'board/123', 1)->args); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('GET', \RX_BASEURL . 'index.php?mid=board&document_srl=123', 0)->args); + + $args = array('mid' => 'board', 'act' => 'dispBoardModifyComment', 'comment_srl' => '456', 'extra_param' => 'foo bar'); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('GET', 'board/comment/456/edit?extra_param=foo+bar', 2)->args); + $this->assertEquals('board', Rhymix\Framework\Router::parseURL('GET', 'board/comment/456/edit?extra_param=foo+bar', 2)->mid); + $this->assertEquals('dispBoardModifyComment', Rhymix\Framework\Router::parseURL('GET', 'board/comment/456/edit?extra_param=foo+bar', 2)->act); + $this->assertEquals('', Rhymix\Framework\Router::parseURL('GET', 'board/comment/456/edit?extra_param=foo+bar', 2)->document_srl); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('GET', 'index.php?mid=board&act=dispBoardModifyComment&comment_srl=456&extra_param=foo+bar', 1)->args); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('GET', 'index.php?mid=board&act=dispBoardModifyComment&comment_srl=456&extra_param=foo+bar', 0)->args); + + $args = array('mid' => 'board', 'act' => 'dispMemberInfo'); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('GET', 'board/member_info', 2)->args); + $this->assertEquals('board', Rhymix\Framework\Router::parseURL('GET', 'board/member_info', 2)->mid); + + $args = array('mid' => 'board', 'act' => 'dispMemberLoginForm'); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('GET', 'board/login', 2)->args); + $this->assertEquals('member', Rhymix\Framework\Router::parseURL('GET', 'board/login', 2)->module); + + $args = array('mid' => 'board', 'act' => 'procMemberLogin'); + $this->assertEquals($args, Rhymix\Framework\Router::parseURL('POST', 'board/login', 2)->args); + $this->assertEquals('member', Rhymix\Framework\Router::parseURL('POST', 'board/login', 2)->module); + } } From 9a158fde7e176f5bdef0b6318ba90dccd440b894 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 22 Jun 2020 00:57:12 +0900 Subject: [PATCH 69/71] Fix unit tests --- tests/unit/framework/RouterTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/framework/RouterTest.php b/tests/unit/framework/RouterTest.php index e5fb7f2ae..a31cf0311 100644 --- a/tests/unit/framework/RouterTest.php +++ b/tests/unit/framework/RouterTest.php @@ -29,6 +29,8 @@ class RouterTest extends \Codeception\TestCase\Test $this->assertGreaterThan(0, count($info->route->GET)); $this->assertGreaterThan(0, count($info->action->dispBoardContent->route)); + getController('module')->registerActionForwardRoutes('member'); + $args = array('mid' => 'board', 'document_srl' => 123); $this->assertEquals('board/123', Rhymix\Framework\Router::getURL($args, 2)); $this->assertEquals('board/123', Rhymix\Framework\Router::getURL($args, 1)); From 36b8ce75c57fd020cb2ffded4c4d46b7cf51722d Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 22 Jun 2020 10:31:44 +0900 Subject: [PATCH 70/71] Always update module module first --- modules/admin/admin.admin.view.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/admin/admin.admin.view.php b/modules/admin/admin.admin.view.php index 98eeeab16..3a2732c19 100644 --- a/modules/admin/admin.admin.view.php +++ b/modules/admin/admin.admin.view.php @@ -312,7 +312,26 @@ class adminAdminView extends admin { $needUpdate = FALSE; $addTables = FALSE; - foreach($module_list AS $key => $value) + $priority = array( + 'module' => 1000000, + 'member' => 100000, + 'document' => 10000, + 'comment' => 1000, + 'file' => 100, + ); + usort($module_list, function($a, $b) use($priority) { + $a_priority = isset($priority[$a->module]) ? $priority[$a->module] : 0; + $b_priority = isset($priority[$b->module]) ? $priority[$b->module] : 0; + if ($a_priority == 0 && $b_priority == 0) + { + return strcmp($a->module, $b->module); + } + else + { + return $b_priority - $a_priority; + } + }); + foreach($module_list as $value) { if($value->need_install) { From a81690953ce96d3c70807c08d29a659442f65362 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 22 Jun 2020 10:39:40 +0900 Subject: [PATCH 71/71] Add detailed information about the choice of rewrite level --- modules/admin/lang/en.php | 1 + modules/admin/lang/ko.php | 1 + modules/admin/tpl/config_advanced.html | 1 + 3 files changed, 3 insertions(+) diff --git a/modules/admin/lang/en.php b/modules/admin/lang/en.php index c30fea8a5..9a0b06451 100644 --- a/modules/admin/lang/en.php +++ b/modules/admin/lang/en.php @@ -268,6 +268,7 @@ $lang->use_rewrite = 'Use Short URLs'; $lang->use_rewrite_0 = 'None'; $lang->use_rewrite_1 = 'XE-compatible URLs only'; $lang->use_rewrite_2 = 'All supported URLs'; +$lang->about_use_rewrite = 'Your web server must support mod_rewrite in order for short URLs to work. Apache usually detects the .htaccess file automatically.
nginx users should configure rewrite rules according to the manual. Outdated versions of nginx rewrite rules only support XE-compatible short URLs.'; $lang->timezone = 'Time Zone'; $lang->use_mobile_view = 'Enable Mobile View'; $lang->about_use_mobile_view = 'Show mobile page when visitors access with mobile devices.'; diff --git a/modules/admin/lang/ko.php b/modules/admin/lang/ko.php index 2c68f3645..e578a05ee 100644 --- a/modules/admin/lang/ko.php +++ b/modules/admin/lang/ko.php @@ -264,6 +264,7 @@ $lang->use_rewrite = '짧은 주소 사용'; $lang->use_rewrite_0 = '사용하지 않음'; $lang->use_rewrite_1 = 'XE와 호환되는 주소 형태만 사용'; $lang->use_rewrite_2 = '모든 주소 형태를 사용'; +$lang->about_use_rewrite = '짧은 주소를 사용하려면 웹서버에서 rewrite 기능을 지원해야 합니다. 아파치의 경우 .htaccess 파일이 있으면 대부분 자동으로 인식합니다.
nginx 사용시 매뉴얼을 참고하여 직접 설정하셔야 합니다. 구 버전의 nginx 설정은 XE와 호환되는 주소만 지원하니 주의하십시오.'; $lang->timezone = '표준 시간대'; $lang->use_mobile_view = '모바일 뷰 사용'; $lang->about_use_mobile_view = '모바일 기기로 접속시 모바일 페이지를 보여줍니다.'; diff --git a/modules/admin/tpl/config_advanced.html b/modules/admin/tpl/config_advanced.html index 8d1d77abf..d68581567 100644 --- a/modules/admin/tpl/config_advanced.html +++ b/modules/admin/tpl/config_advanced.html @@ -16,6 +16,7 @@ +

{$lang->about_use_rewrite}