diff --git a/classes/frontendfile/FrontEndFileHandler.class.php b/classes/frontendfile/FrontEndFileHandler.class.php index 034d190aa..bca493014 100644 --- a/classes/frontendfile/FrontEndFileHandler.class.php +++ b/classes/frontendfile/FrontEndFileHandler.class.php @@ -624,7 +624,8 @@ class FrontEndFileHandler extends Handler { $url .= '?t=' . filemtime($file->fileFullPath); } - $result[] = array('file' => $url); + $attrs = empty($file->jstype) ? '' : (' type="' . $file->jstype . '"'); + $result[] = array('file' => $url, 'attrs' => $attrs); } else { @@ -642,7 +643,7 @@ class FrontEndFileHandler extends Handler Rhymix\Framework\Storage::write(\RX_BASEDIR . $concat_filename, $concat_content); } $concat_filename .= '?t=' . filemtime(\RX_BASEDIR . $concat_filename); - $result[] = array('file' => \RX_BASEURL . $concat_filename); + $result[] = array('file' => \RX_BASEURL . $concat_filename, 'attrs' => ''); } } } diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index 0d5b4d3d8..e2ede1a5b 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -235,7 +235,7 @@ class ModuleObject extends BaseObject catch (Rhymix\Framework\Exception $e) { $this->stop($e->getMessage(), -2); - $this->add('rx_error_location', $e->getFile() . ':' . $e->getLine()); + $this->add('rx_error_location', $e->getUserFileAndLine()); } } @@ -857,8 +857,7 @@ class ModuleObject extends BaseObject catch (Rhymix\Framework\Exception $e) { $output = new BaseObject(-2, $e->getMessage()); - $location = $e->getFile() . ':' . $e->getLine(); - $output->add('rx_error_location', $location); + $output->add('rx_error_location', $e->getUserFileAndLine()); } // Trigger after specific action diff --git a/classes/validator/Validator.class.php b/classes/validator/Validator.class.php index b8f563648..f6ba5ba19 100644 --- a/classes/validator/Validator.class.php +++ b/classes/validator/Validator.class.php @@ -389,8 +389,8 @@ class Validator } /** - * Returns the last error infomation including a field name and an error message. - * @return array The last error infomation + * Returns the last error information including a field name and an error message. + * @return array The last error information */ function getLastError() { diff --git a/common/constants.php b/common/constants.php index fde0b485f..9a11bed4a 100644 --- a/common/constants.php +++ b/common/constants.php @@ -3,7 +3,7 @@ /** * RX_VERSION is the version number of the Rhymix CMS. */ -define('RX_VERSION', '2.1.25'); +define('RX_VERSION', '2.1.26'); /** * RX_MICROTIME is the startup time of the current script, in microseconds since the Unix epoch. diff --git a/common/framework/DB.php b/common/framework/DB.php index f01339d36..b436ce9e8 100644 --- a/common/framework/DB.php +++ b/common/framework/DB.php @@ -1113,29 +1113,43 @@ class DB */ public function addPrefixes(string $query_string): string { + // Return early if no prefix is set. if (!$this->_prefix) { return $query_string; } + + // Generate a list of common table expressions (CTEs) to exclude from prefixing. + if (preg_match_all('/\bWITH(?:\s+RECURSIVE)?\s+`?(\w+)`?\s+AS\b/', $query_string, $matches)) + { + $exceptions = $matches[1]; + } else { - return preg_replace_callback('/((?:DELETE\s+)?FROM|JOIN|INTO|UPDATE)(?i)\s+((?:`?\w+\`?)(?:\s+AS\s+`?\w+`?)?(?:\s*,\s*(?:`?\w+\`?)(?:\s+AS\s+`?\w+`?)?)*)/', function($m) { - $type = strtoupper($m[1]); - $tables = array_map(function($str) use($type) { - return preg_replace_callback('/`?(\w+)`?(?:\s+AS\s+`?(\w+)`?)?/i', function($m) use($type) { - if ($type === 'FROM' || $type === 'JOIN') - { - return isset($m[2]) ? sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[2]) : sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[1]); - } - else - { - return isset($m[2]) ? sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[2]) : sprintf('`%s%s`', $this->_prefix, $m[1]); - } - }, trim($str)); - }, explode(',', $m[2])); - return $m[1] . ' ' . implode(', ', $tables); - }, $query_string); + $exceptions = []; } + + // Add prefixes to all other table names in the query string. + return preg_replace_callback('/\b((?:DELETE\s+)?FROM|JOIN|INTO|(?_prefix, $m[1], $m[2]) : sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[1]); + } + else + { + return isset($m[2]) ? sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[2]) : sprintf('`%s%s`', $this->_prefix, $m[1]); + } + }, trim($str)); + }, explode(',', $m[2])); + return $m[1] . ' ' . implode(', ', $tables); + }, $query_string); } /** @@ -1254,7 +1268,7 @@ class DB if (isset($backtrace[$no])) { $result['called_method'] = ($backtrace[$no]['class'] ?? '') . ($backtrace[$no]['type'] ?? '') . ($backtrace[$no]['function'] ?? ''); - $result['backtrace'] = $this->_debug_full_stack ? array_slice($backtrace, $no) : []; + $result['backtrace'] = $this->_debug_full_stack ? array_slice($backtrace, $no - 1) : []; } else { diff --git a/common/framework/Exception.php b/common/framework/Exception.php index 0ff93aa68..6cd07d412 100644 --- a/common/framework/Exception.php +++ b/common/framework/Exception.php @@ -7,5 +7,27 @@ namespace Rhymix\Framework; */ class Exception extends \Exception { + /** + * Get the file and line, skipping Rhymix framework files. + * + * This can be more helpful than just using getFile() and getLine() + * when the exception is thrown from a Rhymix framework file + * but the actual error is caused by a module or theme. + * + * @return string + */ + public function getUserFileAndLine(): string + { + $regexp = '!^' . preg_quote(\RX_BASEDIR, '!') . '(?:classes|common)/!'; + $trace = $this->getTrace(); + foreach ($trace as $frame) + { + if (!preg_match($regexp, $frame['file'])) + { + return $frame['file'] . ':' . $frame['line']; + } + } + return $this->getFile() . ':' . $this->getLine(); + } } diff --git a/common/js/debug.js b/common/js/debug.js index 9214ec41f..86ab6f548 100644 --- a/common/js/debug.js +++ b/common/js/debug.js @@ -148,7 +148,7 @@ $(function() { if (data.queries[i].backtrace && data.queries[i].backtrace.length) { backtrace = $('').appendTo(description.find('li:first-child')); for (j in data.queries[i].backtrace) { - if (data.queries[i].backtrace[j].file) { + if (j > 0 && data.queries[i].backtrace[j].file) { backtrace.append($('
  • ').text(data.queries[i].backtrace[j].file + ":" + data.queries[i].backtrace[j].line)); } } diff --git a/common/tpl/debug_comment.html b/common/tpl/debug_comment.html index 32faf73cb..b07a39ff3 100644 --- a/common/tpl/debug_comment.html +++ b/common/tpl/debug_comment.html @@ -126,7 +126,7 @@ Database Queries echo sprintf(' - Call Stack: %s', $query_caller) . ($query->count > 1 ? (' (×' . $query->count . ')') : '') . "\n"; foreach ($query->backtrace ?? [] as $key => $backtrace) { - if (isset($backtrace['file']) && isset($backtrace['line'])) + if ($key > 0 && isset($backtrace['file']) && isset($backtrace['line'])) { echo sprintf(' %s line %d', $backtrace['file'], $backtrace['line']) . "\n"; } diff --git a/modules/admin/tpl/css/admin.bootstrap.css b/modules/admin/tpl/css/admin.bootstrap.css index f59f3fde2..bf4fc74a8 100644 --- a/modules/admin/tpl/css/admin.bootstrap.css +++ b/modules/admin/tpl/css/admin.bootstrap.css @@ -310,8 +310,6 @@ .x input.x_full-width, .x textarea.x_full-width, .x .x_uneditable-input.x_full-width{width:calc(100% - 14px);resize:vertical} -.x textarea.x_full-width.lang_code{width:calc(100% - 42px);resize:vertical} -.x textarea.x_full-width + textarea.lang_code{width:calc(100% - 42px);resize:vertical} .x textarea{height:auto;min-height:80px;} .x textarea, .x input[type="text"], diff --git a/modules/admin/tpl/css/admin.css b/modules/admin/tpl/css/admin.css index f5da99642..b93ec383e 100644 --- a/modules/admin/tpl/css/admin.css +++ b/modules/admin/tpl/css/admin.css @@ -1476,6 +1476,20 @@ margin-bottom: 10px; } } + +.x .g11n.x_input-append { + display: inline-flex; + align-items: flex-start; +} +.x .g11n.x_full-width { + width: 100%; +} +.x .g11n > input.lang_code, +.x .g11n > textarea.lang_code { + flex: 1; + width: unset; + min-width: 0; +} .x .g11n>.x_add-on { font-size: 0; position: relative; @@ -1492,8 +1506,9 @@ margin-bottom: 10px; } .x .g11n>.x_add-on.remover { display: none; - width: 26px; - height: 26px; + height: 16px; + padding: 4px; + box-sizing: content-box; } .x .g11n.active>[disabled] { padding-left: 25px; @@ -1501,7 +1516,7 @@ margin-bottom: 10px; background-repeat: no-repeat; } .x .g11n.active>.x_add-on.remover { - display: inline-block; + display: block; } .x .g11n>.x_add-on:hover>i { opacity: 1; diff --git a/modules/admin/tpl/js/admin.js b/modules/admin/tpl/js/admin.js index 7881bcdf5..4d3d52c45 100644 --- a/modules/admin/tpl/js/admin.js +++ b/modules/admin/tpl/js/admin.js @@ -367,7 +367,17 @@ jQuery(function($){ $.fn.tableSpan = function(){ this.each(function(){ var $this = $(this); - var thNum = $this.find('>thead>tr:eq(0)>th').length; + var thList = $this.find('>thead>tr:eq(0)>th'); + var thNum = 0; + thList.each(function(){ + var $th = $(this); + if($th.attr('colspan')){ // th의 colspan 반영 + thNum += parseInt($th.attr('colspan'), 10); + } else { + thNum++; + } + }); + var $tdTarget = $this.find('>tbody>tr:eq(0)>td:only-child'); if(thNum != $tdTarget.attr('colspan')){ $tdTarget.attr('colspan', thNum).css('text-align','center'); @@ -1906,6 +1916,7 @@ jQuery(function($){ // make UI var $this = $(this); + var width = $this.width(); var t = this; if($this.parent().hasClass('g11n')){ @@ -1923,13 +1934,12 @@ jQuery(function($){ function makeUI(){ var $multilingualWindow = $('#g11n'); - var width = $this.width(); var $displayInput; if(t.tagName == 'TEXTAREA' || $this.data('type') == 'textarea'){ - $displayInput = $('