Merge branch 'rhymix:master' into master

This commit is contained in:
Lastorder 2025-05-02 17:07:35 +09:00 committed by GitHub
commit d327bb1926
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 588 additions and 221 deletions

View file

@ -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;

View file

@ -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;
}

View file

@ -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
*

View file

@ -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,
));

View file

@ -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": {

View file

@ -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.

View file

@ -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.
*

View file

@ -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');
}
}
}

View file

@ -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('#(<script\b([^>]*)|</script)#i', function($match) {
// Inline styles.
$content = preg_replace_callback('#(?<=\s)(style=")([^"]*?)"#i', function($match) {
return $match[1] . '<?php $this->config->context = \'CSS\'; ?>' . $match[2] . '<?php $this->config->context = \'HTML\'; ?>"';
}, $content);
// Inline scripts.
$content = preg_replace_callback('#(?<=\s)(href="javascript:|on[a-z]+=")([^"]*?)"#i', function($match) {
return $match[1] . '<?php $this->config->context = \'JS\'; ?>' . $match[2] . '<?php $this->config->context = \'HTML\'; ?>"';
}, $content);
// <style> tags.
$content = preg_replace_callback('#(<style\b([^>]*)|</style)#i', function($match) {
if (substr($match[1], 1, 1) === '/')
{
return '<?php $this->config->context = "HTML"; ?>' . $match[1];
return '<?php $this->config->context = \'HTML\'; ?>' . $match[1];
}
else
{
return $match[1] . '<?php $this->config->context = \'CSS\'; ?>';
}
}, $content);
// <script> tags that aren't links.
$content = preg_replace_callback('#(<script\b([^>]*)|</script)#i', function($match) {
if (substr($match[1], 1, 1) === '/')
{
return '<?php $this->config->context = \'HTML\'; ?>' . $match[1];
}
elseif (!str_contains($match[2] ?? '', 'src="'))
{
return $match[1] . '<?php $this->config->context = "JS"; ?>';
return $match[1] . '<?php $this->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('#(?<!@)@(json|lang|dump|stack|url)\x20?('. $parentheses . ')#', function($match) {
$content = preg_replace_callback('#(?<!@)@(json|lang|dump|dd|stack|url|widget)\x20?('. $parentheses . ')#', function($match) {
$args = self::_convertVariableScope(substr($match[2], 1, -1));
switch ($match[1])
{
@ -757,7 +783,7 @@ class TemplateParser_v2
'json_encode(%s, self::$_json_options2) : ' .
'htmlspecialchars(json_encode(%s, self::$_json_options), \ENT_QUOTES, \'UTF-8\', false); ?>', $args, $args);
case 'lang':
return sprintf('<?php echo $this->config->context === \'JS\' ? escape_js($this->_v2_lang(%s)) : $this->_v2_lang(%s); ?>', $args, $args);
return sprintf('<?php echo $this->config->context === \'HTML\' ? $this->_v2_lang(%s) : $this->_v2_escape($this->_v2_lang(%s)); ?>', $args, $args);
case 'dump':
return sprintf('<?php ob_start(); var_dump(%s); \$__dump = ob_get_clean(); echo rtrim(\$__dump); ?>', $args);
case 'dd':
@ -765,7 +791,9 @@ class TemplateParser_v2
case 'stack':
return sprintf('<?php echo implode("\n", self::\$_stacks[%s] ?? []) . "\n"; ?>', $args);
case 'url':
return sprintf('<?php echo $this->config->context === \'JS\' ? escape_js(getNotEncodedUrl(%s)) : getUrl(%s); ?>', $args, $args);
return sprintf('<?php echo $this->config->context === \'HTML\' ? getUrl(%s) : $this->_v2_escape(getNotEncodedUrl(%s)); ?>', $args, $args);
case 'widget':
return sprintf('<?php echo \WidgetController::getInstance()->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('#(?<!\{)\{(?!\s)([^{}]+?)\}#', function($m) {
$warning = preg_match('#^\$\w#', $m[1]) ? '<?php trigger_error("Template v1 syntax not allowed in CSS/JS context", \E_USER_WARNING); ?>' : '';
return '&#x1B;&#x7B;' . $warning . $m[1] . '&#x1B;&#x7D;';
}, $match[2]);
return $match[1] . $match[2] . $match[3];
}, $content);
// Convert {single} curly braces.
$content = preg_replace_callback('#(?<!\{)\{(?!\s)([^{}]+?)\}#', [$this, '_arrangeOutputFilters'], $content);
@ -943,11 +980,11 @@ class TemplateParser_v2
switch($option)
{
case 'autocontext':
return "\$this->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':

View file

@ -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);
}
}
/**

View file

@ -8,7 +8,7 @@
*
* -----------------------------------------------------------------------------
*
* Copyright (c) Rhymix Developers and Contributors <devops@rhymix.org>
* Copyright (c) Poesis Inc. and Contributors <devops@rhymix.org>
* Copyright (c) NAVER <http://www.navercorp.com>
*
* This program is free software: you can redistribute it and/or modify it

View file

@ -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;
}

View file

@ -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;

View file

@ -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,
]));
}
/**

View file

@ -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');

View file

@ -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';

View file

@ -117,7 +117,7 @@
<!--@if($board_features->document->vote_log)-->
<a class="btn" href="{getUrl('', 'mid', $mid, 'act', 'dispBoardVoteLog', 'target_srl', $oDocument->document_srl, 'target', 'document')}"><i class="xi-list-ul"></i>{$lang->cmd_document_vote_user}</a>
<!--@endif-->
<!--@if($board_features->document->history)-->
<!--@if($module_info->update_log == 'Y' && $grant->can('update_view'))-->
<a cond="$update_view" class="btn" href="{getUrl('', 'mid', $mid, 'act', 'dispBoardUpdateLog', 'document_srl', $oDocument->document_srl)}"><i class="xi-list-ul"></i>{$lang->update_log}</a>
<!--@endif-->
<a cond="$oDocument->isEditable()" class="btn" href="{getUrl('', 'mid', $mid, 'act', 'dispBoardWrite', 'document_srl', $oDocument->document_srl)}"><i class="xi-eraser"></i>{$lang->cmd_modify}</a>

View file

@ -8,7 +8,7 @@
<column name="voted_count" type="number" size="11" default="0" notnull="notnull" index="idx_voted_count" />
<column name="blamed_count" type="number" size="11" default="0" notnull="notnull" index="idx_blamed_count" />
<column name="notify_message" type="char" size="1" default="N" notnull="notnull" />
<column name="password" type="varchar" size="60" />
<column name="password" type="varchar" size="250" />
<column name="user_id" type="varchar" size="80" />
<column name="user_name" type="varchar" size="80" notnull="notnull" />
<column name="nick_name" type="varchar" size="80" notnull="notnull" index="idx_nick_name" />

View file

@ -83,7 +83,6 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
</div>
</form>
<form action="./" class="x_pagination">
<input type="hidden" name="error_return_url" value="" />
<input type="hidden" name="module" value="{$module}" />

View file

@ -139,11 +139,11 @@ class CommunicationView extends communication
if ($message)
{
Context::addBrowserTitle($message->title);
MemberView::setMemberPageBrowserTitle($message->title);
}
else
{
Context::addBrowserTitle(lang('communication.message_box.' . $message_type));
MemberView::setMemberPageBrowserTitle(lang('communication.message_box.' . $message_type));
}
$this->setTemplateFile($template_filename);
@ -186,7 +186,7 @@ class CommunicationView extends communication
Context::set('message', $message);
}
Context::addBrowserTitle($message->title ?? lang('cmd_view_message_box'));
MemberView::setMemberPageBrowserTitle($message->title ?? lang('cmd_view_message_box'));
$this->setTemplateFile('new_message');
}
@ -313,7 +313,7 @@ class CommunicationView extends communication
$editor = $oEditorModel->getEditor(getNextSequence(), $option);
$editor = $editor . "\n" . '<input type="hidden" name="temp_srl" value="" />' . "\n";
Context::set('editor', $editor);
Context::addBrowserTitle(lang('cmd_send_message'));
MemberView::setMemberPageBrowserTitle(lang('cmd_send_message'));
$this->setTemplateFile('send_message');
// Fix for skins that don't support window_type=self
@ -387,7 +387,7 @@ class CommunicationView extends communication
Context::set('friend_list', $output->data);
Context::set('page_navigation', $output->page_navigation);
Context::addBrowserTitle(lang('cmd_view_friend'));
MemberView::setMemberPageBrowserTitle(lang('cmd_view_friend'));
$this->setTemplateFile('friends');
}
@ -460,7 +460,7 @@ class CommunicationView extends communication
$friend_group_list = $oCommunicationModel->getFriendGroups();
Context::set('friend_group_list', $friend_group_list);
Context::addBrowserTitle(lang('cmd_add_friend'));
MemberView::setMemberPageBrowserTitle(lang('cmd_add_friend'));
$this->setTemplateFile('add_friend');
// Fix for skins that don't support window_type=self
@ -524,7 +524,7 @@ class CommunicationView extends communication
}
}
Context::addBrowserTitle(lang('cmd_add_friend_group'));
MemberView::setMemberPageBrowserTitle(lang('cmd_add_friend_group'));
$this->setTemplateFile('add_friend_group');
// Fix for skins that don't support window_type=self

View file

@ -14,7 +14,7 @@
<column name="comment_count" type="number" size="11" default="0" notnull="notnull" index="idx_comment_count" />
<column name="trackback_count" type="number" size="11" default="0" notnull="notnull" index="idx_trackback_count" />
<column name="uploaded_count" type="number" size="11" default="0" notnull="notnull" index="idx_uploaded_count" />
<column name="password" type="varchar" size="60" />
<column name="password" type="varchar" size="250" />
<column name="user_id" type="varchar" size="80" />
<column name="user_name" type="varchar" size="80" notnull="notnull" />
<column name="nick_name" type="varchar" size="80" notnull="notnull" index="idx_nick_name" />

View file

@ -72,16 +72,15 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
</tr>
</tbody>
</table>
<div class="x_clearfix">
<div class="x_btn-group x_pull-right">
<a href="#manageForm" class="x_btn modalAnchor" data-value="trash">{$lang->trash}</a>
<a href="#manageForm" class="x_btn modalAnchor" data-value="delete">{$lang->delete}</a>
<a href="#manageForm" class="x_btn modalAnchor" data-value="move">{$lang->move}</a>
<a href="#manageForm" class="x_btn modalAnchor" data-value="copy">{$lang->copy}</a>
</div>
<div class="x_btn-group x_pull-right">
<a href="#manageForm" class="x_btn modalAnchor" data-value="trash">{$lang->trash}</a>
<a href="#manageForm" class="x_btn modalAnchor" data-value="delete">{$lang->delete}</a>
<a href="#manageForm" class="x_btn modalAnchor" data-value="move">{$lang->move}</a>
<a href="#manageForm" class="x_btn modalAnchor" data-value="copy">{$lang->copy}</a>
</div>
</form>
<form action="./" class="x_pagination x_pull-left" style="margin:-36px 0 0 0">
<form action="./" class="x_pagination">
<input type="hidden" name="error_return_url" value="" />
<input type="hidden" name="module" value="{$module}" />
<input type="hidden" name="act" value="{$act}" />

View file

@ -190,10 +190,15 @@ class Value
{
return $this->default;
}
elseif (is_array($this->options))
if (is_array($this->options))
{
return $this->default;
}
elseif ($this->default)
{
return array_first($this->getOptions());
}
else
{
return null;

View file

@ -1194,7 +1194,7 @@ class FileController extends File
{
$adjusted['type'] = 'mp4';
}
elseif (!empty($config->image_autoconv[$image_info['type']]))
elseif (!empty($config->image_autoconv[$image_info['type']]) && tobool($config->image_autoconv[$image_info['type']]))
{
$adjusted['type'] = $config->image_autoconv[$image_info['type']];
}
@ -1723,14 +1723,18 @@ class FileController extends File
// Get a full list of attachments
$args = new stdClass;
$args->module_srl = $module_srl;
$output = executeQueryArray('file.getModuleFiles', $args);
if(!$output->toBool() || empty($file_list = $output->data))
$output = executeQueryArray('file.getModuleFilesProper', $args);
if (!$output->toBool())
{
return $output;
}
if (!$output->data)
{
return;
}
// Delete the file
return $this->deleteFile($file_list);
// Delete each file.
return $this->deleteFile($output->data);
}
/**
@ -1971,8 +1975,16 @@ class FileController extends File
function triggerMoveDocument($obj)
{
$obj->upload_target_srls = $obj->document_srls;
executeQuery('file.updateFileModule', $obj);
executeQuery('file.updateFileModuleComment', $obj);
$output = executeQuery('file.updateFileModule', $obj);
if (!$output->toBool())
{
return $output;
}
$output = executeQuery('file.updateFileModuleComment', $obj);
if (!$output->toBool())
{
return $output;
}
}
function triggerAddCopyDocument(&$obj)

View file

@ -0,0 +1,31 @@
<query id="getModuleFilesProper" action="select">
<tables>
<table name="files" />
<table name="documents" type="left join">
<conditions>
<condition operation="equal" column="files.upload_target_srl" default="documents.document_srl" />
</conditions>
</table>
<table name="comments" type="left join">
<conditions>
<condition operation="equal" column="files.upload_target_srl" default="comments.comment_srl" />
</conditions>
</table>
</tables>
<columns>
<column name="files.*" />
<column name="documents.module_srl" alias="d_module_srl" />
<column name="comments.module_srl" alias="c_module_srl" />
</columns>
<conditions>
<condition operation="equal" column="files.module_srl" var="module_srl" filter="number" notnull="notnull" />
<group pipe="and">
<condition operation="equal" column="documents.module_srl" var="module_srl" filter="number" notnull="notnull" />
<condition operation="null" column="documents.module_srl" pipe="or" />
</group>
<group pipe="and">
<condition operation="equal" column="comments.module_srl" var="module_srl" filter="number" notnull="notnull" />
<condition operation="null" column="comments.module_srl" pipe="or" />
</group>
</conditions>
</query>

View file

@ -1,13 +1,13 @@
<query id="updateFileModuleComment" action="update">
<tables>
<table name="files" />
<table name="comments" />
<table name="files" alias="f" />
<table name="comments" alias="c" />
</tables>
<columns>
<column name="files.module_srl" var="module_srl" filter="number" notnull="notnull" />
<column name="f.module_srl" var="module_srl" filter="number" notnull="notnull" />
</columns>
<conditions>
<condition operation="equal" column="files.upload_target_srl" default="comments.comment_srl" />
<condition operation="in" column="comments.document_srl" var="upload_target_srls" filter="number" notnull="notnull" pipe="and" />
<condition operation="equal" column="f.upload_target_srl" default="c.comment_srl" />
<condition operation="in" column="c.document_srl" var="upload_target_srls" filter="number" notnull="notnull" pipe="and" />
</conditions>
</query>

View file

@ -1,5 +1,5 @@
<p>
Copyright &copy; Rhymix Developers and Contributors<br />
Copyright &copy; Poesis Inc. and Contributors<br />
Copyright &copy; <a href="https://navercorp.com/" target="_blank">NAVER</a> &amp; <a href="https://www.xehub.io/" target="_blank">XEHub</a>
</p>

View file

@ -1,5 +1,5 @@
<p>
Copyright &copy; Rhymix Developers and Contributors<br />
Copyright &copy; Poesis Inc. and Contributors<br />
Copyright &copy; <a href="https://navercorp.com/" target="_blank">NAVER</a> &amp; <a href="https://www.xehub.io/" target="_blank">XEHub</a>
</p>

View file

@ -1,20 +1,6 @@
<?php
/* Copyright (C) Rhymix <https://rhymix.org> */
/**
* The view class of the integration_search module
*
* @author Rhymix Developers and Contributors <devops@rhymix.org>
*/
class integration_searchMobile extends integration_searchView
{
/**
* Search Result
*
* @return Object
*/
function IS()
{
parent::IS();
}
}
}

View file

@ -390,6 +390,7 @@ class LayoutAdminController extends Layout
if(!is_dir($path)) FileHandler::makeDir($path);
$filename = strtolower($source['name']);
$filename = Rhymix\Framework\Filters\FilenameFilter::clean($filename);
if($filename != urlencode($filename))
{
$ext = substr(strrchr($filename,'.'),1);
@ -407,9 +408,19 @@ class LayoutAdminController extends Layout
*/
function procLayoutAdminUserImageDelete()
{
$filename = Context::get('filename');
$layout_srl = Context::get('layout_srl');
$this->removeUserLayoutImage($layout_srl,$filename);
if (!$layout_srl)
{
throw new Rhymix\Framework\Exceptions\InvalidRequest();
}
$filename = Context::get('filename');
if (preg_match('!(\.\.|[/\\\\])!', $filename))
{
throw new Rhymix\Framework\Exceptions\InvalidRequest();
}
$this->removeUserLayoutImage($layout_srl, $filename);
$this->setMessage('success_deleted');
$this->setRedirectUrl(Context::get('error_return_url'));
}
@ -418,13 +429,19 @@ class LayoutAdminController extends Layout
* delete image into user layout
* @param int $layout_srl
* @param string $filename
* @return void
* @return bool
*/
function removeUserLayoutImage($layout_srl,$filename)
{
$oLayoutModel = getModel('layout');
$path = $oLayoutModel->getUserLayoutImagePath($layout_srl);
@unlink($path . $filename);
$path = FileHandler::getRealPath($path . Rhymix\Framework\Filters\FilenameFilter::clean($filename));
if (!Rhymix\Framework\Storage::exists($path))
{
throw new Rhymix\Framework\Exceptions\TargetNotFound();
}
return Rhymix\Framework\Storage::delete($path);
}
// deprecated

View file

@ -1788,7 +1788,10 @@ class MemberController extends Member
$chk_args = new stdClass;
$chk_args->member_srl = $member_info->member_srl;
$output = executeQuery('member.chkAuthMail', $chk_args);
if($output->toBool() && $output->data->count != '0') throw new Rhymix\Framework\Exception('msg_user_not_confirmed');
if ($output->toBool() && $output->data->count > 0)
{
throw new Rhymix\Framework\Exception(sprintf('msg_user_not_confirmed', $member_info->email_address));
}
}
// Get password reset method

View file

@ -114,6 +114,24 @@ class MemberView extends Member
}
}
/**
* Set the browser title for a page belonging to the member menu.
*
* @param string $title
* @return void
*/
public static function setMemberPageBrowserTitle(string $title): void
{
$seo_title = config('seo.subpage_title') ?: '$SITE_TITLE - $SUBPAGE_TITLE';
$seo_title = Context::replaceUserLang($seo_title);
Context::setBrowserTitle($seo_title, array(
'site_title' => Context::getSiteTitle(),
'site_subtitle' => Context::getSiteSubtitle(),
'subpage_title' => $title,
'page' => Context::get('page') ?: 1,
));
}
/**
* Module index
*/
@ -181,7 +199,7 @@ class MemberView extends Member
$member_info->group_list[$key] = Context::replaceUserLang($val, true);
}
Context::addBrowserTitle(lang('cmd_view_member_info'));
self::setMemberPageBrowserTitle(lang('cmd_view_member_info'));
Context::set('memberInfo', get_object_vars($member_info));
$extendForm = MemberModel::getCombineJoinForm($member_info);
@ -376,7 +394,7 @@ class MemberView extends Member
$member_config->agreement = $member_config->agreements[1]->content ?? '';
// Set a template file
Context::addBrowserTitle(lang('cmd_signup'));
self::setMemberPageBrowserTitle(lang('cmd_signup'));
$this->setTemplateFile('signup_form');
}
@ -413,7 +431,7 @@ class MemberView extends Member
Context::set('identifierValue', $logged_info->user_id);
}
Context::addBrowserTitle(lang('cmd_modify_member_info'));
self::setMemberPageBrowserTitle(lang('cmd_modify_member_info'));
$this->setTemplateFile('rechecked_password');
}
@ -499,7 +517,7 @@ class MemberView extends Member
$this->addExtraFormValidatorMessage();
// Set a template file
Context::addBrowserTitle(lang('cmd_modify_member_info'));
self::setMemberPageBrowserTitle(lang('cmd_modify_member_info'));
$this->setTemplateFile('modify_info');
}
@ -550,7 +568,7 @@ class MemberView extends Member
$oSecurity = new Security();
$oSecurity->encodeHTML('document_list...title', 'search_target', 'search_keyword');
Context::addBrowserTitle(lang('cmd_view_own_document'));
self::setMemberPageBrowserTitle(lang('cmd_view_own_document'));
$this->setTemplateFile('document_list');
}
@ -598,7 +616,7 @@ class MemberView extends Member
$oSecurity = new Security();
$oSecurity->encodeHTML('search_target', 'search_keyword');
Context::addBrowserTitle(lang('cmd_view_own_comment'));
self::setMemberPageBrowserTitle(lang('cmd_view_own_comment'));
$this->setTemplateFile('comment_list');
}
@ -708,7 +726,7 @@ class MemberView extends Member
$security = new Security($output->data);
$security->encodeHTML('..nick_name');
Context::addBrowserTitle(lang('cmd_view_scrapped_document'));
self::setMemberPageBrowserTitle(lang('cmd_view_scrapped_document'));
$this->setTemplateFile('scrapped_list');
}
@ -743,7 +761,7 @@ class MemberView extends Member
Context::set('document_list', $output->data);
Context::set('page_navigation', $output->page_navigation);
Context::addBrowserTitle(lang('cmd_view_saved_document'));
self::setMemberPageBrowserTitle(lang('cmd_view_saved_document'));
$this->setTemplateFile('saved_list');
}
@ -783,7 +801,7 @@ class MemberView extends Member
$output = executeQueryArray('member.getMemberDevice', $args);
Context::set('registered_devices', $output->data);
Context::addBrowserTitle(lang('cmd_view_active_logins'));
self::setMemberPageBrowserTitle(lang('cmd_view_active_logins'));
$this->setTemplateFile('active_logins');
}
@ -822,7 +840,7 @@ class MemberView extends Member
}
// Set a template file
Context::addBrowserTitle(lang('cmd_login'));
self::setMemberPageBrowserTitle(lang('cmd_login'));
$this->setTemplateFile('login_form');
}
@ -858,7 +876,7 @@ class MemberView extends Member
Context::set('formValue', $member_info->email_address);
}
// Set a template file
Context::addBrowserTitle(lang('cmd_modify_member_password'));
self::setMemberPageBrowserTitle(lang('cmd_modify_member_password'));
$this->setTemplateFile('modify_password');
}
@ -893,7 +911,7 @@ class MemberView extends Member
Context::set('formValue', $member_info->email_address);
}
// Set a template file
Context::addBrowserTitle(lang('msg_leave_member'));
self::setMemberPageBrowserTitle(lang('msg_leave_member'));
$this->setTemplateFile('leave_form');
}
@ -943,7 +961,7 @@ class MemberView extends Member
Context::set('identifier', $this->member_config->identifier);
Context::set('enable_find_account_question', 'N');
Context::addBrowserTitle(lang('cmd_find_member_account'));
self::setMemberPageBrowserTitle(lang('cmd_find_member_account'));
$this->setTemplateFile('find_member_account');
}
@ -962,7 +980,7 @@ class MemberView extends Member
return;
}
Context::addBrowserTitle(lang('cmd_resend_auth_mail'));
self::setMemberPageBrowserTitle(lang('cmd_resend_auth_mail'));
$this->setTemplateFile('resend_auth_mail');
}
@ -982,7 +1000,7 @@ class MemberView extends Member
$_SESSION['rechecked_password_step'] = 'INPUT_DATA';
Context::addBrowserTitle(lang('cmd_modify_member_email_address'));
self::setMemberPageBrowserTitle(lang('cmd_modify_member_email_address'));
$this->setTemplateFile('modify_email_address');
}
@ -1101,7 +1119,7 @@ class MemberView extends Member
Context::set('nickname_list', $output->data);
Context::set('page_navigation', $output->page_navigation);
Context::addBrowserTitle(lang('cmd_modify_nickname_log'));
self::setMemberPageBrowserTitle(lang('cmd_modify_nickname_log'));
$this->setTemplateFile('member_nick');
}
}

View file

@ -28,7 +28,7 @@ class NcenterliteView extends Ncenterlite
Context::set('ncenterlite_list', $output->data);
Context::set('page_navigation', $output->page_navigation);
Context::addBrowserTitle(lang('ncenterlite_my_list'));
MemberView::setMemberPageBrowserTitle(lang('ncenterlite_my_list'));
$this->setTemplateFileOrDefault('NotifyList');
}
@ -86,7 +86,7 @@ class NcenterliteView extends Ncenterlite
Context::set('sms_available', Rhymix\Framework\SMS::getDefaultDriver()->getName() !== 'Dummy');
Context::set('push_available', count(Rhymix\Framework\Config::get('push.types') ?? []) > 0);
Context::addBrowserTitle(lang('ncenterlite_my_settings'));
MemberView::setMemberPageBrowserTitle(lang('ncenterlite_my_settings'));
$this->setTemplateFileOrDefault('userconfig');
}
@ -140,7 +140,7 @@ class NcenterliteView extends Ncenterlite
Context::set('unsubscribe_list', $output->data);
Context::set('page_navigation', $output->page_navigation);
Context::addBrowserTitle(lang('unsubscribe_list'));
MemberView::setMemberPageBrowserTitle(lang('unsubscribe_list'));
$this->setTemplateFileOrDefault('unsubscribeList');
}
@ -230,7 +230,7 @@ class NcenterliteView extends Ncenterlite
Context::set('text', $text);
Context::set('type', $type);
Context::addBrowserTitle(lang('unsubscribe_list'));
MemberView::setMemberPageBrowserTitle(lang('unsubscribe_list'));
$this->setTemplateFileOrDefault('unsubscribe');
}

View file

@ -305,8 +305,17 @@ class PageAdminView extends Page
Context::set('oDocument', $oDocument);
Context::set('mid', $this->module_info->mid);
$this->setLayoutFile('');
$this->setTemplateFile('article_content_modify');
$this->setLayoutAndTemplatePaths($isMobile ? 'M' : 'P', $this->module_info);
$skin_path = rtrim($this->getTemplatePath(), '/') . '/';
if (file_exists($skin_path . 'content_modify.blade.php') || file_exists($skin_path . 'content_modify.html'))
{
$this->setTemplateFile('content_modify');
}
else
{
$this->setTemplatePath($this->module_path . 'tpl');
$this->setTemplateFile('article_content_modify');
}
}
/**

View file

@ -57,10 +57,10 @@ class PointModel extends Point
}
// Get from file cache
$cache_path = sprintf(RX_BASEDIR . 'files/member_extra_info/point/%s', getNumberingPath($member_srl));
$cache_filename = sprintf('%s/%d.cache.txt', $cache_path, $member_srl);
if (!$from_db && !$use_cache)
{
$cache_path = sprintf(RX_BASEDIR . 'files/member_extra_info/point/%s', getNumberingPath($member_srl));
$cache_filename = sprintf('%s/%d.cache.txt', $cache_path, $member_srl);
if (file_exists($cache_filename))
{
$point = trim(Rhymix\Framework\Storage::read($cache_filename));

View file

@ -383,60 +383,49 @@ class WidgetController extends Widget
$widget_cache = intval(floatval($widget_cache) * 60);
}
/**
* Even if the cache number and value of the cache and return it to extract data
*/
// If widget cache is disabled, just execute the widget and return the result.
if(!$ignore_cache && !$widget_cache)
{
$oWidget = $this->getWidgetObject($widget);
if(!$oWidget || !method_exists($oWidget, 'proc')) return;
if (!$oWidget || !method_exists($oWidget, 'proc'))
{
return;
}
$widget_content = $oWidget->proc($args);
$widget_content = Context::replaceUserLang($widget_content);
return $widget_content;
return Context::replaceUserLang($widget_content);
}
$cache_data = Rhymix\Framework\Cache::get('widget_cache:' . $widget_sequence);
if ($cache_data)
// If cached data exists, return it.
$cache_key = 'widget_cache:' . $widget_sequence . ':' . $lang_type;
$cache_data = Rhymix\Framework\Cache::get($cache_key);
if (is_object($cache_data) && isset($cache_data->assets))
{
// Load the variables, need to load the LESS or SCSS files.
if(is_object($cache_data))
foreach ($cache_data->assets as $asset)
{
foreach ($cache_data->variables as $key => $value)
{
Context::set($key, $value);
}
$cache_data = $cache_data->content;
Context::loadFile($asset);
}
return str_replace('<!--#Meta:', '<!--Meta:', $cache_data);
return Context::replaceUserLang($cache_data->content);
}
// Otherwise, execute the widget, cache the result, and return it.
$oWidget = $this->getWidgetObject($widget);
if(!$oWidget || !method_exists($oWidget,'proc')) return;
if (!$oWidget || !method_exists($oWidget, 'proc'))
{
return;
}
$oFrontEndFileHandler = FrontEndFileHandler::getInstance();
$oFrontEndFileHandler->startLog();
$widget_content = $oWidget->proc($args);
$widget_content = Context::replaceUserLang($widget_content);
Rhymix\Framework\Cache::set('widget_cache:' . $widget_sequence, $widget_content, $widget_cache, true);
$cache_data = new stdClass;
$cache_data->assets = $oFrontEndFileHandler->endLog();
$cache_data->content = $widget_content;
Rhymix\Framework\Cache::set($cache_key, $cache_data, $widget_cache, true);
// Keep the variables, need to load the LESS or SCSS files.
if(preg_match_all('/<!--#Meta:([a-z0-9\_\-\/\.\@\:]+)(\?\$\_\_Context\-\>[a-z0-9\_\-\/\.\@\:]+)?-->/is', $widget_content, $widget_var_matches, PREG_SET_ORDER))
{
$cache_content = new stdClass();
$cache_content->content = $widget_content;
$cache_content->variables = new stdClass();
foreach($widget_var_matches as $matches)
{
if(isset($matches[2]) && $matches[2])
{
$key = str_replace('?$__Context->', '', $matches[2]);
$cache_content->variables->{$key} = Context::get($key);
}
}
Rhymix\Framework\Cache::set('widget_cache:' . $widget_sequence, $cache_content, $widget_cache, true);
}
return $widget_content;
return Context::replaceUserLang($widget_content);
}
/**
@ -450,14 +439,21 @@ class WidgetController extends Widget
// Save for debug run-time widget
$start = microtime(true);
// urldecode the value of args haejum
$object_vars = get_object_vars($args);
if(count($object_vars))
// Type juggling
if (is_array($args))
{
foreach($object_vars as $key => $val)
$args = (object)$args;
}
// Apply urldecode for backward compatibility
if ($escaped)
{
foreach (get_object_vars($args) ?: [] as $key => $val)
{
if(in_array($key, array('widgetbox_content','body','class','style','widget_sequence','widget','widget_padding_left','widget_padding_top','widget_padding_bottom','widget_padding_right','widgetstyle','document_srl'))) continue;
if($escaped) $args->{$key} = utf8RawUrlDecode($val);
if (!in_array($key, ['body', 'class', 'style', 'document_srl', 'widget', 'widget_sequence', 'widgetstyle', 'widgetbox_content', 'widget_padding_left', 'widget_padding_top', 'widget_padding_bottom', 'widget_padding_right']))
{
$args->{$key} = utf8RawUrlDecode($val);
}
}
}
@ -810,10 +806,14 @@ class WidgetController extends Widget
if($vars->widget_sequence)
{
Rhymix\Framework\Cache::delete('widget_cache:' . $vars->widget_sequence);
$lang_type = Context::getLangType();
Rhymix\Framework\Cache::delete('widget_cache:' . $vars->widget_sequence . ':' . $lang_type);
}
if($vars->widget_cache>0) $vars->widget_sequence = getNextSequence();
if($vars->widget_cache > 0)
{
$vars->widget_sequence = getNextSequence();
}
$attribute = array();
foreach($vars as $key => $val)

View file

@ -0,0 +1,26 @@
<a href="javascript:alert('Hello \u003C\u0022world\u0022\u003E (\u0027string\u0027) variable.jpg')">
Hello &lt;&quot;world&quot;&gt; (&#039;string&#039;) variable.jpg</p>
<p onclick="location.href = 'Hello \u003C\u0022world\u0022\u003E (\u0027string\u0027) variable.jpg';">
<span style="font: Hello &lt;&quot;world&quot;&gt; (&#039;string&#039;) variable.jpg">
Hello &lt;&quot;world&quot;&gt; (&#039;string&#039;) variable.jpg </span>
</p>
<script type="text/javascript">
const str = "Hello \u003C\u0022world\u0022\u003E ('string') variable.jpg";
const tpl = `template literal with ${var} inside`;
const fun = function() {
const foo = 'Hello \u003C\u0022world\u0022\u003E (\u0027string\u0027) variable.jpg';
const bar = 'Hello <"world"> ('string') variable.jpg';
}
</script>
<style>
body{background-image: url('Hello &lt;&quot;world&quot;&gt; (&#039;string&#039;) variable.jpg')}
</style>
<ul class="test">
<li>Hello &lt;&quot;world&quot;&gt; (&#039;string&#039;) variable.jpg</li>
<li>Hello <"world"> ('string') variable.jpg</li>
</ul>

View file

@ -0,0 +1,29 @@
@version(2)
<a href="javascript:alert('{{ $var }}')">
{{ $var }}
</p>
<p onclick="location.href = '{{ $var }}';">
<span style="font: {{ $var }}">
{{ $var }}
</span>
</p>
<script type="text/javascript">
const str = @json($var);
const tpl = `template literal with ${var} inside`;
const fun = function() {
const foo = '{{ $var }}';
const bar = '{{ $var|noescape }}';
}
</script>
<style>
body{background-image: url('{{ $var }}')}
</style>
<ul class="test">
<li>{{ $var }}</li>
<li>{{ $var|noescape }}</li>
</ul>

View file

@ -15,16 +15,16 @@
{{ $foo }}
<form action="<?php echo $this->config->context === 'JS' ? escape_js(\RX_BASEURL) : htmlspecialchars(\RX_BASEURL, \ENT_QUOTES, 'UTF-8', false); ?>" method="post">
<form action="<?php echo $this->config->context === 'HTML' ? htmlspecialchars(\RX_BASEURL, \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape(\RX_BASEURL); ?>" method="post">
<input type="hidden" name="_rx_csrf_token" value="<?php echo \Rhymix\Framework\Session::getGenericToken(); ?>" />
<input type="text"<?php if (Context::getInstance()->get('foo')): ?> required="required"<?php endif; ?>>
<input type="text" value="<?php echo $this->config->context === 'JS' ? escape_js($__Context->bar[0] ?? '') : htmlspecialchars($__Context->bar[0] ?? '', \ENT_QUOTES, 'UTF-8', false); ?>"<?php if ($__Context->bar[3] === 'da'): ?> required="required"<?php endif; ?> />
<input type="text" value="<?php echo $this->config->context === 'HTML' ? htmlspecialchars($__Context->bar[0] ?? '', \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape($__Context->bar[0] ?? ''); ?>"<?php if ($__Context->bar[3] === 'da'): ?> required="required"<?php endif; ?> />
</form>
<div<?php if (!(isset($__Context->baz))): ?> class="foobar"<?php endif; ?>>
<?php if ($__Context->foo || $__Context->bar): ?>
<p>Hello <?php if ($__Context->bar): ?><?php echo $__Context->foo ?? ''; ?><?php endif; ?></p>
<p><?php echo $this->config->context === 'JS' ? escape_js(implode('|', array_map(function($i) { return strtoupper($i); }, $__Context->bar))) : htmlspecialchars(implode('|', array_map(function($i) { return strtoupper($i); }, $__Context->bar)), \ENT_QUOTES, 'UTF-8', false); ?></p>
<p><?php echo $this->config->context === 'HTML' ? htmlspecialchars(implode('|', array_map(function($i) { return strtoupper($i); }, $__Context->bar)), \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape(implode('|', array_map(function($i) { return strtoupper($i); }, $__Context->bar))); ?></p>
<?php endif; ?>
</div>
@ -33,7 +33,7 @@
<div>
<?php if (empty($__Context->nosuchvar)): ?>
<img src="/rhymix/tests/_data/template/bar/rhymix.svg" alt="unit tests are cool" />
<span <?php if ($__Context->k >= 2): ?>class="<?php echo $this->config->context === 'JS' ? escape_js($__Context->val ?? '') : htmlspecialchars($__Context->val ?? '', \ENT_QUOTES, 'UTF-8', false); ?>"<?php endif; ?>></span>
<span <?php if ($__Context->k >= 2): ?>class="<?php echo $this->config->context === 'HTML' ? htmlspecialchars($__Context->val ?? '', \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape($__Context->val ?? ''); ?>"<?php endif; ?>></span>
<?php endif; ?>
</div>
<?php $this->_v2_incrLoopVar($__loop_RANDOM_LOOP_ID); endforeach; $this->_v2_removeLoopVar($__loop_RANDOM_LOOP_ID); unset($__loop_RANDOM_LOOP_ID); else: ?><div>Nothing here...</div><?php endif; ?>
@ -42,8 +42,8 @@
<?php (function($__filename, $__vars, $__varname, $__empty = null) { if (!$__vars): $__vars = []; if ($__empty): $__filename = $__empty; $__vars[] = ''; endif; endif; foreach ($__vars as $__var): echo $this->_v2_include("include", $__filename, [(string)$__varname => $__var]); endforeach; })('incl/eachtest', $__Context->bar, 'var'); ?>
<?php (function($__filename, $__vars, $__varname, $__empty = null) { if (!$__vars): $__vars = []; if ($__empty): $__filename = $__empty; $__vars[] = ''; endif; endif; foreach ($__vars as $__var): echo $this->_v2_include("include", $__filename, [(string)$__varname => $__var]); endforeach; })('incl/eachtest', [], 'anything', 'incl/empty'); ?>
<?php if (!\Context::get('m')): ?>
<p>The full class name is <?php echo htmlspecialchars(get_class(new Rhymix\Framework\Push), \ENT_QUOTES, 'UTF-8', true); ?>, <?php echo $this->config->context === 'JS' ? escape_js(Rhymix\Framework\Push::class) : htmlspecialchars(Rhymix\Framework\Push::class, \ENT_QUOTES, 'UTF-8', false); ?> really.</p>
<?php if (!$this->_v2_isMobile()): ?>
<p>The full class name is <?php echo htmlspecialchars(get_class(new Rhymix\Framework\Push), \ENT_QUOTES, 'UTF-8', true); ?>, <?php echo $this->config->context === 'HTML' ? htmlspecialchars(Rhymix\Framework\Push::class, \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape(Rhymix\Framework\Push::class); ?> really.</p>
<?php endif; ?>
<div class="barContainer" data-bar="<?php echo $this->config->context === 'JS' ? json_encode($__Context->bar ?? '', self::$_json_options) : htmlspecialchars(json_encode($__Context->bar ?? '', self::$_json_options), \ENT_QUOTES, 'UTF-8', false); ?>">
@ -60,7 +60,11 @@
]); ?>></span>
</div>
<script type="text/javascript"<?php $this->config->context = "JS"; ?>>
const foo = '<?php echo $this->config->context === 'JS' ? escape_js($__Context->foo ?? '') : htmlspecialchars($__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false); ?>';
<script type="text/javascript"<?php $this->config->context = 'JS'; ?>>
const foo = '<?php echo $this->config->context === 'HTML' ? htmlspecialchars($__Context->foo ?? '', \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape($__Context->foo ?? ''); ?>';
const bar = <?php echo $this->config->context === 'JS' ? json_encode($__Context->bar, self::$_json_options2) : htmlspecialchars(json_encode($__Context->bar, self::$_json_options), \ENT_QUOTES, 'UTF-8', false); ?>;
<?php $this->config->context = "HTML"; ?></script>
<?php $this->config->context = 'HTML'; ?></script>
<style<?php $this->config->context = 'CSS'; ?>>
body { background-color: <?php echo $this->config->context === 'HTML' ? htmlspecialchars('#ffffff', \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape('#ffffff'); ?>; }
<?php $this->config->context = 'HTML'; ?></style>

View file

@ -60,3 +60,7 @@
const foo = 'FOOFOO\u003C\u0022FOO\u0022\u003EBAR';
const bar = ["Rhy","miX","is","da","BEST!"];
</script>
<style>
body { background-color: #ffffff; }
</style>

View file

@ -64,3 +64,7 @@
const foo = '{{ $foo }}';
const bar = @json($bar);
</script>
<style>
body { background-color: {{ '#ffffff' }}; }
</style>

View file

@ -248,11 +248,39 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
$this->assertEquals($target, $this->_parse($source));
}
public function testContextSwitches()
{
// <script> tag
$source = '<script type="text/javascript"> foobar(); </script>';
$target = '<script type="text/javascript"<?php $this->config->context = \'JS\'; ?>> foobar(); <?php $this->config->context = \'HTML\'; ?></script>';
$this->assertEquals($target, $this->_parse($source, true, false));
// Inline script in link href
$source = '<a href="javascript:void(0)">Hello</a>';
$target = '<a href="javascript:<?php $this->config->context = \'JS\'; ?>void(0)<?php $this->config->context = \'HTML\'; ?>">Hello</a>';
$this->assertEquals($target, $this->_parse($source, true, false));
// Inline script in event handler
$source = '<div class="foo" onClick="bar.barr()">Hello</div>';
$target = '<div class="foo" onClick="<?php $this->config->context = \'JS\'; ?>bar.barr()<?php $this->config->context = \'HTML\'; ?>">Hello</div>';
$this->assertEquals($target, $this->_parse($source, true, false));
// <style> tag
$source = '<style> body { font-size: 16px; } </style>';
$target = '<style<?php $this->config->context = \'CSS\'; ?>> body { font-size: 16px; } <?php $this->config->context = \'HTML\'; ?></style>';
$this->assertEquals($target, $this->_parse($source, true, false));
// Inline style
$source = '<div style="background-color: #ffffff;" class="foobar"><span></span></div>';
$target = '<div style="<?php $this->config->context = \'CSS\'; ?>background-color: #ffffff;<?php $this->config->context = \'HTML\'; ?>" class="foobar"><span></span></div>';
$this->assertEquals($target, $this->_parse($source, true, false));
}
public function testEchoStatements()
{
// Basic usage of XE-style single braces
$source = '{$var}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(\$__Context->var ?? '') : htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(\$__Context->var ?? ''); ?>";
$this->assertEquals($target, $this->_parse($source));
// Single braces with space at beginning will not be parsed
@ -262,22 +290,22 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// Single braces with space at end are OK
$source = '{$var }';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(\$__Context->var ?? '') : htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(\$__Context->var ?? ''); ?>";
$this->assertEquals($target, $this->_parse($source));
// Correct handling of object property and array access
$source = '{Context::getRequestVars()->$foo[$bar]}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(Context::getRequestVars()->{\$__Context->foo}[\$__Context->bar]) : htmlspecialchars(Context::getRequestVars()->{\$__Context->foo}[\$__Context->bar], \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(Context::getRequestVars()->{\$__Context->foo}[\$__Context->bar], \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(Context::getRequestVars()->{\$__Context->foo}[\$__Context->bar]); ?>";
$this->assertEquals($target, $this->_parse($source));
// Basic usage of Blade-style double braces
$source = '{{ $var }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(\$__Context->var ?? '') : htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(\$__Context->var ?? ''); ?>";
$this->assertEquals($target, $this->_parse($source));
// Double braces without spaces are OK
$source = '{{$var}}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(\$__Context->var ?? '') : htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(\$__Context->var ?? '', \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(\$__Context->var ?? ''); ?>";
$this->assertEquals($target, $this->_parse($source));
// Literal double braces
@ -297,7 +325,7 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// Multiline echo statement
$source = '{{ $foo ?' . "\n" . ' date($foo) :' . "\n" . ' toBool($bar) }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(\$__Context->foo ? date(\$__Context->foo) : toBool(\$__Context->bar)) : htmlspecialchars(\$__Context->foo ?\n date(\$__Context->foo) :\n toBool(\$__Context->bar), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(\$__Context->foo ?\n date(\$__Context->foo) :\n toBool(\$__Context->bar), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(\$__Context->foo ? date(\$__Context->foo) : toBool(\$__Context->bar)); ?>";
$this->assertEquals($target, $this->_parse($source));
}
@ -339,11 +367,11 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
$this->assertEquals($target, $this->_parse($source));
$source = '{{ $lang->cmd_hello_world }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(\$__Context->lang->cmd_hello_world) : (\$__Context->lang->cmd_hello_world); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? (\$__Context->lang->cmd_hello_world) : \$this->_v2_escape(\$__Context->lang->cmd_hello_world); ?>";
$this->assertEquals($target, $this->_parse($source));
$source = '{{ $user_lang->user_lang_1234567890 }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(\$__Context->user_lang->user_lang_1234567890 ?? '') : (\$__Context->user_lang->user_lang_1234567890 ?? ''); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? (\$__Context->user_lang->user_lang_1234567890 ?? '') : \$this->_v2_escape(\$__Context->user_lang->user_lang_1234567890 ?? ''); ?>";
$this->assertEquals($target, $this->_parse($source));
// Escape
@ -366,11 +394,6 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
$target = "<?php echo escape_js(\$__Context->foo ?? ''); ?>";
$this->assertEquals($target, $this->_parse($source));
// Context-aware escape
$source = '<script type="text/javascript"> foobar(); </script>';
$target = '<script type="text/javascript"<?php $this->config->context = "JS"; ?>> foobar(); <?php $this->config->context = "HTML"; ?></script>';
$this->assertEquals($target, $this->_parse($source));
// JSON using context-aware escape
$source = '{{ $foo|json }}';
$target = implode('', [
@ -382,12 +405,12 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// strip_tags
$source = '{{ $foo|strip }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(strip_tags(\$__Context->foo ?? '')) : htmlspecialchars(strip_tags(\$__Context->foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(strip_tags(\$__Context->foo ?? ''), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(strip_tags(\$__Context->foo ?? '')); ?>";
$this->assertEquals($target, $this->_parse($source));
// strip_tags (alternate name)
$source = '{{ $foo|upper|strip_tags }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(strip_tags(strtoupper(\$__Context->foo ?? ''))) : htmlspecialchars(strip_tags(strtoupper(\$__Context->foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(strip_tags(strtoupper(\$__Context->foo ?? '')), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(strip_tags(strtoupper(\$__Context->foo ?? ''))); ?>";
$this->assertEquals($target, $this->_parse($source));
// Trim
@ -397,12 +420,12 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// URL encode
$source = '{{ $foo|trim|urlencode }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(rawurlencode(trim(\$__Context->foo ?? ''))) : htmlspecialchars(rawurlencode(trim(\$__Context->foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(rawurlencode(trim(\$__Context->foo ?? '')), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(rawurlencode(trim(\$__Context->foo ?? ''))); ?>";
$this->assertEquals($target, $this->_parse($source));
// Lowercase
$source = '{{ $foo|trim|lower }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(strtolower(trim(\$__Context->foo ?? ''))) : htmlspecialchars(strtolower(trim(\$__Context->foo ?? '')), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(strtolower(trim(\$__Context->foo ?? '')), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(strtolower(trim(\$__Context->foo ?? ''))); ?>";
$this->assertEquals($target, $this->_parse($source));
// Uppercase
@ -422,37 +445,37 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// Array join (default joiner is comma)
$source = '{{ $foo|join }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(implode(', ', \$__Context->foo ?? '')) : htmlspecialchars(implode(', ', \$__Context->foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(implode(', ', \$__Context->foo ?? ''), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(implode(', ', \$__Context->foo ?? '')); ?>";
$this->assertEquals($target, $this->_parse($source));
// Array join (custom joiner)
$source = '{{ $foo|join:"!@!" }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(implode(\"!@!\", \$__Context->foo ?? '')) : htmlspecialchars(implode(\"!@!\", \$__Context->foo ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(implode(\"!@!\", \$__Context->foo ?? ''), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(implode(\"!@!\", \$__Context->foo ?? '')); ?>";
$this->assertEquals($target, $this->_parse($source));
// Date conversion (default format)
$source = '{{ $item->regdate | date }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'Y-m-d H:i:s')) : htmlspecialchars(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'Y-m-d H:i:s'), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'Y-m-d H:i:s'), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'Y-m-d H:i:s')); ?>";
$this->assertEquals($target, $this->_parse($source));
// Date conversion (custom format)
$source = "{{ \$item->regdate | date:'n/j H:i' }}";
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'n/j H:i')) : htmlspecialchars(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'n/j H:i'), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'n/j H:i'), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), 'n/j H:i')); ?>";
$this->assertEquals($target, $this->_parse($source));
// Date conversion (custom format in variable)
$source = "{{ \$item->regdate | date:\$format }}";
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), \$__Context->format)) : htmlspecialchars(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), \$__Context->format), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), \$__Context->format), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(getDisplayDateTime(ztime(\$__Context->item->regdate ?? ''), \$__Context->format)); ?>";
$this->assertEquals($target, $this->_parse($source));
// Number format
$source = '{{ $num | format }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(number_format(\$__Context->num ?? '')) : htmlspecialchars(number_format(\$__Context->num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(number_format(\$__Context->num ?? ''), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(number_format(\$__Context->num ?? '')); ?>";
$this->assertEquals($target, $this->_parse($source));
// Number format (alternate name)
$source = '{{ $num | number_format }}';
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(number_format(\$__Context->num ?? '')) : htmlspecialchars(number_format(\$__Context->num ?? ''), \ENT_QUOTES, 'UTF-8', false); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(number_format(\$__Context->num ?? ''), \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(number_format(\$__Context->num ?? '')); ?>";
$this->assertEquals($target, $this->_parse($source));
// Number format (custom format)
@ -573,7 +596,7 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// Script tag with external path
$source = '<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.0/js/bootstrap.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>';
$target = '<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.0/js/bootstrap.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"><?php $this->config->context = "HTML"; ?></script>';
$target = '<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.0/js/bootstrap.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>';
$this->assertEquals($target, $this->_parse($source));
// Absolute URL
@ -797,7 +820,7 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
]);
$target = implode("\n", [
"<?php if (\$this->_v2_errorExists('email', 'login')): ?>",
"<?php echo \$this->config->context === 'JS' ? escape_js(\$__Context->message ?? '') : htmlspecialchars(\$__Context->message ?? '', \ENT_QUOTES, 'UTF-8', false); ?>",
"<?php echo \$this->config->context === 'HTML' ? htmlspecialchars(\$__Context->message ?? '', \ENT_QUOTES, 'UTF-8', false) : \$this->_v2_escape(\$__Context->message ?? ''); ?>",
'<?php endif; ?>',
]);
$this->assertEquals($target, $this->_parse($source));
@ -858,10 +881,10 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
'@endmobile',
]);
$target = implode("\n", [
"<?php if (!\\Context::get('m')): ?>",
'<?php if (!$this->_v2_isMobile()): ?>',
'<p>4K or GTFO!</p>',
'<?php endif; ?>',
"<?php if (\\Context::get('m')): ?>",
'<?php if ($this->_v2_isMobile()): ?>',
'<p>USB C is the way to go~</p>',
'<?php endif; ?>',
]);
@ -976,17 +999,17 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// Lang code with variable as name
$source = '@lang($var->name)';
$target = '<?php echo $this->config->context === \'JS\' ? escape_js($this->_v2_lang($__Context->var->name)) : $this->_v2_lang($__Context->var->name); ?>';
$target = '<?php echo $this->config->context === \'HTML\' ? $this->_v2_lang($__Context->var->name) : $this->_v2_escape($this->_v2_lang($__Context->var->name)); ?>';
$this->assertEquals($target, $this->_parse($source));
// Lang code with literal name and variable
$source = "@lang('board.cmd_list_items', \$var)";
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(\$this->_v2_lang('board.cmd_list_items', \$__Context->var)) : \$this->_v2_lang('board.cmd_list_items', \$__Context->var); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? \$this->_v2_lang('board.cmd_list_items', \$__Context->var) : \$this->_v2_escape(\$this->_v2_lang('board.cmd_list_items', \$__Context->var)); ?>";
$this->assertEquals($target, $this->_parse($source));
// Lang code with class alias
$source = "@use('Rhymix\Framework\Lang', 'Lang')\n" . '<p>@lang(Lang::getLang())</p>';
$target = "\n" . '<p><?php echo $this->config->context === \'JS\' ? escape_js($this->_v2_lang(Rhymix\Framework\Lang::getLang())) : $this->_v2_lang(Rhymix\Framework\Lang::getLang()); ?></p>';
$target = "\n" . '<p><?php echo $this->config->context === \'HTML\' ? $this->_v2_lang(Rhymix\Framework\Lang::getLang()) : $this->_v2_escape($this->_v2_lang(Rhymix\Framework\Lang::getLang())); ?></p>';
$this->assertEquals($target, $this->_parse($source));
// Dump one variable
@ -1001,12 +1024,17 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
// URL
$source = "@url(['mid' => 'foo', 'act' => 'dispBoardWrite'])";
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(getNotEncodedUrl(['mid' => 'foo', 'act' => 'dispBoardWrite'])) : getUrl(['mid' => 'foo', 'act' => 'dispBoardWrite']); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? getUrl(['mid' => 'foo', 'act' => 'dispBoardWrite']) : \$this->_v2_escape(getNotEncodedUrl(['mid' => 'foo', 'act' => 'dispBoardWrite'])); ?>";
$this->assertEquals($target, $this->_parse($source));
// URL old-style with variables
$source = "@url('', 'mid', \$mid, 'act', \$act])";
$target = "<?php echo \$this->config->context === 'JS' ? escape_js(getNotEncodedUrl('', 'mid', \$__Context->mid, 'act', \$__Context->act])) : getUrl('', 'mid', \$__Context->mid, 'act', \$__Context->act]); ?>";
$target = "<?php echo \$this->config->context === 'HTML' ? getUrl('', 'mid', \$__Context->mid, 'act', \$__Context->act]) : \$this->_v2_escape(getNotEncodedUrl('', 'mid', \$__Context->mid, 'act', \$__Context->act])); ?>";
$this->assertEquals($target, $this->_parse($source));
// Widget
$source = "@widget('login_info', ['skin' => 'default'])";
$target = "<?php echo \WidgetController::getInstance()->execute('login_info', ['skin' => 'default']); ?>";
$this->assertEquals($target, $this->_parse($source));
}
@ -1197,6 +1225,24 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
$this->assertStringContainsString('/tests/_data/template/css/style.scss', array_first($list)['file']);
}
public function testCompileContextualEscape()
{
// Contextual escape
$tmpl = new \Rhymix\Framework\Template('./tests/_data/template', 'v2contextual.html');
$tmpl->disableCache();
$tmpl->setVars([
'var' => 'Hello <"world"> (\'string\') variable.jpg'
]);
$executed_output = $tmpl->compile();
//Rhymix\Framework\Storage::write(\RX_BASEDIR . 'tests/_data/template/v2contextual.executed.html', $executed_output);
$expected = file_get_contents(\RX_BASEDIR . 'tests/_data/template/v2contextual.executed.html');
$this->assertEquals(
$this->_normalizeWhitespace($expected),
$this->_normalizeWhitespace($executed_output)
);
}
public function testCompileLang()
{
// Lang
@ -1295,9 +1341,10 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
*
* @param string $source
* @param bool $force_v2 Disable version detection
* @param bool $remove_context_switches Remove context switches that make code difficult to read
* @return string
*/
protected function _parse(string $source, bool $force_v2 = true): string
protected function _parse(string $source, bool $force_v2 = true, bool $remove_context_switches = true): string
{
$tmpl = new \Rhymix\Framework\Template('./tests/_data/template', 'empty.html');
if ($force_v2)
@ -1309,6 +1356,13 @@ class TemplateParserV2Test extends \Codeception\Test\Unit
{
$result = substr($result, strlen($this->prefix));
}
// Remove context switches.
if ($remove_context_switches)
{
$result = preg_replace('#<\?php \$this->config->context = \'[A-Z]+\'; \?>#', '', $result);
}
return $result;
}

View file

@ -50,8 +50,10 @@ class FunctionsTest extends \Codeception\Test\Unit
$this->assertEquals('$user_lang-&gt;userLang1234567890', escape('$user_lang->userLang1234567890', true, false));
$this->assertEquals('$user_lang->userLang1234567890', escape('$user_lang->userLang1234567890', true, true));
$this->assertEquals('expressionalertXSS', escape_css('expression:alert("XSS")'));
$this->assertEquals('expressionalert(XSS)', escape_css('expression:alert("XSS")'));
$this->assertEquals('#123456', escape_css('#123456'));
$this->assertEquals('16px/160% Segoe UI, sans-serif font-style', escape_css('16px/160% Segoe UI, sans-serif; font-style'));
$this->assertEquals('box-shadow(0 1px 2px rgba(0, 0, 0, 0.15)', escape_css('box-shadow(0 1px 2px rgba(0, 0, 0, "0.15")'));
$this->assertEquals('hello\\\\world', escape_js('hello\\world'));
$this->assertEquals('\u003Cbr \/\u003E', escape_js('<br />'));
@ -226,6 +228,8 @@ class FunctionsTest extends \Codeception\Test\Unit
$this->assertEquals("Weird spaces are in this string", utf8_normalize_spaces("Weird\x20spaces\xe2\x80\x80are\xe2\x80\x84in\xe2\x80\x86\xe2\x80\x8bthis\x0astring"));
$this->assertEquals("Weird spaces are in this\nstring", utf8_normalize_spaces("Weird\x20spaces\xe2\x80\x80are\xe2\x80\x84in\xe2\x80\x86\xe2\x80\x8bthis\x0astring", true));
$this->assertEquals("Stupid Windows\nLine Breaks", utf8_normalize_spaces("Stupid Windows \r\n Line Breaks", true));
$this->assertEquals("Multiple\nCRLF\n\nsequences", utf8_normalize_spaces("Multiple \r\nCRLF\r\n\t\r\n sequences", true));
$this->assertEquals("Trimmed", utf8_trim("\x20\xe2\x80\x80Trimmed\xe2\x80\x84\xe2\x80\x86\xe2\x80\x8b"));
$this->assertEquals("Trimmed", utf8_trim("\x20\xe2\x80\x80Trimmed\x0a\x0c\x07\x09"));
}