diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php
index 9dde027fd..95b5b8776 100644
--- a/classes/context/Context.class.php
+++ b/classes/context/Context.class.php
@@ -163,7 +163,7 @@ class Context
{
// Create a singleton instance and initialize static properties.
self::$_instance = new Context();
- self::$_oFrontEndFileHandler = self::$_instance->oFrontEndFileHandler = new FrontEndFileHandler();
+ self::$_oFrontEndFileHandler = self::$_instance->oFrontEndFileHandler = FrontEndFileHandler::getInstance();
self::$_user_vars = self::$_user_vars ?: new stdClass;
}
return self::$_instance;
diff --git a/classes/display/DisplayHandler.class.php b/classes/display/DisplayHandler.class.php
index d9ed8241c..1d2fd8194 100644
--- a/classes/display/DisplayHandler.class.php
+++ b/classes/display/DisplayHandler.class.php
@@ -9,7 +9,6 @@ class DisplayHandler extends Handler
{
public static $response_size = 0;
public static $debug_printed = 0;
- public $content_size = 0;
public $handler = NULL;
/**
@@ -140,12 +139,15 @@ class DisplayHandler extends Handler
$buff = ltrim($buff, "\n\r\t\v\x00\x20\u{FEFF}");
// call a trigger after display
- self::$response_size = $this->content_size = strlen($output);
ModuleHandler::triggerCall('display', 'after', $output);
+ // Measure the response size.
+ self::$response_size = strlen((string)$output);
+
// Output buffered content only if the current page is HTML.
if ($handler instanceof HTMLDisplayHandler)
{
+ self::$response_size += strlen($buff);
echo $buff;
}
diff --git a/classes/frontendfile/FrontEndFileHandler.class.php b/classes/frontendfile/FrontEndFileHandler.class.php
index a09a69142..f98958256 100644
--- a/classes/frontendfile/FrontEndFileHandler.class.php
+++ b/classes/frontendfile/FrontEndFileHandler.class.php
@@ -53,13 +53,38 @@ class FrontEndFileHandler extends Handler
*/
public $jsBodyMapIndex = array();
+ /**
+ * Logging
+ */
+ protected $_log_enabled = false;
+ protected $_log_entries = [];
+
+ /**
+ * Singleton
+ */
+ protected static $_instance = null;
+
+ /**
+ * Get singleton instance
+ *
+ * @return self
+ */
+ public static function getInstance(): self
+ {
+ if (self::$_instance === null)
+ {
+ self::$_instance = new self();
+ }
+ return self::$_instance;
+ }
+
/**
* Check SSL
*
* @return bool If using ssl returns true, otherwise returns false.
* @deprecated
*/
- public function isSsl()
+ public static function isSsl()
{
return \RX_SSL;
}
@@ -92,6 +117,10 @@ class FrontEndFileHandler extends Handler
{
$args = array($args);
}
+ if ($this->_log_enabled)
+ {
+ $this->_log_entries[] = $args;
+ }
// Replace obsolete paths with current paths.
$args[0] = preg_replace(array_keys(HTMLDisplayHandler::$replacements), array_values(HTMLDisplayHandler::$replacements), $args[0]);
@@ -252,6 +281,26 @@ class FrontEndFileHandler extends Handler
return $file;
}
+ /**
+ * Start logging.
+ */
+ public function startLog()
+ {
+ $this->_log_enabled = true;
+ $this->_log_entries = [];
+ }
+
+ /**
+ * End logging and return the log entries.
+ *
+ * @return array
+ */
+ public function endLog(): array
+ {
+ $this->_log_enabled = false;
+ return $this->_log_entries;
+ }
+
/**
* Process CSS and JS file
*
diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php
index 8d5ebfe70..0a00ad038 100644
--- a/classes/module/ModuleHandler.class.php
+++ b/classes/module/ModuleHandler.class.php
@@ -848,15 +848,10 @@ class ModuleHandler extends Handler
$seo_title = config('seo.subpage_title') ?: '$SITE_TITLE - $SUBPAGE_TITLE';
}
$seo_title = Context::replaceUserLang($seo_title);
- $subpage_title = $module_info->browser_title;
- if (in_array($module_info->module, ['member']))
- {
- $subpage_title = '';
- }
Context::setBrowserTitle($seo_title, array(
'site_title' => Context::getSiteTitle(),
'site_subtitle' => Context::getSiteSubtitle(),
- 'subpage_title' => $subpage_title,
+ 'subpage_title' => $module_info->browser_title,
'page' => Context::get('page') ?: 1,
));
diff --git a/common/composer.json b/common/composer.json
index 72e948084..7e515f2bd 100644
--- a/common/composer.json
+++ b/common/composer.json
@@ -5,7 +5,7 @@
"license": "GPL-2.0-or-later",
"type": "project",
"authors": [
- { "name": "Rhymix Developers and Contributors", "email": "devops@rhymix.org" },
+ { "name": "Poesis Inc. and Contributors", "email": "devops@rhymix.org" },
{ "name": "NAVER", "email": "developers@xpressengine.com" }
],
"config": {
diff --git a/common/constants.php b/common/constants.php
index 800b3ff22..3589e734d 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.21');
+define('RX_VERSION', '2.1.23');
/**
* RX_MICROTIME is the startup time of the current script, in microseconds since the Unix epoch.
diff --git a/common/framework/Template.php b/common/framework/Template.php
index 8330aae8a..f5919beab 100644
--- a/common/framework/Template.php
+++ b/common/framework/Template.php
@@ -945,6 +945,31 @@ class Template
return count($args) ? in_array((string)$validator_id, $args, true) : true;
}
+ /**
+ * Check if the current visitor is using a mobile device for v2.
+ *
+ * @return bool
+ */
+ protected function _v2_isMobile(): bool
+ {
+ return UA::isMobile() && (config('mobile.tablets') || !UA::isTablet());
+ }
+
+ /**
+ * Contextual escape function for v2.
+ *
+ * @param string $str
+ * @return string
+ */
+ protected function _v2_escape($str): string
+ {
+ switch ($this->config->context)
+ {
+ case 'JS': return escape_js(strval($str));
+ default: return escape(strval($str));
+ }
+ }
+
/**
* Lang shortcut for v2.
*
diff --git a/common/framework/parsers/dbquery/VariableBase.php b/common/framework/parsers/dbquery/VariableBase.php
index 5d20ffe00..0c5f984dd 100644
--- a/common/framework/parsers/dbquery/VariableBase.php
+++ b/common/framework/parsers/dbquery/VariableBase.php
@@ -450,14 +450,28 @@ class VariableBase
}
// Check minimum and maximum lengths.
- $length = is_scalar($value) ? iconv_strlen($value, 'UTF-8') : (is_countable($value) ? count($value) : 1);
- if (isset($this->minlength) && $this->minlength > 0 && $length < $this->minlength)
+ $length = null;
+ if (isset($this->minlength) && $this->minlength > 0)
{
- throw new \Rhymix\Framework\Exceptions\QueryError('Variable ' . $this->var . ' for column ' . $column . ' must contain no less than ' . $this->minlength . ' characters');
+ if ($length === null)
+ {
+ $length = is_scalar($value) ? mb_strlen($value, 'UTF-8') : (is_countable($value) ? count($value) : 1);
+ }
+ if ($length < $this->minlength)
+ {
+ throw new \Rhymix\Framework\Exceptions\QueryError('Variable ' . $this->var . ' for column ' . $column . ' must contain no less than ' . $this->minlength . ' characters');
+ }
}
- if (isset($this->maxlength) && $this->maxlength > 0 && $length > $this->maxlength)
+ if (isset($this->maxlength) && $this->maxlength > 0)
{
- throw new \Rhymix\Framework\Exceptions\QueryError('Variable ' . $this->var . ' for column ' . $column . ' must contain no more than ' . $this->maxlength . ' characters');
+ if ($length === null)
+ {
+ $length = is_scalar($value) ? mb_strlen($value, 'UTF-8') : (is_countable($value) ? count($value) : 1);
+ }
+ if ($length > $this->maxlength)
+ {
+ throw new \Rhymix\Framework\Exceptions\QueryError('Variable ' . $this->var . ' for column ' . $column . ' must contain no more than ' . $this->maxlength . ' characters');
+ }
}
}
diff --git a/common/framework/parsers/template/TemplateParser_v2.php b/common/framework/parsers/template/TemplateParser_v2.php
index b1d6c0c46..501845a2a 100644
--- a/common/framework/parsers/template/TemplateParser_v2.php
+++ b/common/framework/parsers/template/TemplateParser_v2.php
@@ -104,8 +104,8 @@ class TemplateParser_v2
'cannot' => ['if ($this->_v2_checkCapability(2, %s)):', 'endif;'],
'canany' => ['if ($this->_v2_checkCapability(3, %s)):', 'endif;'],
'guest' => ['if (!$this->user->isMember()):', 'endif;'],
- 'desktop' => ["if (!\\Context::get('m')):", 'endif;'],
- 'mobile' => ["if (\\Context::get('m')):", 'endif;'],
+ 'desktop' => ['if (!$this->_v2_isMobile()):', 'endif;'],
+ 'mobile' => ['if ($this->_v2_isMobile()):', 'endif;'],
'env' => ['if (!empty($_ENV[%s])):', 'endif;'],
'else' => ['else:'],
'elseif' => ['elseif (%s):'],
@@ -179,20 +179,45 @@ class TemplateParser_v2
*/
protected function _addContextSwitches(string $content): string
{
- return preg_replace_callback('#(config->context = \'CSS\'; ?>' . $match[2] . 'config->context = \'HTML\'; ?>"';
+ }, $content);
+
+ // Inline scripts.
+ $content = preg_replace_callback('#(?<=\s)(href="javascript:|on[a-z]+=")([^"]*?)"#i', function($match) {
+ return $match[1] . 'config->context = \'JS\'; ?>' . $match[2] . 'config->context = \'HTML\'; ?>"';
+ }, $content);
+
+ // config->context = "HTML"; ?>' . $match[1];
+ return 'config->context = \'HTML\'; ?>' . $match[1];
+ }
+ else
+ {
+ return $match[1] . 'config->context = \'CSS\'; ?>';
+ }
+ }, $content);
+
+ // config->context = \'HTML\'; ?>' . $match[1];
}
elseif (!str_contains($match[2] ?? '', 'src="'))
{
- return $match[1] . 'config->context = "JS"; ?>';
+ return $match[1] . 'config->context = \'JS\'; ?>';
}
else
{
return $match[0];
}
}, $content);
+
+ return $content;
}
/**
@@ -203,7 +228,7 @@ class TemplateParser_v2
*/
protected static function _removeContextSwitches(string $content): string
{
- return preg_replace('#<\?php \$this->config->context = "[A-Z]+"; \?>#', '', $content);
+ return preg_replace('#<\?php \$this->config->context = \'[A-Z]+\'; \?>#', '', $content);
}
/**
@@ -235,7 +260,7 @@ class TemplateParser_v2
$basepath = \RX_BASEURL . $this->template->relative_dirname;
// Convert all src and srcset attributes.
- $regexp = '#(<(?:img|audio|video|script|input|source|link)\s[^>]*)(src|srcset|poster)="([^"]+)"#';
+ $regexp = '#(<(?:img|audio|video|script|input|source|link)\s[^>]*)(?<=\s)(src|srcset|poster)="([^"]+)"#';
$content = preg_replace_callback($regexp, function($match) use ($basepath) {
if ($match[2] !== 'srcset')
{
@@ -735,6 +760,7 @@ class TemplateParser_v2
* @dd($var, $var, ...)
* @stack('name')
* @url(['mid' => $mid, 'act' => $act])
+ * @widget('name', $args)
*
* @param string $content
* @return string
@@ -748,7 +774,7 @@ class TemplateParser_v2
// Insert JSON, lang codes, and dumps.
$parentheses = self::_getRegexpForParentheses(2);
- $content = preg_replace_callback('#(?', $args, $args);
case 'lang':
- return sprintf('config->context === \'JS\' ? escape_js($this->_v2_lang(%s)) : $this->_v2_lang(%s); ?>', $args, $args);
+ return sprintf('config->context === \'HTML\' ? $this->_v2_lang(%s) : $this->_v2_escape($this->_v2_lang(%s)); ?>', $args, $args);
case 'dump':
return sprintf('', $args);
case 'dd':
@@ -765,7 +791,9 @@ class TemplateParser_v2
case 'stack':
return sprintf('', $args);
case 'url':
- return sprintf('config->context === \'JS\' ? escape_js(getNotEncodedUrl(%s)) : getUrl(%s); ?>', $args, $args);
+ return sprintf('config->context === \'HTML\' ? getUrl(%s) : $this->_v2_escape(getNotEncodedUrl(%s)); ?>', $args, $args);
+ case 'widget':
+ return sprintf('execute(%s); ?>', $args);
default:
return $match[0];
}
@@ -797,6 +825,15 @@ class TemplateParser_v2
return $this->_arrangeOutputFilters($match);
}, $content);
+ // Exclude {single} curly braces in non-HTML contexts.
+ $content = preg_replace_callback('#(<\?php \$this->config->context = \'(?:CSS|JS)\'; \?>)(.*?)(<\?php \$this->config->context = \'HTML\'; \?>)#s', function($match) {
+ $match[2] = preg_replace_callback('#(?' : '';
+ return '{' . $warning . $m[1] . '}';
+ }, $match[2]);
+ return $match[1] . $match[2] . $match[3];
+ }, $content);
+
// Convert {single} curly braces.
$content = preg_replace_callback('#(?config->context === 'JS' ? escape_js({$str2}) : htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false)";
+ return "\$this->config->context === 'HTML' ? htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape({$str2})";
case 'autocontext_json':
return "\$this->config->context === 'JS' ? {$str2} : htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false)";
case 'autocontext_lang':
- return "\$this->config->context === 'JS' ? escape_js({$str2}) : ({$str})";
+ return "\$this->config->context === 'HTML' ? ({$str}) : \$this->_v2_escape({$str2})";
case 'autoescape':
return "htmlspecialchars({$str}, \ENT_QUOTES, 'UTF-8', false)";
case 'autolang':
diff --git a/common/functions.php b/common/functions.php
index 84147358d..7ddb59b59 100644
--- a/common/functions.php
+++ b/common/functions.php
@@ -3,7 +3,7 @@
/**
* Function library for Rhymix
*
- * Copyright (c) Rhymix Developers and Contributors
+ * Copyright (c) Poesis Inc. and Contributors
*/
/**
@@ -205,7 +205,7 @@ function escape($str, bool $double_escape = true, bool $except_lang_code = false
*/
function escape_css(string $str): string
{
- return preg_replace('/[^a-zA-Z0-9_.#\/-]/', '', (string)$str);
+ return preg_replace('/[^a-zA-Z0-9_.,#%\/\'()\x20-]/', '', (string)$str);
}
/**
@@ -680,7 +680,14 @@ function utf8_mbencode($str): string
*/
function utf8_normalize_spaces($str, bool $multiline = false): string
{
- return $multiline ? preg_replace('/((?!\x0A)[\pZ\pC])+/u', ' ', (string)$str) : preg_replace('/[\pZ\pC]+/u', ' ', (string)$str);
+ if ($multiline)
+ {
+ return preg_replace(['/((?!\x0A)[\pZ\pC])+/u', '/\x20*\x0A\x20*/'], [' ', "\n"], (string)$str);
+ }
+ else
+ {
+ return preg_replace('/[\pZ\pC]+/u', ' ', (string)$str);
+ }
}
/**
diff --git a/index.php b/index.php
index d5df354aa..fe4bb0a06 100644
--- a/index.php
+++ b/index.php
@@ -8,7 +8,7 @@
*
* -----------------------------------------------------------------------------
*
- * Copyright (c) Rhymix Developers and Contributors
+ * Copyright (c) Poesis Inc. and Contributors
* Copyright (c) NAVER
*
* This program is free software: you can redistribute it and/or modify it
diff --git a/modules/admin/tpl/css/admin.css b/modules/admin/tpl/css/admin.css
index c3a09a36a..8ef7ff6a5 100644
--- a/modules/admin/tpl/css/admin.css
+++ b/modules/admin/tpl/css/admin.css
@@ -185,6 +185,11 @@ body>.x,
height: 24px;
padding: 0 6px;
}
+@media screen and (max-width: 800px) {
+ .x .x_pagination {
+ clear: both;
+ }
+}
.x .btn {
color: #333;
}
diff --git a/modules/admin/tpl/js/admin.js b/modules/admin/tpl/js/admin.js
index 02d6ae0c8..f3152ef88 100644
--- a/modules/admin/tpl/js/admin.js
+++ b/modules/admin/tpl/js/admin.js
@@ -1299,7 +1299,7 @@ jQuery(function($){
position = {x:event.pageX, y:event.pageY};
offset = getOffset($tr.get(0), ofspar);
- $clone = $tr.attr('target', true).clone(true).appendTo($table);
+ $clone = $tr.attr('target', true).clone(true).find('input').removeAttr('id name').end().appendTo($table);
// get colspan
cols = ($th=$table.find('thead th')).length;
diff --git a/modules/board/board.controller.php b/modules/board/board.controller.php
index 830a70f85..e8bc4aec4 100644
--- a/modules/board/board.controller.php
+++ b/modules/board/board.controller.php
@@ -290,30 +290,30 @@ class BoardController extends Board
public function procBoardRevertDocument()
{
$update_id = Context::get('update_id');
- $logged_info = Context::get('logged_info');
- if(!$update_id)
+ if (!$update_id)
{
throw new Rhymix\Framework\Exception('msg_no_update_id');
}
- $oDocumentController = DocumentController::getInstance();
$update_log = DocumentModel::getUpdateLog($update_id);
-
- if($logged_info->is_admin != 'Y')
- {
- $Exists_log = DocumentModel::getUpdateLogAdminisExists($update_log->document_srl);
- if($Exists_log === true)
- {
- throw new Rhymix\Framework\Exception('msg_admin_update_log');
- }
- }
-
- if(!$update_log)
+ if (!$update_log)
{
throw new Rhymix\Framework\Exception('msg_no_update_log');
}
$oDocument = DocumentModel::getDocument($update_log->document_srl);
+ if (!$oDocument->isGranted())
+ {
+ throw new Rhymix\Framework\Exceptions\NotPermitted();
+ }
+ if (!$this->user->isAdmin())
+ {
+ if (DocumentModel::getUpdateLogAdminisExists($update_log->document_srl))
+ {
+ throw new Rhymix\Framework\Exception('msg_admin_update_log');
+ }
+ }
+
$obj = new stdClass();
$obj->title = $update_log->title;
$obj->document_srl = $update_log->document_srl;
@@ -322,10 +322,19 @@ class BoardController extends Board
$obj->content = $update_log->content;
$obj->update_log_setting = 'Y';
$obj->reason_update = lang('board.revert_reason_update');
+ $oDocumentController = DocumentController::getInstance();
$output = $oDocumentController->updateDocument($oDocument, $obj);
- $this->setRedirectUrl(getNotEncodedUrl('', 'mid', Context::get('mid'),'act', '', 'document_srl', $update_log->document_srl));
+ if (!$output->toBool())
+ {
+ return $output;
+ }
+
$this->add('mid', Context::get('mid'));
$this->add('document_srl', $update_log->document_srl);
+ $this->setRedirectUrl(getNotEncodedUrl([
+ 'mid' => Context::get('mid'),
+ 'document_srl' => $update_log->document_srl,
+ ]));
}
/**
diff --git a/modules/board/board.view.php b/modules/board/board.view.php
index 82953eea3..5bf138204 100644
--- a/modules/board/board.view.php
+++ b/modules/board/board.view.php
@@ -1524,6 +1524,12 @@ class BoardView extends Board
throw new Rhymix\Framework\Exception('msg_not_target');
}
+ $features = Rhymix\Modules\Board\Models\Features::fromModuleInfo($this->module_info);
+ if (!$features->{$target}->vote_log)
+ {
+ throw new Rhymix\Framework\Exceptions\FeatureDisabled;
+ }
+
$output = executeQueryArray($queryId, $args);
if(!$output->toBool())
{
@@ -1538,7 +1544,11 @@ class BoardView extends Board
{
if($log->point > 0)
{
- if($log->member_srl == $vote_member_infos[$log->member_srl]->member_srl)
+ if (isset($vote_member_infos[$log->member_srl]))
+ {
+ continue;
+ }
+ if (!$features->{$target}->vote_up_log)
{
continue;
}
@@ -1546,7 +1556,11 @@ class BoardView extends Board
}
else
{
- if($log->member_srl == $blame_member_infos[$log->member_srl]->member_srl)
+ if (isset($blame_member_infos[$log->member_srl]))
+ {
+ continue;
+ }
+ if (!$features->{$target}->vote_down_log)
{
continue;
}
@@ -1554,6 +1568,8 @@ class BoardView extends Board
}
}
}
+
+ Context::set('board_features', $features);
Context::set('vote_member_info', $vote_member_infos);
Context::set('blame_member_info', $blame_member_infos);
$this->setTemplateFile('vote_log');
diff --git a/modules/board/models/Features.php b/modules/board/models/Features.php
index 6bbc567ec..5f815be70 100644
--- a/modules/board/models/Features.php
+++ b/modules/board/models/Features.php
@@ -61,8 +61,10 @@ class Features
// Document features
$features->document->vote_up = ($document_config->use_vote_up ?? 'Y') !== 'N';
+ $features->document->vote_up_log = ($document_config->use_vote_up ?? 'Y') === 'S';
$features->document->vote_down = ($document_config->use_vote_down ?? 'Y') !== 'N';
- $features->document->vote_log = ($document_config->use_vote_up ?? 'Y') === 'S' || ($document_config->use_vote_down ?? 'Y') === 'S';
+ $features->document->vote_down_log = ($document_config->use_vote_down ?? 'Y') === 'S';
+ $features->document->vote_log = $features->document->vote_up_log || $features->document->vote_down_log;
if (isset($document_config->allow_vote_cancel))
{
$features->document->cancel_vote = $document_config->allow_vote_cancel === 'Y';
@@ -92,8 +94,10 @@ class Features
// Comment features
$features->comment->vote_up = ($comment_config->use_vote_up ?? 'Y') !== 'N';
+ $features->comment->vote_up_log = ($comment_config->use_vote_up ?? 'Y') === 'S';
$features->comment->vote_down = ($comment_config->use_vote_down ?? 'Y') !== 'N';
- $features->comment->vote_log = ($comment_config->use_vote_up ?? 'Y') === 'S' || ($comment_config->use_vote_down ?? 'Y') === 'S';
+ $features->comment->vote_down_log = ($comment_config->use_vote_down ?? 'Y') === 'S';
+ $features->comment->vote_log = $features->comment->vote_up_log || $features->comment->vote_down_log;
if (isset($comment_config->allow_vote_cancel))
{
$features->comment->cancel_vote = $comment_config->allow_vote_cancel === 'Y';
diff --git a/modules/board/skins/xedition/_read.html b/modules/board/skins/xedition/_read.html
index 7a48f42eb..5c676a658 100644
--- a/modules/board/skins/xedition/_read.html
+++ b/modules/board/skins/xedition/_read.html
@@ -117,7 +117,7 @@
{$lang->cmd_document_vote_user}
-
+
{$lang->update_log}
{$lang->cmd_modify}
diff --git a/modules/comment/schemas/comments.xml b/modules/comment/schemas/comments.xml
index 6c6a6f34a..b689a2fe9 100644
--- a/modules/comment/schemas/comments.xml
+++ b/modules/comment/schemas/comments.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/modules/comment/tpl/comment_list.html b/modules/comment/tpl/comment_list.html
index 838df8911..ff14e4691 100644
--- a/modules/comment/tpl/comment_list.html
+++ b/modules/comment/tpl/comment_list.html
@@ -83,7 +83,6 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
-