Merge branch 'rhymix:master' into master

This commit is contained in:
Lastorder 2025-10-13 20:09:50 +09:00 committed by GitHub
commit a5c3dc8ae5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 219 additions and 95 deletions

View file

@ -232,6 +232,7 @@ var initPhotoSwipeFromDOM = function(gallerySelector) {
var regx_skip = /(?:(modules|addons|classes|common|layouts|libs|widgets|widgetstyles)\/)/i;
var regx_allow_i6pngfix = /(?:common\/tpl\/images\/blank\.gif$)/i;
var isMobile = String(navigator.userAgent).match(/mobile/i);
var galleryImgEls = $(galleryElements[i]).find(ps_find_selector);
for(var j = 0, jl = galleryImgEls.length; j < jl; j++) {
// skip components
@ -240,6 +241,11 @@ var initPhotoSwipeFromDOM = function(gallerySelector) {
//$(galleryImgEls[j]).attr('data-pswp-uid', i+1);
$(galleryImgEls[j]).attr('data-pswp-pid', j+1);
// Fix stretching of image on mobile
if (isMobile) {
galleryImgEls[j].style.height = 'auto';
galleryImgEls[j].height = null;
}
}
}

View file

@ -338,7 +338,11 @@ class Context
}
// start session
if (\PHP_SAPI !== 'cli')
if (\PHP_SAPI === 'cli')
{
$_SESSION = [];
}
else
{
if (self::$_current_request->getRouteOption('enable_session'))
{
@ -1214,7 +1218,7 @@ class Context
else
{
// Set HTTP_RAW_POST_DATA for third-party apps that look for it.
if (!$_POST && !isset($GLOBALS['HTTP_RAW_POST_DATA']))
if (empty($_POST) && empty($_FILES) && !isset($GLOBALS['HTTP_RAW_POST_DATA']))
{
$GLOBALS['HTTP_RAW_POST_DATA'] = file_get_contents('php://input');
}
@ -1230,7 +1234,7 @@ class Context
}
// Decide whether it's JSON or XMLRPC by looking at the first character of the POST data.
if (!$_POST && !empty($GLOBALS['HTTP_RAW_POST_DATA']))
if (empty($_POST) && !empty($GLOBALS['HTTP_RAW_POST_DATA']))
{
self::$_instance->request_method = substr($GLOBALS['HTTP_RAW_POST_DATA'], 0, 1) === '<' ? 'XMLRPC' : 'JSON';
return;
@ -1258,7 +1262,7 @@ class Context
}
// Set JSON and XMLRPC arguments.
if(isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST' && !$_POST && !empty($GLOBALS['HTTP_RAW_POST_DATA']))
if(isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST' && empty($_POST) && !empty($GLOBALS['HTTP_RAW_POST_DATA']))
{
$params = array();
$request_method = self::getRequestMethod();

View file

@ -151,14 +151,21 @@ class HTMLDisplayHandler
Context::loadFile(array($edited_layout_css, 'all', '', 100));
}
}
if(!$layout_path)
if (!$layout_path)
{
$layout_path = './common/tpl';
}
if(!$layout_file)
if (!$layout_file)
{
if ($layout_path === './common/tpl')
{
$layout_file = 'default_layout';
}
else
{
$layout_file = 'layout';
}
}
$oTemplate = new Rhymix\Framework\Template;
$output = $oTemplate->compile($layout_path, $layout_file, $edited_layout_file);

View file

@ -151,7 +151,16 @@ class ModuleHandler extends Handler
$site_module_info->domain = Rhymix\Framework\URL::getCurrentDomain();
$site_module_info->is_default_domain = 'N';
$site_module_info->is_default_replaced = true;
// Reset context variables if the domain was replaced.
Context::set('site_module_info', $site_module_info);
Context::set('_default_url', null);
Context::set('request_uri', $current_url = Context::getRequestUri());
if ($query_string = http_build_query(Context::getCurrentRequest()->args))
{
$current_url .= '?' . $query_string;
}
Context::set('current_url', $current_url);
}
}
}

View file

@ -44,24 +44,23 @@ class PageHandler extends Handler implements Iterator
$this->page_count = $page_count;
$this->point = 0;
$first_page = $cur_page - (int) ($page_count / 2);
if($first_page < 1)
if ($this->cur_page > $total_page)
{
$first_page = 1;
$this->cur_page = $total_page;
}
if ($this->page_count > $total_page)
{
$this->page_count = $total_page;
}
if($total_page > $page_count && $first_page + $page_count - 1 > $total_page)
$first_page = max(1, $this->cur_page - floor($this->page_count / 2));
if (($first_page + $this->page_count - 1) > $total_page)
{
$first_page -= $first_page + $page_count - 1 - $total_page;
$first_page = max(1, $total_page - $this->page_count + 1);
}
$this->first_page = $first_page;
$this->last_page = $total_page;
if($total_page < $this->page_count)
{
$this->page_count = $total_page;
}
}
/**

View file

@ -1126,17 +1126,17 @@ class DB
}
else
{
$exceptions = [];
$exceptions = ['TABLE'];
}
// Add prefixes to all other table names in the query string.
return preg_replace_callback('/\b((?:DELETE\s+)?FROM|JOIN|INTO|(?<!KEY\s)UPDATE)(?i)\s+((?:`?\w+`?)(?:\s+AS\s+`?\w+`?)?(?:\s*,\s*(?:`?\w+\`?)(?:\s+AS\s+`?\w+`?)?)*)/', function($m) use($exceptions) {
return preg_replace_callback('/\b((?:DELETE\s+)?FROM|JOIN|INTO(?: TABLE)?|TABLE|(?<!KEY\s)UPDATE)(?i)\s+((?:`?\w+`?)(?:\s+AS\s+`?\w+`?)?(?:\s*,\s*(?:`?\w+\`?)(?:\s+AS\s+`?\w+`?)?)*)/', function($m) use($exceptions) {
$type = strtoupper($m[1]);
$tables = array_map(function($str) use($type, $exceptions) {
return preg_replace_callback('/`?(\w+)`?(?:\s+AS\s+`?(\w+)`?)?/i', function($m) use($type, $exceptions) {
if (count($exceptions) && in_array($m[1], $exceptions))
{
return isset($m[2]) ? sprintf('`%s` AS `%s`', $m[1], $m[2]) : sprintf('`%s`', $m[1]);
return isset($m[2]) ? sprintf('`%s` AS `%s`', $m[1], $m[2]) : (ctype_upper($m[1]) ? $m[1] : sprintf('`%s`', $m[1]));
}
elseif ($type === 'FROM' || $type === 'JOIN')
{

View file

@ -70,8 +70,21 @@ class DBTableParser extends BaseParser
// Load columns.
foreach ($xml->column as $column_info)
{
// Get the column name and type.
// Is this column generated?
$is_generated = strval($column_info['generated'] ?? '') !== '';
if ($is_generated)
{
$column = new DBTable\GeneratedColumn;
$column->generated = strtolower($column_info['generated']);
$column->is_stored = strtolower($column_info['stored'] ?? '');
$column->is_stored = $column->is_stored !== 'virtual' && toBool($column->is_stored);
}
else
{
$column = new DBTable\Column;
}
// Get the column name and type.
$column->name = strval($column_info['name']);
list($column->type, $column->xetype, $column->size) = self::getTypeAndSize(strval($column_info['type']), strval($column_info['size']));

View file

@ -398,7 +398,11 @@ class VariableBase
// Don't apply a filter if there is no variable.
$column = $this instanceof ColumnWrite ? $this->name : $this->column;
$filter = isset($this->filter) ? $this->filter : '';
if (!is_array($value) && strval($value) === '')
if (is_object($value) && !method_exists($value, '__toString'))
{
throw new \Rhymix\Framework\Exceptions\QueryError('Variable ' . $this->var . ' for column ' . $column . ' is not stringable');
}
if (is_scalar($value) && strval($value) === '')
{
$filter = '';
}

View file

@ -0,0 +1,12 @@
<?php
namespace Rhymix\Framework\Parsers\DBTable;
/**
* Generated column class.
*/
class GeneratedColumn extends Column
{
public $generated = 'always';
public $is_stored = false;
}

View file

@ -53,10 +53,17 @@ class Table
$columndef .= ' CHARACTER SET ' . $column->charset . ' COLLATE ' . $column->charset . '_general_ci';
}
}
if ($column->not_null)
if ($column instanceof GeneratedColumn)
{
$columndef .= ' NOT NULL';
$columndef .= ' GENERATED ' . strtoupper($column->generated ?: 'always');
$columndef .= ' AS (' . $column->default_value . ')';
if ($column->is_stored)
{
$columndef .= ' STORED';
}
}
else
{
if ($column->default_value !== null)
{
if (preg_match('/(?:int|float|double|decimal|number)/i', $column->type) && is_numeric($column->default_value))
@ -72,10 +79,16 @@ class Table
$columndef .= ' DEFAULT \'' . $column->default_value . '\'';
}
}
}
if ($column->not_null)
{
$columndef .= ' NOT NULL';
}
if ($column->auto_increment)
{
$columndef .= ' AUTO_INCREMENT';
}
$columns[] = $columndef;
}

View file

@ -671,7 +671,7 @@ function utf8_mbencode($str): string
$bytes = array(ord($m[0][0]), ord($m[0][1]), ord($m[0][2]), ord($m[0][3]));
$codepoint = ((0x07 & $bytes[0]) << 18) + ((0x3F & $bytes[1]) << 12) + ((0x3F & $bytes[2]) << 6) + (0x3F & $bytes[3]);
return '&#x' . dechex($codepoint) . ';';
}, (string)$str);
}, (string)$str) ?? '';
}
/**
@ -686,11 +686,11 @@ function utf8_normalize_spaces($str, bool $multiline = false): string
{
if ($multiline)
{
return preg_replace(['/((?!\x0A)[\pZ\pC])+/u', '/\x20*\x0A\x20*/'], [' ', "\n"], (string)$str);
return preg_replace(['/((?!\x0A)[\pZ\pC])+/u', '/\x20*\x0A\x20*/'], [' ', "\n"], (string)$str) ?? '';
}
else
{
return preg_replace('/[\pZ\pC]+/u', ' ', (string)$str);
return preg_replace('/[\pZ\pC]+/u', ' ', (string)$str) ?? '';
}
}

View file

@ -145,7 +145,7 @@
chunkfail: function(e, res) {
lastUploadTime = Date.now();
if (chunkStatus) {
alert(window.xe.lang.msg_file_upload_error + " (Type 3)" + "<br>\n" + res.errorThrown + "<br>\n" + res.textStatus);
alert(window.xe.lang.msg_file_upload_error + " (Type 3)" + "\n" + res.errorThrown + "\n" + res.textStatus);
return chunkStatus = false;
}
},
@ -169,7 +169,7 @@
result = jQuery.parseJSON(result);
}
if (!result) {
alert(window.xe.lang.msg_file_upload_error + " (Type 5)" + "<br>\n" + res.response().result);
alert(window.xe.lang.msg_file_upload_error + " (Type 5)" + "\n" + res.response().result);
return false;
}
@ -215,7 +215,7 @@
return false;
} else {
$container.data('editorStatus', null);
alert(window.xe.lang.msg_file_upload_error + " (Type 6)" + "<br>\n" + res.response().result);
alert(window.xe.lang.msg_file_upload_error + " (Type 6)" + "\n" + res.response().result);
return false;
}
},
@ -229,7 +229,7 @@
}
}, 1000);
if (chunkStatus) {
alert(window.xe.lang.msg_file_upload_error + " (Type 7)" + "<br>\n" + res.errorThrown + "<br>\n" + res.textStatus);
alert(window.xe.lang.msg_file_upload_error + " (Type 7)" + "\n" + res.errorThrown + "\n" + res.textStatus);
return false;
}
},

View file

@ -4,14 +4,18 @@
* This script runs the task queue.
*
* Unlike other scripts provided with Rhymix, it can be called
* both on the command line and over the network.
* both on the CLI (through index.php) and over the network (directly).
*/
define('RXQUEUE_CRON', true);
// If called on the CLI, run additional checks.
if (PHP_SAPI === 'cli')
{
require_once __DIR__ . '/common.php';
if (!defined('RX_VERSION'))
{
echo "Error: This script must not be called directly.\n";
exit(1);
}
}
else
{

View file

@ -56,11 +56,7 @@ class AdminAdminModel extends Admin
*/
public function getSiteAllList()
{
if(Context::get('domain'))
{
$domain = Context::get('domain');
}
$siteList = $this->getAllSitesThatHaveModules($domain);
$siteList = $this->getAllSitesThatHaveModules(Context::get('domain'));
$this->add('site_list', $siteList);
}

View file

@ -466,10 +466,12 @@ class CommentItem extends BaseObject
$content = trim(utf8_normalize_spaces(html_entity_decode(strip_tags($content))));
if($strlen)
{
$content = cut_str($content, $strlen, '...');
$content = escape(cut_str($content, $strlen, '...'), false);
}
else
{
$content = escape($content);
}
if ($content === '')
{
@ -511,10 +513,13 @@ class CommentItem extends BaseObject
$content = trim(utf8_normalize_spaces(html_entity_decode(strip_tags($content))));
if($strlen)
{
$content = cut_str($content, $strlen, '...');
return escape(cut_str($content, $strlen, '...'), false);
}
else
{
return escape($content);
}
}
/**
* Return content after filter

View file

@ -634,11 +634,13 @@ class DocumentItem extends BaseObject
$content = trim(utf8_normalize_spaces(html_entity_decode(strip_tags($content))));
if($strlen)
{
$content = cut_str($content, $strlen, '...');
return escape(cut_str($content, $strlen, '...'), false);
}
else
{
return escape($content);
}
}
function getContentText($strlen = 0)
{
@ -653,17 +655,22 @@ class DocumentItem extends BaseObject
}
$content = preg_replace('!(</p>|</div>|<br)!i', ' $1', $this->get('content'));
$content = preg_replace_callback('/<(object|param|embed)[^>]*/is', array($this, '_checkAllowScriptAccess'), $content);
$content = preg_replace_callback('/<object[^>]*>/is', array($this, '_addAllowScriptAccess'), $content);
//$content = preg_replace_callback('/<(object|param|embed)[^>]*/is', array($this, '_checkAllowScriptAccess'), $content);
//$content = preg_replace_callback('/<object[^>]*>/is', array($this, '_addAllowScriptAccess'), $content);
if($strlen)
{
$content = trim(utf8_normalize_spaces(html_entity_decode(strip_tags($content))));
$content = cut_str($content, $strlen, '...');
return escape(cut_str($content, $strlen, '...'), false);
}
else
{
return escape($content);
}
}
/**
* @deprecated
*/
function _addAllowScriptAccess($m)
{
if($this->allowscriptaccessList[$this->allowscriptaccessKey] == 1)
@ -674,6 +681,9 @@ class DocumentItem extends BaseObject
return $m[0];
}
/**
* @deprecated
*/
function _checkAllowScriptAccess($m)
{
if($m[1] == 'object')
@ -806,8 +816,7 @@ class DocumentItem extends BaseObject
// Truncate string
$content = cut_str($content, $str_size, $tail);
return escape($content);
return escape($content, false);
}
function getRegdate($format = 'Y.m.d H:i:s', $conversion = true)

View file

@ -19,31 +19,32 @@ function memberSetEvent() {
// 실제 서버에 특정 필드의 value check를 요청하고 이상이 있으면 메세지를 뿌려주는 함수
function memberCheckValue(event) {
var field = event.target;
var _name = field.name;
var _value = field.value;
if(!_name || !_value) return;
var params = {name:_name, value:_value};
var response_tags = ['error','message'];
exec_xml('member','procMemberCheckValue', params, completeMemberCheckValue, response_tags, field);
}
// 서버에서 응답이 올 경우 이상이 있으면 메세지를 출력
function completeMemberCheckValue(ret_obj, response_tags, field) {
var _id = 'dummy_check'+field.name;
var dummy = jQuery('#'+_id);
if(ret_obj['message']=='success') {
dummy.html('').hide();
if(!field.name || !field.value) {
return;
}
exec_json('member.procMemberCheckValue', {
name: field.name,
value: field.value
}, function(data) {
completeMemberCheckValue(data, null, field);
});
}
// 서버에서 응답이 올 경우 이상이 있으면 메세지를 출력
function completeMemberCheckValue(data, unused, field) {
var _id = 'dummy_check'+field.name;
var dummy = jQuery('#'+_id);
if (!dummy.length) {
dummy = jQuery('<p class="checkValue help-inline" style="color:red" />').attr('id', _id).appendTo(field.parentNode);
}
dummy.html(ret_obj['message']).show();
if(data.message == 'success') {
dummy.html('').hide();
return;
} else {
dummy.html(data.message).show();
}
}
// 결과 메세지를 정리하는 함수

View file

@ -443,6 +443,10 @@ class ModuleController extends Module
{
$oMenuAdminController = getAdminController('menu');
$menuSrl = $oMenuAdminController->getUnlinkedMenu();
if ($menuSrl instanceof BaseObject && !$menuSrl->toBool())
{
return $menuSrl;
}
$menuArgs->menu_srl = $menuSrl;
$menuArgs->menu_item_srl = getNextSequence();

View file

@ -1,7 +1,11 @@
<query id="getDomainInfo" action="select">
<tables>
<table name="domains" />
<table name="modules" />
<table name="modules" type="left join">
<conditions>
<condition operation="equal" column="modules.module_srl" default="domains.index_module_srl" />
</conditions>
</table>
</tables>
<columns>
<column name="domains.*" />
@ -9,7 +13,6 @@
<column name="domains.domain_srl" alias="domain_srl" />
</columns>
<conditions>
<condition operation="equal" column="modules.module_srl" default="domains.index_module_srl" notnull="notnull" />
<condition operation="equal" column="domains.domain_srl" var="domain_srl" pipe="and" />
<condition operation="equal" column="domains.domain" var="domain" pipe="and" />
<condition operation="equal" column="domains.is_default_domain" var="is_default_domain" pipe="and" />

View file

@ -28,7 +28,7 @@
<item loop="$document_list=>$oDocument" rdf:about="{$oDocument->getPermanentUrl()}">
<title>{$oDocument->getTitleText()}</title>
<link>{$oDocument->getPermanentUrl()}</link>
<description>{$oDocument->getSummary(400)|escape}</description>
<description>{$oDocument->getSummary(400)}</description>
<dc:creator>{$oDocument->getNickName()}</dc:creator>
<dc:date>{date('c', ztime($oDocument->get('regdate')))}</dc:date>
</item>

View file

@ -22,7 +22,7 @@
<!--@if($target_modules[$oDocument->get('module_srl')] == 'Y')-->
<description>{\Rhymix\Framework\Filters\HTMLFilter::fixRelativeUrls(utf8_trim(utf8_normalize_spaces($oDocument->get('content'))))|escape}</description>
<!--@else-->
<description>{$oDocument->getSummary(400)|escape}</description>
<description>{$oDocument->getSummary(400)}</description>
<!--@end-->
<category cond="$oDocument->getModuleName()">{Context::replaceUserLang($oDocument->getModuleName())}</category>
<category cond="$oDocument->get('category_srl') && $category_name = $category_list[$oDocument->get('module_srl')][$oDocument->get('category_srl')]->title">{Context::replaceUserLang($category_name)}</category>

View file

@ -0,0 +1,9 @@
<table name="generated">
<column name="id" type="bignumber" size="11" notnull="notnull" auto_increment="auto_increment" primary_key="primary_key" />
<column name="module_srl" type="number" notnull="notnull" index="idx_module_srl" />
<column name="document_srl" type="number" notnull="notnull" index="idx_document_srl" />
<column name="member_srl" type="number" notnull="notnull" index="idx_member_srl" />
<column name="gentest1" type="number" generated="always" default="document_srl + member_srl" stored="true" notnull="notnull" />
<column name="gentest2" type="number" generated="always" default="MAX(module_srl, document_srl)" stored="false" />
<column name="gentest3" type="varchar" size="40" notnull="notnull" generated="always" default="CONCAT(module_srl, '_', document_srl)" />
</table>

View file

@ -175,6 +175,22 @@ class DBTest extends \Codeception\Test\Unit
$target = 'INSERT INTO `' . $prefix . 'documents` (a, b, c) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE b = ?, c = ?';
$this->assertEquals($target, $oDB->addPrefixes($source));
$source = "LOAD DATA LOCAL INFILE '/tmp/foo.csv' INTO TABLE foo_table FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\n' (a, b, c)";
$target = "LOAD DATA LOCAL INFILE '/tmp/foo.csv' INTO TABLE `" . $prefix . "foo_table` FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\n' (a, b, c)";
$this->assertEquals($target, $oDB->addPrefixes($source));
$source = 'ALTER TABLE documents ADD INDEX idx_foo (a, b)';
$target = 'ALTER TABLE `' . $prefix . 'documents` ADD INDEX idx_foo (a, b)';
$this->assertEquals($target, $oDB->addPrefixes($source));
$source = 'TRUNCATE TABLE documents';
$target = 'TRUNCATE TABLE `' . $prefix . 'documents`';
$this->assertEquals($target, $oDB->addPrefixes($source));
$source = 'DROP TABLE documents';
$target = 'DROP TABLE `' . $prefix . 'documents`';
$this->assertEquals($target, $oDB->addPrefixes($source));
$source = 'update documents set a = ?, b = ? where c = ?';
$this->assertEquals($source, $oDB->addPrefixes($source));

View file

@ -63,4 +63,14 @@ class DBTableParserTest extends \Codeception\Test\Unit
$this->assertStringContainsString('CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci', $sql);
$this->assertStringContainsString('ENGINE = InnoDB', $sql);
}
public function testGeneratedColumn()
{
$table = Rhymix\Framework\Parsers\DBTableParser::loadXML(\RX_BASEDIR . 'tests/_data/dbtable/generated.xml');
$sql = $table->getCreateQuery('rx_');
$this->assertStringContainsString('CREATE TABLE `rx_generated`', $sql);
$this->assertStringContainsString('`gentest1` BIGINT GENERATED ALWAYS AS (document_srl + member_srl) STORED NOT NULL,', $sql);
$this->assertStringContainsString('`gentest2` BIGINT GENERATED ALWAYS AS (MAX(module_srl, document_srl)),', $sql);
$this->assertStringContainsString('`gentest3` VARCHAR(40) GENERATED ALWAYS AS (CONCAT(module_srl, \'_\', document_srl)) NOT NULL,', $sql);
}
}