Compare commits

..

17 commits

Author SHA1 Message Date
Lastorder-DC
1c8ae0d766 Merge remote-tracking branch 'upstream/master' into master 2025-10-27 23:09:27 +09:00
Kijin Sung
2a9b336988 Add option to always reencode images #2601 2025-10-25 12:36:18 +09:00
Kijin Sung
e676949669 Allow users to edit existing documents, even if they belong in categories they don't have permission to write in anymore 2025-10-24 17:51:33 +09:00
Kijin Sung
b119578209 Add prefix index on value column of document_extra_vars table 2025-10-24 09:04:18 +09:00
Kijin Sung
f7301b0a22 Prevent warning when NOT sorting by extra var as number 2025-10-23 23:27:18 +09:00
Kijin Sung
77379fe230 Add PHP 8.5 to test matrix 2025-10-23 16:41:50 +09:00
Kijin Sung
bc6eaae544 Remove old Flash-based uploader plugin 2025-10-23 13:42:21 +09:00
Kijin Sung
bef2871ce7 Cleanup related to document_extra_vars 2025-10-23 13:10:21 +09:00
Kijin Sung
2a7eeebcbd Add numeric value column to extra vars, and add option to mark extra var as sortable #2604 #2559 2025-10-23 11:17:24 +09:00
Kijin Sung
978aecd7a8 Support SQL expressions in the "default" attribute of XML query 2025-10-23 10:53:12 +09:00
Kijin Sung
ad293fa4d0 Add empty paragraph after <video> tag in CKEditor 2025-10-22 23:02:15 +09:00
Kijin Sung
e2124ed1c0 Refactor checkCSRF() to use Sec-Fetch-Site and Origin headers
- 최근 브라우저에서 지원하는 헤더를 사용하여 더 정확하게 체크
- Sec-Fetch-Site, Origin 헤더를 지원하지 않는 경우 기존처럼 Referer 체크
- CSRF 토큰은 더이상 기본 방법보다 보안이 더 뛰어나다고 말하기 힘듬
2025-10-22 22:53:45 +09:00
Kijin Sung
f536f0f382 Delete message about point reversion, because it is now customizable per module 2025-10-22 17:56:47 +09:00
Kijin Sung
ee48d4efea Fix inconsistent length of ipaddress column #2605 2025-10-22 17:52:30 +09:00
Kijin Sung
82de42dfc8 Fix invalid widget info shown in server environment page 2025-10-22 15:30:14 +09:00
Kijin Sung
f3494e8a11 Fix failure to convert palette-based PNG to WebP #2608 2025-10-22 15:21:36 +09:00
Kijin Sung
fd0e2a1cc3 Fix missing temp directory when admin resizes image using magick 2025-10-22 15:18:15 +09:00
52 changed files with 447 additions and 1007 deletions

View file

@ -6,7 +6,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ]
php: [ '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ]
name: PHP ${{ matrix.php }}
steps:

View file

@ -4,6 +4,21 @@
sudo add-apt-repository -y ppa:ondrej/php
# Install all required packages
if [[ "$1" == "8.5" ]]; then
sudo apt -y install \
php$1-bcmath \
php$1-cli \
php$1-common \
php$1-curl \
php$1-gd \
php$1-intl \
php$1-mbstring \
php$1-mysql \
php$1-readline \
php$1-sqlite3 \
php$1-xml \
php$1-zip
else
sudo apt -y install \
php$1-apcu \
php$1-bcmath \
@ -19,15 +34,22 @@ sudo apt -y install \
php$1-sqlite3 \
php$1-xml \
php$1-zip
fi
# Enable and tune opcache
sudo bash -c 'echo "opcache.enable = 1" >> /etc/php/$1/cli/conf.d/10-opcache.ini'
sudo bash -c 'echo "opcache.enable_cli = 1" >> /etc/php/$1/cli/conf.d/10-opcache.ini'
sudo bash -c 'echo "opcache.jit = tracing" >> /etc/php/$1/cli/conf.d/10-opcache.ini'
sudo bash -c 'echo "opcache.jit_buffer_size = 128M" >> /etc/php/$1/cli/conf.d/10-opcache.ini'
# Adjust php.ini settings
if [[ "$1" == "8.5" ]]; then
sudo bash -c "echo 'register_argc_argv = On' >> /etc/php/$1/cli/php.ini"
else
sudo bash -c "echo 'opcache.enable = 1' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
sudo bash -c "echo 'opcache.enable_cli = 1' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
sudo bash -c "echo 'opcache.jit = tracing' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
sudo bash -c "echo 'opcache.jit_buffer_size = 128M' >> /etc/php/$1/cli/conf.d/10-opcache.ini"
fi
# Enable APCu
sudo bash -c 'echo "apc.enable_cli = 1" >> /etc/php/$1/cli/conf.d/20-apcu.ini'
if [ -f "/etc/php/$1/cli/conf.d/20-apcu.ini" ]; then
sudo bash -c "echo 'apc.enable_cli = 1' >> /etc/php/$1/cli/conf.d/20-apcu.ini"
fi
# Disable xdebug
sudo phpdismod -v ALL -s ALL xdebug

View file

@ -728,6 +728,10 @@ class FileHandler
}
elseif ($target_type === 'webp' && function_exists('imagewebp'))
{
if (!imageistruecolor($thumb))
{
imagepalettetotruecolor($thumb);
}
$output = imagewebp($thumb, $target_file);
}
else

View file

@ -314,36 +314,55 @@ class Security
*/
public static function checkCSRF(?string $referer = null): bool
{
// If CSRF token checking is enabled, check the token header or parameter first.
// Tokens are allowed to be missing for unauthenticated users.
$check_csrf_token = config('security.check_csrf_token') ? true : false;
if ($token = isset($_SERVER['HTTP_X_CSRF_TOKEN']) ? $_SERVER['HTTP_X_CSRF_TOKEN'] : null)
if ($check_csrf_token)
{
$token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ($_REQUEST['_rx_csrf_token'] ?? null);
if ($token)
{
return Session::verifyToken((string)$token, '', $check_csrf_token);
}
elseif ($token = isset($_REQUEST['_rx_csrf_token']) ? $_REQUEST['_rx_csrf_token'] : null)
elseif (Session::getMemberSrl())
{
return Session::verifyToken((string)$token, '', $check_csrf_token);
trigger_error('CSRF token missing in authenticated POST request: ' . (\Context::get('act') ?: '(no act)'), \E_USER_WARNING);
return false;
}
}
elseif ($token = isset($_REQUEST['_fb_adsense_token']) ? $_REQUEST['_fb_adsense_token'] : null)
{
return Password::checkPassword($token, 'bb15471de21f33c373abbea6438730ace9bbbacf5f4f9a0cbebdfff7e99c50fe631a78efe3e39736836b5b2082a0c3939e4c4e0f0f2e0028042411c4a8797b73');
}
elseif ($_SERVER['REQUEST_METHOD'] === 'GET')
// Return early for GET and other "safe" requests.
if (in_array($_SERVER['REQUEST_METHOD'], ['GET', 'HEAD', 'OPTIONS', 'TRACE']))
{
return false;
}
else
// The Sec-Fetch-Site header is the standard way to check whether the request is same-origin.
$sec_fetch_site = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? '';
if ($sec_fetch_site === 'same-origin' || $sec_fetch_site === 'none')
{
$is_logged = Session::getMemberSrl();
if ($is_logged)
return true;
}
if ($sec_fetch_site === 'cross-site')
{
trigger_error('CSRF token missing in POST request: ' . (\Context::get('act') ?: '(no act)'), \E_USER_WARNING);
return false;
}
if (!$referer)
// If the Sec-Fetch-Site header is not available, check the Origin header.
$origin = strval($_SERVER['HTTP_ORIGIN'] ?? '');
if ($origin !== '' && $origin !== 'null')
{
$referer = strval(($_SERVER['HTTP_ORIGIN'] ?? '') ?: ($_SERVER['HTTP_REFERER'] ?? ''));
return URL::isInternalURL($origin);
}
if ($referer !== '' && $referer !== 'null' && (!$check_csrf_token || !$is_logged))
// If the Origin header is not available, fall back to the Referer header.
// The Referer header is NOT allowed to be empty in this case.
$referer = $referer ?? strval($_SERVER['HTTP_REFERER'] ?? '');
if ($referer !== '' && $referer !== 'null')
{
return URL::isInternalURL($referer);
}
@ -352,7 +371,6 @@ class Security
return false;
}
}
}
/**
* Check if the current request seems to be an XXE (XML external entity) attack.

View file

@ -383,6 +383,12 @@ class VariableBase
}
}
// If the default value is any other kind of SQL expression, return it as is.
if (isset($column) && preg_match('/^[A-Z_]+\([^)]+\)/', $this->default))
{
return [true, $this->default];
}
// Otherwise, just return the literal value.
return [false, $this->default];
}

View file

@ -385,6 +385,9 @@
if (html !== '' && data.settings.autoinsertPosition === 'paragraph') {
html = '<p>' + html + '</p>\n';
if (html.match(/<video /)) {
html += '<p>&nbsp;</p>\n';
}
}
if (html === '') {

View file

@ -1 +0,0 @@
uploader.js

View file

@ -1,792 +0,0 @@
/**
* Uploader
*/
(function($){
var runtime;
var Uploader = xe.createApp('Uploader', {
settings : {},
files : [],
init : function(browseButton, opts) {
if (!runtime) {
$.each(runtimes, function(key,obj) {
if (obj.check()) { runtime = key; return false; }
});
}
this.queue = [];
this.settings = $.extend({
filters : {},
params : {}
}, opts);
this.settings.browse = $(browseButton);
if (!this.settings.browse.length) throw 'The parameter browseButton is not valid object or selector.';
var s = this.settings;
if (s.dropzone) s.dropzone = $(s.dropzone);
if (s.upload) s.upload = $(s.upload);
runtimes[runtime].create(this, this.settings);
},
API_ON_START : function(sender, params) {
if ($.isFunction(this.settings.onstart)) {
this.settings.onstart.apply(this, params);
}
},
API_ON_FINISH : function(sender, params) {
if ($.isFunction(this.settings.onfinish)) {
this.settings.onfinish.apply(this, params);
}
},
API_ON_SELECT : function(sender, params) {
if ($.isFunction(this.settings.onselect)) {
this.settings.onselect.apply(this, params);
}
},
API_ON_STARTONE : function(sender, params) {
if ($.isFunction(this.settings.onstartone)) {
this.settings.onstartone.apply(this, params);
}
},
API_ON_FINISHONE : function(sender, params) {
if ($.isFunction(this.settings.onfinishone)) {
this.settings.onfinishone.apply(this, params);
}
},
API_ON_PROGRESS : function(sender, params) {
if ($.isFunction(this.settings.onprogress)) {
this.settings.onprogress.apply(this, params);
}
},
API_START : function(sender, params) {
var files = $.grep(this.files, function(file){ return (file.status != 'DONE') });
runtimes[runtime].upload(this, this.settings, files);
},
API_STOP : function(sender, params) {
runtimes[runtime].stop(this, this.settings);
},
API_SET_PARAM : function(sender, params) {
this.settings.params[params[0]] = params[1];
},
API_DEL_PARAM : function(sender, params) {
try {
delete this.settings.params[params[0]];
} catch(e){}
}
});
// Shortcut function in jQuery
$.fn.uploader = function(opts) {
var u = new Uploader(this.eq(0), opts);
if (u) xe.registerApp(u);
return u;
};
// Shortcut function in XE
xe.createUploader = function(browseButton, opts) {
var u = new Uploader(browseButton, opts);
if (u) xe.registerApp(u);
return u;
};
// {{{ runtimes
var runtimes = {};
// Google Gears
runtimes.gears = {
_desktop : null,
create : function(uploader, settings) {
var self = this;
var opt = {filter:[]};
if (!window.google || google.gears || !google.gears.factory) this.createFactory();
if (!this._desktop && google.gears.factory) this._desktop = google.gears.factory.create('beta.desktop');
if (!this._desktop) return false;
// browse button
settings.browse.click(function(){
self._desktop.openFiles(onselect, opt);
return false;
});
// file filters
$.each(settings.filters, function(k,v){ opt.filter=$.merge(opt.filter,v.split(' ')) });
opt.filter = $.map(opt.filter, function(ext){ return '.'+ext });
// select file callback
function onselect(files) {
var old_length = uploader.files.length;
$.each(files, function() {
uploader.files.push(new File(this, this.blob.length))
});
uploader.cast('ON_SELECT', [uploader.files, old_length]);
}
// drag and drop
if (settings.dropzone) {
settings.dropzone
.bind('dragover', function(){ return false; })
.bind('drop', function(event) {
var data = self._desktop.getDragData(event.originalEvent, 'application/x-gears-files');
var files = $.grep(data.files, function(file) {
var ext = file.name.match(/\.[a-z0-9]+$/)[0] || '';
return ($.inArray(ext, opt.filter)!=-1);
});
onselect(files);
return false;
});
}
// upload
if (settings.upload) {
settings.upload.click(function(){
uploader.cast('START');
return false;
});
}
},
createFactory : function() {
var f, m;
if (!window.google) window.google = {};
if (!google.gears) {
google.gears = {};
if (typeof(GearsFactory)!='undefined'){f = new GearsFactory()} // Firefox
else {
try{ // IE
f = new ActiveXObject('Gears.Factory');
}catch(e){ // Safari
if ((typeof(m=navigator.mimeTypes)!='undefined')&&(m['application/x-googlegears'])) {
f = $('<object type="application/x-googlegears" style="display:none;width:0;height:0">').appendTo(document.documentElement);
}
}
}
if(f) google.gears.factory = f;
}
},
check : function() {
if ((window.google && google.gears && google.gears.factory) || (typeof(GearsFactory) != 'undefined')) return true;
try {
this.factory = new ActiveXObject('Gears.factory');
return true;
} catch(e) {
var m = navigator.mimeTypes;
if (m && m['application/x-googlegears']) return true;
}
},
upload : function(uploader, settings, files) {
if (uploader.request || !files.length) return false;
uploader.cast('ON_START');
(function uploadNext() {
var file = files.shift();
var req = google.gears.factory.create('beta.httprequest');
var blob = google.gears.factory.create('beta.blobbuilder');
var bndr = '--------------xe-boundary'+random();
var data, gap;
$.each(settings.params, function(key, val) {
blob.append(
'--'+bndr+'\r\n'+
'Content-Disposition: form-data; name="'+key+'"\r\n\r\n'+
val+'\r\n'
);
});
blob.append(
'--'+bndr+'\r\n'+
'Content-Disposition: form-data; name="Filedata"; filename="'+file.name+'"\r\n'+
'Content-Type: application/octet-stream\r\n\r\n'
);
blob.append(file.object.blob);
blob.append('\r\n--'+bndr+'--\r\n');
data = blob.getAsBlob();
gap = data.length - file.size;
uploader.cast('ON_STARTONE', [file]);
req.open('POST', settings.url);
req.setRequestHeader('Content-Type', 'multipart/form-data; boundary='+bndr);
req.onreadystatechange = function() {
if (req.readyState == 1) {
file.status = 'UPLOADING';
return;
}
if (req.readyState != 4) return;
uploader.request = null;
if (req.status == 200) {
file.status = 'DONE';
} else {
file.status = 'FAILED';
}
if (files.length) {
uploadNext();
} else {
setTimeout(function(){ uploader.cast('ON_FINISH') }, 0);
}
uploader.cast('ON_FINISHONE', [file]);
};
req.upload.onprogress = function(event) {
if (event.lengthComputable) {
file.loaded = Math.max(event.loaded - gap, 0);
uploader.cast('ON_PROGRESS', [file]);
}
};
req.send(data);
})();
},
stop : function(uploader, settings) {
if (uploader.request) {
uploader.request.abort();
uploader.request = null;
}
uploader.cast('ON_STOP');
}
};
// HTML5
runtimes.html5 = {
create : function(uploader, settings) {
var self = this;
var filter = [];
// filter by extension
$.each(settings.filters, function(k,v){ filter=$.merge(filter,v.split(' ')) });
// when file is selected
function onselect() {
var files = $.grep(this.files, function(file) {
var ext = (file.fileName.match(/\.([^\.]+)$/)[1] || '').toLowerCase();
return (!filter.length || $.inArray(ext,filter) != -1);
});
if (files.length) {
var old_length = uploader.files.length;
$.each(files, function(idx, file){
uploader.files.push(new File(file, file.size||file.fileSize));
});
uploader.cast('ON_SELECT', [uploader.files, old_length]);
}
this.value = '';
}
function make_button(event) {
var op = this.offsetParent;
var ow = this.offsetWidth;
var oh = this.offsetHeight;
var ot = this.offsetTop;
var ol = this.offsetLeft;
if (!uploader.browseButton) {
uploader.browseButton = $('<div style="position:absolute;overflow:hidden;"><input type="file" name="Filedata" multiple="multiple" style="position:absolute;top:0;right:0;font-size:100px;" /></div>').appendTo(op);
uploader.browseButton.find('>input').css({cursor:'pointer',opacity:0}).change(onselect);
}
uploader.browseButton.css({width:ow+'px', height:oh+'px', left:ol+'px', top:ot+'px', margin:0, padding:0});
if (event.type == 'focus') uploader.browseButton.find('>input').focus();
}
// browse button
settings.browse.bind({mouseover:make_button,focus:make_button});
// drag and drop (available only Firefox)
if (settings.dropzone) {
settings.dropzone
.bind('dragover', function(){ return false; })
.bind('drop', function(event){
var data = event.originalEvent.dataTransfer;
var obj = {files:data.files||[]};
onselect.apply(obj);
return false;
});
}
// upload
if (settings.upload) {
settings.upload.click(function(){
uploader.cast('START');
return false;
});
}
},
check : function() {
if (window.XMLHttpRequest) {
var xhr = new XMLHttpRequest();
if (!!xhr.sendAsBinary || !!xhr.upload) return true;
}
return false;
},
upload : function(uploader, settings, files) {
if (uploader.request || !files.length) return false;
uploader.cast('ON_START');
(function uploadNext() {
var file = files.shift();
var req = uploader.request = new XMLHttpRequest();
var bndr = '--------------xe-boundary'+random();
var compatFF, data, bin, gap = 0;
// Firefox compatible mode
compatFF = typeof(file.object.getAsBinary) == 'function';
if (compatFF) {
data = '';
$.each(settings.params, function(key, val) {
data += '--'+bndr+'\r\n';
data += 'Content-Disposition: form-data; name="'+key+'"\r\n\r\n';
data += val+'\r\n';
});
// Firefox had a bug that recognises some unicode filename as invalid string.
// So, I made a workaround to encode the filename by RFC2231
data += '--'+bndr+'\r\n';
data += 'Content-Disposition: form-data; name="Filedata"; filename="=?UTF-8?B?'+Base64.encode(file.name).replace(/\//g, ':')+'?="\r\n';
data += 'Content-Type: application/octet-stream\r\n\r\n';
data += file.object.getAsBinary();
data += '\r\n';
data += '--'+bndr+'--\r\n';
bin = null;
gap = data.length - file.object.fileSize;
} else {
data = new FormData();
$.each(settings.params, function(key, val) {
data.append(key, val);
});
data.append('Filedata', file.object);
}
uploader.cast('ON_STARTONE', [file]);
function nextOrFinish() {
if (files.length) {
uploadNext();
} else {
setTimeout(function(){ uploader.cast('ON_FINISH') }, 0);
}
uploader.cast('ON_FINISHONE', [file]);
}
req.onreadystatechange = function() {
if (req.readyState == 1) {
file.status = 'UPLOADING';
return;
}
if (req.readyState != 4) return;
uploader.request = null;
if (req.status == 200) {
file.loaded = file.size;
file.status = 'DONE';
} else {
file.status = 'FAILED';
}
nextOrFinish();
};
req.onerror = function(event) {
file.status = 'FAILED';
nextOrFinish();
};
if (req.upload) {
req.upload.onprogress =
req.upload.onload =
function(event){
if (event.lengthComputable) {
file.loaded = Math.max(event.loaded - gap, 0);
uploader.cast('ON_PROGRESS', [file]);
}
};
}
req.open('POST', settings.url);
if (compatFF) {
req.setRequestHeader('Content-Type', 'multipart/form-data; boundary='+bndr);
req.sendAsBinary(data);
} else {
req.send(data);
}
})();
},
stop : function(uploader, settings) {
if (uploader.request) {
uploader.request.abort();
uploader.request = null;
}
uploader.cast('ON_STOP');
}
};
// Adobe Flash
runtimes.flash = {
version : 0,
object : null,
uploaders : [],
create : function(uploader, settings) {
var self = this;
var rand = random();
var name = 'xe_flashuploader_object'+rand;
var swf = (window.request_uri||'/')+'common/js/plugins/uploader/uploader.swf';
if (!window.xe_flashuploaders) window.xe_flashuploaders = [];
function make_button(event) {
var b = this;
var op = b.offsetParent;
var ow = b.offsetWidth;
var oh = b.offsetHeight;
var ot = b.offsetTop;
var ol = b.offsetLeft;
var $div, flash, css;
if (typeof uploader.flashindex == 'undefined') {
uploader.flashindex = xe_flashuploaders.length;
xe_flashuploaders.push({
uploader : uploader,
settings : settings,
onselect : function(files) {
var old_length = uploader.files.length;
$.each(files, function() {
uploader.files.push(new File(this, this.size))
});
uploader.cast('ON_SELECT', [uploader.files, old_length]);
},
onprogress : function(fileInfo) {},
oncomplete : function(fileInfo) {},
onerror : function(fileInfo) {},
oncancel : function(fileInfo) {}
});
}
div = $('#'+name+'-container');
css = {position:'absolute',width:ow+'px',height:oh+'px',left:ol+'px',top:ot+'px',margin:0,padding:0,overflow:'hidden'};
if (!div.length) {
div = $('<div />').attr('id', name+'-container').css(css).appendTo(op);
div[0].innerHTML = ''
+ '<object id="'+name+'" classid="clsid:D27CDB6E-AE6D-11CF-96B8-444553540000" width="100%" height="100%">'
+ '<param name="movie" value="'+swf+'" />'
+ '<param name="wmode" value="transparent" />'
+ '<param name="allowScriptAccess" value="always" />'
+ '<embed src="'+swf+'" width="100%" height="100%" name="'+name+'" type="application/x-shockwave-flash" pluginpage="http://www.macromedia.com/go/getflashplayer" wmode="transparent" allowscriptaccess="always" />'
+ '</object>';
}
flash = window[name] || document[name];
if (!uploader.flash) {
try {
if (!flash) throw '';
var _settings = {};
var pass_keys = ['filters','params','url'];
$.each(settings, function(key,val){
if ($.inArray(key, pass_keys) < 0) return true;
_settings[key] = val;
});
flash.setConfig(uploader.flashindex, _settings);
uploader.flash = flash;
uploader.flash_box = $(flash).parents('div:first');
} catch(e) {
return setTimeout(arguments.callee, 10);
}
}
if (uploader.flash) {
uploader.flash_box.css(css);
uploader.flash.setIndex(uploader.flashindex);
}
}
// browse button
settings.browse.bind({mouseover:make_button,focus:make_button});
// upload
if (settings.upload) {
settings.upload.click(function(){
uploader.cast('START');
return false;
});
}
},
check : function() {
var p = navigator.plugins, v;
if (p && (v=p['Shockwave Flash'])) {
v = v.description;
} else {
try {
v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
} catch(e) {
v = "0.0";
}
}
v = v.match(/\d+/g);
v = parseFloat(v[0]+'.'+v[1]);
if (!isNaN(v)) this.version = v;
return (this.version >= 9);
},
upload : function(uploader, settings, files) {
if (typeof uploader.flashindex == 'undefined') return false;
if (uploader.request || !files.length) return false;
var g_uploader = xe_flashuploaders[uploader.flashindex];
uploader.cast('ON_START');
(function uploadNext(){
var file = files.shift();
function nextOrFinish() {
if (files.length) {
uploadNext();
} else {
setTimeout(function(){ uploader.cast('ON_FINISH') }, 0);
}
uploader.cast('ON_FINISHONE', [file]);
}
file.status = 'UPLOADING';
g_uploader.onprogress = function(fileInfo){
file.loaded = fileInfo.loaded;
uploader.cast('ON_PROGRESS', [file]);
};
g_uploader.oncomplete = function(fileInfo) {
file.loaded = fileInfo.loaded;
file.status = 'DONE';
nextOrFinish();
};
g_uploader.onerror = function() {
file.status = 'FAILED';
nextOrFinish();
};
uploader.flash.startUpload(file.object.index);
uploader.cast('ON_STARTONE', [file]);
})();
},
stop : function(uploader, settings) {
if (typeof uploader.flashindex == 'undefined') return false;
}
};
// HTML4
runtimes.html4 = {
create : function(uploader, settings) {
var self = this;
var filter = [];
// filter by extension
$.each(settings.filters, function(k,v){ filter=$.merge(filter,v.split(' ')) });
// when file is selected
function onselect() {
var ext = (this.value.match(/\.([^\.]+)$/)[1] || '').toLowerCase();
if ($.inArray(ext, filter) != -1) {
uploader.files.push(new File(this, -1));
uploader.cast('ON_SELECT', [uploader.files, uploader.files.length - 1]);
}
uploader.browseButton = null;
$(this).parent().remove();
}
function make_button(event) {
var op = this.offsetParent;
var ow = this.offsetWidth;
var oh = this.offsetHeight;
var ot = this.offsetTop;
var ol = this.offsetLeft;
if (!uploader.browseButton) {
uploader.browseButton = $('<div style="position:absolute;overflow:hidden;"><input type="file" name="Filedata" style="position:absolute;top:0;right:0;font-size:100px;" /></div>')
.appendTo(op)
.change(onselect);
uploader.browseButton.find('>input').css({cursor:'pointer',opacity:0});
}
uploader.browseButton.css({width:ow+'px', height:oh+'px', left:ol+'px', top:ot+'px', margin:0, padding:0});
if (event.type == 'focus') uploader.browseButton.find('>input').focus();
}
// browse button
settings.browse.bind({mouseover:make_button,focus:make_button});
// upload
if (settings.upload) {
settings.upload.click(function(){
uploader.cast('START');
return false;
});
}
},
check : function() {
return true;
},
upload : function(uploader, settings, files) {
if (uploader.request || !files.length) return false;
var css = {position:'absolute',width:'1px',height:'1px',left:'-100px',top:'-100px',overflow:'hidden'};
// callback
if (!window.callbacks) window.callbacks = {};
uploader.cast('ON_START');
(function uploadNext(){
var file = files.shift();
var id = 'tmp_upload_'+random();
var iframe = $('<iframe src="about:blank" name="'+id+'"></iframe>').css(css).appendTo(document.documentElement);
var form = $('<form method="post" enctype="multipart/form-data" target="'+id+'"></form>').css(css).appendTo(document.documentElement);
form.attr('action', settings.url);
uploader.request = {iframe:iframe, form:form};
// set parameters
settings.params._callback = 'callbacks.'+id; // callback
$.each(settings.params, function(k,v) {
$('<input type="hidden" name="'+k+'" />').val(v).appendTo(form);
});
// set status of a file
file.status = 'UPLOADING';
callbacks[id] = function(fileInfo) {
file.status = 'DONE';
file.size = fileInfo.size;
file.loaded = fileInfo.size;
form.find('>input[type=file]').remove();
uploader.cast('ON_FINISHONE', [file]);
if (files.length) {
setTimeout(uploadNext, 0);
} else {
uploader.request = null;
uploader.cast('ON_FINISH');
}
setTimeout(function(){
form.remove();
iframe.remove();
delete callbacks[id];
}, 0);
};
form.append(file.object).submit();
})();
},
stop : function(uploader, settings) {
if (uploader.request) {
uploader.request.iframe.attr('src', 'about:blank').remove();
uploader.request.form.remove();
uploader.request = null;
}
}
};
// }}} runtimes
var mimetypes = {
'application/java-archive' : 'jar',
'application/java-vm' : 'class',
'application/javascript' : 'js',
'application/msword' : 'doc dot',
'application/pdf' : 'pdf',
'application/octet-stream' : 'bin lha lzh iso dmg dist pkg exe',
'application/postscript' : 'ai eps ps',
'application/rtf' : 'rtf',
'application/smil' : 'smi smil',
'application/vnd.ms-excel' : 'xls xlm xla xlc xlt xlw',
'application/vnd.openxmlformats': 'docx pptx xlsx',
'application/vnd.ms-powerpoint' : 'ppt pps pot',
'application/zip' : 'zip',
'application/x-shockwave-flash' : 'swf swfl',
'audio/mpeg' : 'mpga mpega mp2 mp3',
'audio/x-wav' : 'wav',
'image/bmp' : 'bmp',
'image/gif' : 'gif',
'image/jpeg' : 'jpeg jpg jpe',
'image/png' : 'png',
'image/svg+xml' : 'svg svgz',
'image/tiff' : 'tiff tif',
'text/html' : 'htm html xhtml',
'text/plain' : 'asc txt text diff log',
'video/mpeg' : 'mpeg mpg mpe',
'video/quicktime' : 'qt mov',
'video/x-flv' : 'flv',
'video/x-ms-asf' : 'asf',
'video/x-ms-wmv' : 'wmv',
'video/x-msvideo' : 'avi'
};
function File(obj, filesize) {
if (runtime == 'html4') {
this.name = obj.value.match(/[^\\\/]+$/)[0];
} else {
this.name = obj.name || obj.fileName;
}
this.size = filesize;
this.loaded = 0;
this.status = 'QUEUED'; // QUEUED, UPLOADING, FAILED, DONE
this.object = obj;
};
function random() {
return Math.floor(Math.random()*80000+10000);
}
})(jQuery);

View file

@ -22,6 +22,7 @@ $lang->cmd_upload = 'Upload';
$lang->cmd_load = 'Load';
$lang->cmd_input = 'Input';
$lang->cmd_search = 'Search';
$lang->cmd_sort = 'Sort';
$lang->cmd_find = 'Find';
$lang->cmd_replace = 'Replace';
$lang->cmd_confirm = 'Confirm';

View file

@ -22,6 +22,7 @@ $lang->cmd_upload = '업로드';
$lang->cmd_load = '불러오기';
$lang->cmd_input = '입력';
$lang->cmd_search = '검색';
$lang->cmd_sort = '정렬';
$lang->cmd_find = '찾기';
$lang->cmd_replace = '바꾸기';
$lang->cmd_confirm = '확인';

View file

@ -335,6 +335,7 @@ class Cleanup extends Base
'common/js/plugins/spectrum/docs/' => 'deleted',
'common/js/plugins/spectrum/example/' => 'deleted',
'common/js/plugins/spectrum/test/' => 'deleted',
'common/js/plugins/uploader/' => 'deleted:xe',
'common/lang/lang.info' => 'deleted:xe',
'common/libraries/bmp.php' => 'deleted',
'common/manual/server_config/rhymix-nginx-help.md' => 'deleted',

View file

@ -187,7 +187,7 @@ $lang->about_use_session_ssl = 'Force the session to be SSL-only.<br>This helps
$lang->use_cookies_ssl = 'Use SSL-only cookies';
$lang->about_use_cookies_ssl = 'Force all cookies to be SSL-only.';
$lang->check_csrf_token = 'Use CSRF tokens';
$lang->about_check_csrf_token = 'Use CSRF tokens to validate requests. This is more secure but may break some functionality.<br>If not selected, Rhymix will use only the Referer header to defend against CSRF attacks.';
$lang->about_check_csrf_token = 'Use CSRF tokens to validate requests. This may break some functionality.<br>If not selected, Rhymix will use headers such as Sec-Fetch-Site and Origin to block CSRF attacks.';
$lang->use_nofollow = 'Add nofollow attribute to Links';
$lang->about_use_nofollow = 'Add rel=&quot;nofollow&quot; to all links submitted by users in order to reduce the effectiveness of spamming.<br>This does not apply to content submitted by the administrator.';
$lang->use_object_cache = 'Use Cache';

View file

@ -188,7 +188,7 @@ $lang->about_use_session_ssl = '세션을 SSL 전용으로 지정하여 SSL이
$lang->use_cookies_ssl = 'SSL 전용 쿠키 사용';
$lang->about_use_cookies_ssl = '세션뿐 아니라 모든 쿠키를 SSL 전용으로 지정합니다.<br>SSL을 항상 사용하도록 설정되어 있는 경우에만 활성화됩니다.';
$lang->check_csrf_token = 'CSRF 토큰 사용';
$lang->about_check_csrf_token = 'CSRF 토큰을 사용하여 정상적인 요청 여부를 확인합니다. 보안이 향상되지만, 일부 서드파티 자료와 호환되지 않을 수 있습니다.<br>사용하지 않을 경우 리퍼러 헤더 확인만으로 CSRF 공격에 방어합니다.';
$lang->about_check_csrf_token = 'CSRF 토큰을 사용하여 정상적인 요청 여부를 확인합니다. 일부 자료와 호환되지 않을 수 있습니다.<br>사용하지 않을 경우 Sec-Fetch-Site, Origin 등의 헤더를 사용하여 CSRF 공격을 감지합니다.';
$lang->use_nofollow = '링크에 nofollow 추가';
$lang->about_use_nofollow = '사용자들이 작성한 글에 포함된 모든 링크에 rel=&quot;nofollow&quot; 속성을 추가하여 스팸으로 인한 사이트 신뢰도 저하를 방지합니다.<br>관리자가 작성한 글에는 적용되지 않습니다.';
$lang->use_object_cache = '캐시 사용';

View file

@ -71,32 +71,14 @@ class BoardController extends Board
}
// Check category
if (!$obj->category_srl && !$this->grant->manager && $this->module_info->allow_no_category !== 'Y')
{
$category_list = DocumentModel::getCategoryList($this->module_srl);
if (count($category_list) > 0)
{
if ($obj->category_srl)
{
if (isset($category_list[$obj->category_srl]))
{
if (!$category_list[$obj->category_srl]->grant)
{
return new BaseObject(-1, 'msg_not_permitted');
}
}
else
{
$obj->category_srl = 0;
}
}
if (!$obj->category_srl && $this->module_info->allow_no_category !== 'Y')
{
if (!$this->grant->manager)
{
return new BaseObject(-1, sprintf(lang('common.filter.isnull'), lang('common.category')));
}
}
}
// unset document style if not manager
if(!$this->grant->manager)
@ -134,7 +116,6 @@ class BoardController extends Board
$manual = false;
$logged_info = Context::get('logged_info');
$oDocument = DocumentModel::getDocument($obj->document_srl);
// Set anonymous information when insert mode or status is temp

View file

@ -99,10 +99,14 @@ class BoardView extends Board
if (is_array($extra_keys))
{
foreach($extra_keys as $val)
{
if ($val->sort === 'Y')
{
$this->order_target[] = $val->eid;
}
}
}
/**
* load javascript, JS filters
*/
@ -856,60 +860,6 @@ class BoardView extends Board
// Fix any missing module configurations
BoardModel::fixModuleConfig($this->module_info);
/**
* check if the category option is enabled not not
*/
if ($this->module_info->use_category === 'Y')
{
// get the user group information
if(Context::get('is_logged'))
{
$group_srls = array_keys($this->user->group_list);
}
else
{
$group_srls = array();
}
// check the grant after obtained the category list
$category_list = array();
$normal_category_list = DocumentModel::getCategoryList($this->module_srl);
if(count($normal_category_list))
{
foreach($normal_category_list as $category_srl => $category)
{
$is_granted = TRUE;
if(isset($category->group_srls) && $category->group_srls)
{
$category_group_srls = explode(',',$category->group_srls);
$is_granted = FALSE;
if(count(array_intersect($group_srls, $category_group_srls))) $is_granted = TRUE;
}
if($is_granted) $category_list[$category_srl] = $category;
}
}
// check if at least one category is granted
$grant_exists = false;
foreach ($category_list as $category)
{
if ($category->grant)
{
$grant_exists = true;
}
}
if ($grant_exists)
{
Context::set('category_list', $category_list);
}
else
{
$this->module_info->use_category = 'N';
Context::set('category_list', array());
}
}
// GET parameter document_srl from request
$document_srl = Context::get('document_srl');
$oDocument = DocumentModel::getDocument(0);
@ -985,12 +935,70 @@ class BoardView extends Board
}
}
}
if(!$oDocument->get('status')) $oDocument->add('status', DocumentModel::getDefaultStatus());
$statusList = $this->_getStatusNameList();
if(count($statusList) > 0) Context::set('status_list', $statusList);
if (count($statusList) > 0)
{
Context::set('status_list', $statusList);
}
if (!$oDocument->get('status'))
{
$oDocument->add('status', DocumentModel::getDefaultStatus());
}
// get Document status config value
// Check category grants
if ($this->module_info->use_category === 'Y')
{
$category_list = array();
$normal_category_list = DocumentModel::getCategoryList($this->module_srl);
$group_srls = $this->user->group_list ?? [];
if(count($normal_category_list))
{
foreach ($normal_category_list as $category_srl => $category)
{
$is_granted = true;
if (isset($category->group_srls) && $category->group_srls)
{
$category_group_srls = explode(',', $category->group_srls);
$is_granted = false;
if (count(array_intersect($group_srls, $category_group_srls)))
{
$is_granted = true;
}
}
if ($oDocument->isExists() && $oDocument->get('category_srl') == $category_srl)
{
$category->grant = true;
$is_granted = true;
}
if ($is_granted)
{
$category_list[$category_srl] = $category;
}
}
}
// check if at least one category is granted
$grant_exists = false;
foreach ($category_list as $category)
{
if ($category->grant)
{
$grant_exists = true;
}
}
if ($grant_exists)
{
Context::set('category_list', $category_list);
}
else
{
$this->module_info->use_category = 'N';
Context::set('category_list', array());
}
}
// Set to Context
Context::set('document_srl',$document_srl);
Context::set('oDocument', $oDocument);

View file

@ -183,7 +183,8 @@ class DocumentAdminController extends Document
$options = array_map('trim', explode("\n", $options));
}
$desc = Context::get('desc') ? Context::get('desc') : '';
$search = Context::get('search');
$search = Context::get('search') === 'Y' ? 'Y' : 'N';
$sort = Context::get('sort') === 'Y' ? 'Y' : 'N';
$eid = Context::get('eid');
$obj = new stdClass();
@ -210,7 +211,7 @@ class DocumentAdminController extends Document
$oDocumentController = DocumentController::getInstance();
$output = $oDocumentController->insertDocumentExtraKey(
$module_srl, $var_idx, $name, $type, $is_required, $search,
$default, $desc, $eid, $is_strict, $options
$default, $desc, $eid, $is_strict, $options, $sort
);
if(!$output->toBool()) return $output;

View file

@ -87,9 +87,6 @@ class Document extends ModuleObject
if(!$oDB->isIndexExists("document_extra_vars", "unique_extra_vars")) return true;
if($oDB->isIndexExists("document_extra_vars", "unique_module_vars")) return true;
// 2011. 03. 30 Cubrid index Check the index in the document_extra_vars table
if(!$oDB->isIndexExists("document_extra_vars", "idx_document_list_order")) return true;
// 2011. 10. 25 status index check
if(!$oDB->isIndexExists("documents", "idx_module_status")) return true;
@ -113,6 +110,14 @@ class Document extends ModuleObject
if(!$oDB->isColumnExists('document_extra_keys', 'var_is_strict')) return true;
if(!$oDB->isColumnExists('document_extra_keys', 'var_options')) return true;
// 2025.10.23 Add sort to document_extra_keys table, and sort_value to document_extra_vars table
if(!$oDB->isColumnExists('document_extra_keys', 'var_sort')) return true;
if(!$oDB->isColumnExists('document_extra_vars', 'sort_value') || !$oDB->isIndexExists('document_extra_vars', 'idx_sort_value')) return true;
if(!$oDB->isIndexExists('document_extra_vars', 'idx_prefix_value')) return true;
// Delete unnecessary index
if($oDB->isIndexExists('document_extra_vars', 'idx_document_list_order')) return true;
return false;
}
@ -178,12 +183,7 @@ class Document extends ModuleObject
$oDB->dropIndex("document_extra_vars", "unique_module_vars", true);
}
// 2011. 03. 30 Cubrid index Check the index in the document_extra_vars table
if(!$oDB->isIndexExists("document_extra_vars", "idx_document_list_order"))
{
$oDB->addIndex("document_extra_vars", "idx_document_list_order", array("document_srl","module_srl","var_idx"), false);
}
// 2011. 10. 25 status index check
if(!$oDB->isIndexExists("documents", "idx_module_status"))
{
$oDB->addIndex("documents", "idx_module_status", array("module_srl","status"));
@ -233,6 +233,47 @@ class Document extends ModuleObject
{
$oDB->addColumn('document_extra_keys', 'var_options', 'text', null, null, false, 'var_default');
}
// 2025.10.23 Add sort to document_extra_keys table, and sort_value to document_extra_vars table
if(!$oDB->isColumnExists('document_extra_keys', 'var_sort'))
{
$oDB->addColumn('document_extra_keys', 'var_sort', 'char', '1', 'N', true, 'var_search');
}
if(!$oDB->isColumnExists('document_extra_vars', 'sort_value') || !$oDB->isIndexExists('document_extra_vars', 'idx_sort_value'))
{
$oDB->addColumn('document_extra_vars', 'sort_value', 'bigint', null, null, false, 'value');
$oDB->begin();
$output = executeQueryArray('document.getDocumentNumericExtraKeys', ['var_type' => 'number']);
if (!$output->toBool())
{
$oDB->rollback();
return $output;
}
foreach ($output->data ?? [] as $item)
{
$output = executeQuery('document.updateDocumentExtraVarSortValue', [
'module_srl' => $item->module_srl,
'var_idx' => $item->var_idx,
]);
if (!$output->toBool())
{
$oDB->rollback();
return $output;
}
}
$oDB->commit();
$oDB->addIndex('document_extra_vars', 'idx_sort_value', array('module_srl', 'sort_value'));
}
if(!$oDB->isIndexExists('document_extra_vars', 'idx_prefix_value'))
{
$oDB->addIndex('document_extra_vars', 'idx_prefix_value', array('module_srl', 'value(10)'));
}
// Delete unnecessary index
if($oDB->isIndexExists('document_extra_vars', 'idx_document_list_order'))
{
$oDB->dropIndex('document_extra_vars', 'idx_document_list_order');
}
}
/**

View file

@ -761,7 +761,7 @@ class DocumentController extends Document
{
if (!$category_list[$obj->category_srl]->grant)
{
return new BaseObject(-1, 'msg_not_permitted');
return new BaseObject(-1, 'document.msg_category_not_permitted');
}
}
else
@ -769,6 +769,10 @@ class DocumentController extends Document
$obj->category_srl = 0;
}
}
else
{
$obj->category_srl = 0;
}
}
// Set the read counts and update order.
@ -851,7 +855,7 @@ class DocumentController extends Document
{
foreach($extra_keys as $idx => $extra_item)
{
$value = NULL;
$value = $sort_value = null;
if(isset($obj->{'extra_vars'.$idx}))
{
$tmp = $obj->{'extra_vars'.$idx};
@ -904,7 +908,11 @@ class DocumentController extends Document
}
$extra_vars[$extra_item->name] = $value;
$this->insertDocumentExtraVar($obj->module_srl, $obj->document_srl, $idx, $value, $extra_item->eid);
if ($extra_item->type === 'number')
{
$sort_value = (int)$value;
}
$this->insertDocumentExtraVar($obj->module_srl, $obj->document_srl, $idx, $value, $extra_item->eid, null, $sort_value);
}
}
@ -1117,7 +1125,7 @@ class DocumentController extends Document
{
if (!$category_list[$obj->category_srl]->grant)
{
return new BaseObject(-1, 'msg_not_permitted');
return new BaseObject(-1, 'document.msg_category_not_permitted');
}
}
else
@ -1125,6 +1133,10 @@ class DocumentController extends Document
$obj->category_srl = 0;
}
}
else
{
$obj->category_srl = 0;
}
}
// Hash the password if it exists
@ -1234,7 +1246,7 @@ class DocumentController extends Document
{
foreach($extra_keys as $idx => $extra_item)
{
$value = NULL;
$value = $sort_value = null;
if(isset($obj->{'extra_vars'.$idx}))
{
$tmp = $obj->{'extra_vars'.$idx};
@ -1331,7 +1343,11 @@ class DocumentController extends Document
}
}
$extra_vars[$extra_item->name] = $value;
$this->insertDocumentExtraVar($obj->module_srl, $obj->document_srl, $idx, $value, $extra_item->eid);
if ($extra_item->type === 'number')
{
$sort_value = (int)$value;
}
$this->insertDocumentExtraVar($obj->module_srl, $obj->document_srl, $idx, $value, $extra_item->eid, null, $sort_value);
}
}
@ -1779,9 +1795,10 @@ class DocumentController extends Document
* @param int $eid
* @param string $var_is_strict
* @param array $var_options
* @param string $var_sort
* @return object
*/
function insertDocumentExtraKey($module_srl, $var_idx, $var_name, $var_type, $var_is_required = 'N', $var_search = 'N', $var_default = '', $var_desc = '', $eid = 0, $var_is_strict = 'N', $var_options = null)
function insertDocumentExtraKey($module_srl, $var_idx, $var_name, $var_type, $var_is_required = 'N', $var_search = 'N', $var_default = '', $var_desc = '', $eid = 0, $var_is_strict = 'N', $var_options = null, $var_sort = 'N')
{
if (!$module_srl || !$var_idx || !$var_name || !$var_type || !$eid)
{
@ -1796,6 +1813,7 @@ class DocumentController extends Document
$obj->var_is_required = $var_is_required=='Y'?'Y':'N';
$obj->var_is_strict = $var_is_strict=='Y'?'Y':'N';
$obj->var_search = $var_search=='Y'?'Y':'N';
$obj->var_sort = $var_sort=='Y'?'Y':'N';
$obj->var_default = $var_default;
$obj->var_options = $var_options ? json_encode($var_options, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES) : null;
$obj->var_desc = $var_desc;
@ -1883,9 +1901,10 @@ class DocumentController extends Document
* @param mixed $value
* @param int $eid
* @param string $lang_code
* @return Object|void
* @param ?int $sort_value
* @return BaseObject
*/
public static function insertDocumentExtraVar($module_srl, $document_srl, $idx_or_eid, $value, $eid = null, $lang_code = null)
public static function insertDocumentExtraVar($module_srl, $document_srl, $idx_or_eid, $value, $eid = null, $lang_code = null, $sort_value = null)
{
if(!$module_srl || !$document_srl || !$idx_or_eid || !isset($value))
{
@ -1918,6 +1937,7 @@ class DocumentController extends Document
$obj->document_srl = $document_srl;
$obj->var_idx = $idx_or_eid;
$obj->value = $value;
$obj->sort_value = $sort_value;
$obj->lang_code = $lang_code ?: Context::getLangType();
$obj->eid = $eid;
@ -1932,9 +1952,10 @@ class DocumentController extends Document
* @param mixed $value
* @param int $eid
* @param string $lang_code
* @return Object|void
* @param ?int $sort_value
* @return BaseObject
*/
public static function updateDocumentExtraVar($module_srl, $document_srl, $idx_or_eid, $value, $eid = null, $lang_code = null)
public static function updateDocumentExtraVar($module_srl, $document_srl, $idx_or_eid, $value, $eid = null, $lang_code = null, $sort_value = null)
{
if(!$module_srl || !$document_srl || !$idx_or_eid || !isset($value))
{
@ -1962,13 +1983,10 @@ class DocumentController extends Document
}
}
$obj = new stdClass;
$obj->module_srl = $module_srl;
$obj->document_srl = $document_srl;
$obj->var_idx = $idx_or_eid;
$obj->value = $value;
$obj->lang_code = $lang_code ?: Context::getLangType();
$obj->eid = $eid;
if (!$lang_code)
{
$lang_code = Context::getLangType();
}
$oDB = DB::getInstance();
$oDB->begin();
@ -1980,7 +1998,7 @@ class DocumentController extends Document
return $output;
}
$output = self::insertDocumentExtraVar($module_srl, $document_srl, $idx_or_eid, $value, $eid, $lang_code);
$output = self::insertDocumentExtraVar($module_srl, $document_srl, $idx_or_eid, $value, $eid, $lang_code, $sort_value);
if (!$output->toBool())
{
$oDB->rollback();
@ -1998,7 +2016,7 @@ class DocumentController extends Document
* @param int $var_idx
* @param string $lang_code
* @param int $eid
* @return $output
* @return BaseObject
*/
public static function deleteDocumentExtraVars($module_srl, $document_srl = null, $var_idx = null, $lang_code = null, $eid = null)
{
@ -2008,8 +2026,7 @@ class DocumentController extends Document
if(!is_null($var_idx)) $obj->var_idx = $var_idx;
if(!is_null($lang_code)) $obj->lang_code = $lang_code;
if(!is_null($eid)) $obj->eid = $eid;
$output = executeQuery('document.deleteDocumentExtraVars', $obj);
return $output;
return executeQuery('document.deleteDocumentExtraVars', $obj);
}

View file

@ -239,6 +239,7 @@ class DocumentModel extends Document
$sort_check = self::_setSortIndex($obj, $load_extra_vars);
$obj->sort_index = $sort_check->sort_index;
$obj->isExtraVars = $sort_check->isExtraVars;
$obj->isExtraVarsSortAsNumber = $sort_check->isExtraVarsSortAsNumber;
$obj->except_notice = $except_notice;
$obj->columnList = $columnList;
@ -672,6 +673,7 @@ class DocumentModel extends Document
$sort_check = self::_setSortIndex($opt);
$opt->sort_index = $sort_check->sort_index;
$opt->isExtraVars = $sort_check->isExtraVars;
$opt->isExtraVarsSortAsNumber = $sort_check->isExtraVarsSortAsNumber;
self::_setSearchOption($opt, $args, $query_id, $use_division);
@ -1348,6 +1350,7 @@ class DocumentModel extends Document
$args = new stdClass;
$args->sort_index = $obj->sort_index ?? null;
$args->isExtraVars = false;
$args->isExtraVarsSortAsNumber = false;
// check it's default sort
$default_sort = array('list_order', 'regdate', 'last_update', 'update_order', 'readed_count', 'voted_count', 'blamed_count', 'comment_count', 'trackback_count', 'uploaded_count', 'title', 'category_srl');
@ -1363,20 +1366,20 @@ class DocumentModel extends Document
return $args;
}
$eids = array();
foreach($extra_keys as $idx => $key)
foreach($extra_keys as $extra_key)
{
$eids[] = $key->eid;
if ($args->sort_index === $extra_key->eid)
{
$args->isExtraVars = true;
if ($extra_key->type === 'number')
{
$args->isExtraVarsSortAsNumber = true;
}
// check it exists in extra keys of the module
if(!in_array($args->sort_index, $eids))
{
$args->sort_index = 'list_order';
return $args;
}
}
$args->isExtraVars = true;
$args->sort_index = 'list_order';
return $args;
}
@ -1560,8 +1563,15 @@ class DocumentModel extends Document
{
$args->sort_eid = $args->sort_index;
$args->sort_lang = Context::getLangType();
if ($searchOpt->isExtraVarsSortAsNumber ?? false)
{
$args->sort_index = 'extra_sort.sort_value';
}
else
{
$args->sort_index = 'extra_sort.value';
}
}
$query_id = 'document.getDocumentListWithExtraVars';
}
else

View file

@ -38,6 +38,7 @@ $lang->msg_category_not_moved = 'Could not be moved';
$lang->msg_is_secret = 'This is a secret article.';
$lang->msg_checked_document_is_deleted = '%d article(s) was(were) deleted.';
$lang->msg_document_is_admin_not_permitted = 'You don\'t have permission to delete the posts of Top Admin.';
$lang->msg_category_not_permitted = 'You don\'t have permission to post in the selected category.';
$lang->move_target_module = 'Target module ';
$lang->search_target_list['title'] = 'Subject';
$lang->search_target_list['content'] = 'Content';

View file

@ -38,6 +38,7 @@ $lang->msg_category_not_moved = '이동할 수 없습니다.';
$lang->msg_is_secret = '비밀글입니다.';
$lang->msg_checked_document_is_deleted = '%d개의 글이 삭제되었습니다.';
$lang->msg_document_is_admin_not_permitted = '최고 관리자의 게시글을 삭제할 권한이 없습니다.';
$lang->msg_category_not_permitted = '선택한 분류에 게시할 권한이 없습니다.';
$lang->move_target_module = '대상 페이지';
$lang->search_target_list['title'] = '제목';
$lang->search_target_list['content'] = '내용';

View file

@ -10,6 +10,7 @@
<column name="var_is_required" alias="is_required" />
<column name="var_is_strict" alias="is_strict" />
<column name="var_search" alias="search" />
<column name="var_sort" alias="sort" />
<column name="var_default" alias="default" />
<column name="var_options" alias="options" />
<column name="var_desc" alias="desc" />

View file

@ -0,0 +1,13 @@
<query id="getDocumentNumericExtraKeys" action="select">
<tables>
<table name="document_extra_keys" />
</tables>
<columns>
<column name="module_srl" />
<column name="var_idx" />
<column name="var_type" />
</columns>
<conditions>
<condition operation="in" column="var_type" var="var_type" />
</conditions>
</query>

View file

@ -10,6 +10,7 @@
<column name="var_is_required" var="var_is_required" default="N" notnull="notnull" />
<column name="var_is_strict" var="var_is_strict" default="N" notnull="notnull" />
<column name="var_search" var="var_search" default="N" notnull="notnull" />
<column name="var_sort" var="var_sort" default="N" notnull="notnull" />
<column name="var_default" var="var_default" />
<column name="var_options" var="var_options" />
<column name="var_desc" var="var_desc" />

View file

@ -7,6 +7,7 @@
<column name="document_srl" var="document_srl" filter="number" notnull="notnull" />
<column name="var_idx" var="var_idx" filter="number" notnull="notnull" />
<column name="value" var="value" notnull="notnull" />
<column name="sort_value" var="sort_value" />
<column name="lang_code" var="lang_code" />
<column name="eid" var="eid" notnull="notnull" />
</columns>

View file

@ -8,6 +8,7 @@
<column name="var_is_required" var="var_is_required" default="N" notnull="notnull" />
<column name="var_is_strict" var="var_is_strict" default="N" notnull="notnull" />
<column name="var_search" var="var_search" default="N" notnull="notnull" />
<column name="var_sort" var="var_sort" default="N" notnull="notnull" />
<column name="var_default" var="var_default" default="" />
<column name="var_options" var="var_options" default="" />
<column name="var_desc" var="var_desc" />

View file

@ -4,6 +4,7 @@
</tables>
<columns>
<column name="value" var="value" />
<column name="sort_value" var="sort_value" />
<column name="lang_code" var="lang_code" />
<column name="eid" var="eid" />
</columns>

View file

@ -0,0 +1,12 @@
<query id="updateDocumentExtraVarSortValue" action="update">
<tables>
<table name="document_extra_vars" />
</tables>
<columns>
<column name="sort_value" default="CAST(value AS SIGNED)" />
</columns>
<conditions>
<condition operation="equal" column="module_srl" var="module_srl" filter="number" notnull="notnull" />
<condition operation="equal" column="var_idx" var="var_idx" filter="number" notnull="notnull" />
</conditions>
</query>

View file

@ -6,6 +6,7 @@
<column name="var_is_required" type="char" size="1" default="N" notnull="notnull" />
<column name="var_is_strict" type="char" size="1" default="N" notnull="notnull" />
<column name="var_search" type="char" size="1" default="N" notnull="notnull" />
<column name="var_sort" type="char" size="1" default="N" notnull="notnull" />
<column name="var_default" type="text" />
<column name="var_options" type="text" />
<column name="var_desc" type="text" />

View file

@ -4,5 +4,8 @@
<column name="var_idx" type="number" size="11" notnull="notnull" unique="unique_extra_vars" />
<column name="lang_code" type="varchar" size="10" notnull="notnull" unique="unique_extra_vars" />
<column name="value" type="bigtext" />
<column name="sort_value" type="bigint" />
<column name="eid" type="varchar" size="40" />
<index name="idx_prefix_value" columns="module_srl,value(10)" />
<index name="idx_sort_value" columns="module_srl,sort_value" />
</table>

View file

@ -86,6 +86,13 @@
<label class="x_inline" for="search_n"><input type="radio" name="search" id="search_n" value="N" checked="checked"|cond="$selected_var->search!='Y'" /> {$lang->not}</label>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->cmd_sort}</label>
<div class="x_controls">
<label class="x_inline" for="sort_y"><input type="radio" name="sort" id="sort_y" value="Y" checked="checked"|cond="$selected_var->sort=='Y'" /> {$lang->yes}</label>
<label class="x_inline" for="sort_n"><input type="radio" name="sort" id="sort_n" value="N" checked="checked"|cond="$selected_var->sort!='Y'" /> {$lang->not}</label>
</div>
</div>
<div class="x_clearfix btnArea">
<div class="x_pull-left">
@ -120,6 +127,7 @@
<th>{$lang->default_value}</th>
<th>{$lang->is_required}</th>
<th>{$lang->cmd_search}</th>
<th>{$lang->cmd_sort}</th>
<th>&nbsp;</th>
</tr>
</thead>
@ -138,6 +146,7 @@
<td>{$val->default}&nbsp;</td>
<td class="nowr"><!--@if($val->is_required=='Y')--><strong>Y</strong><!--@else-->N<!--@end--></td>
<td class="nowr"><!--@if($val->search=='Y')--><strong>Y</strong><!--@else-->N<!--@end--></td>
<td class="nowr"><!--@if($val->sort=='Y')--><strong>Y</strong><!--@else-->N<!--@end--></td>
<td class="nowr" style="text-align:right">
<block cond="$val->idx > 1">
<button type="button" class="x_icon-arrow-up" onclick="moveVar('up','{$module_srl}','{$val->idx}')">{$lang->cmd_move_up}</button>

View file

@ -36,6 +36,7 @@ class Value
public $is_disabled = 'N';
public $is_readonly = 'N';
public $search = 'N';
public $sort = 'N';
public $style = null;
/**
@ -87,8 +88,9 @@ class Value
* @param string $parent_type
* @param string $is_strict
* @param string $options
* @param string $sort
*/
function __construct(int $module_srl, int $idx, string $name, string $type = 'text', $default = null, $desc = '', $is_required = 'N', $search = 'N', $value = null, $eid = '', $parent_type = 'document', $is_strict = '', $options = null)
function __construct(int $module_srl, int $idx, string $name, string $type = 'text', $default = null, $desc = '', $is_required = 'N', $search = 'N', $value = null, $eid = '', $parent_type = 'document', $is_strict = '', $options = null, $sort = 'N')
{
if (!$idx)
{
@ -108,6 +110,7 @@ class Value
$this->is_required = $is_required;
$this->is_strict = $is_strict;
$this->search = $search;
$this->sort = $sort;
}
/**

View file

@ -50,7 +50,7 @@ class ValueCollection
foreach ($keys as $val)
{
$this->keys[$val->idx] = new Value($val->module_srl, $val->idx, $val->name, $val->type, $val->default, $val->desc, $val->is_required, $val->search, $val->value ?? null, $val->eid, $val->parent_type ?? 'document', $val->is_strict, $val->options);
$this->keys[$val->idx] = new Value($val->module_srl, $val->idx, $val->name, $val->type, $val->default, $val->desc, $val->is_required, $val->search, $val->value ?? null, $val->eid, $val->parent_type ?? 'document', $val->is_strict, $val->options, $val->sort ?? 'N');
}
}

View file

@ -92,6 +92,7 @@ class FileAdminController extends File
$config->image_quality_adjustment = max(50, min(100, intval(Context::get('image_quality_adjustment'))));
$config->image_autorotate = Context::get('image_autorotate') === 'Y' ? true : false;
$config->image_remove_exif_data = Context::get('image_remove_exif_data') === 'Y' ? true : false;
$config->image_always_reencode = Context::get('image_always_reencode') === 'Y' ? true : false;
// Video settings
$config->max_video_width = intval(Context::get('max_video_width')) ?: '';
@ -389,6 +390,11 @@ class FileAdminController extends File
$result = FileHandler::createImageFile(FileHandler::getRealPath($file->uploaded_filename), $temp_filename, $width, $height, $format, 'fill', $quality);
if (!$result && !empty($config->magick_command))
{
$temp_dir = dirname($temp_filename);
if (!Rhymix\Framework\Storage::isDirectory($temp_dir))
{
Rhymix\Framework\Storage::createDirectory($temp_dir);
}
$command = vsprintf('%s %s -resize %dx%d -quality %d %s %s %s', [
\RX_WINDOWS ? escapeshellarg($config->magick_command) : $config->magick_command,
escapeshellarg(FileHandler::getRealPath($file->uploaded_filename)),

View file

@ -1290,6 +1290,12 @@ class FileController extends File
}
}
// Check if this image should be reencoded anyway
if (isset($config->image_always_reencode) && $config->image_always_reencode)
{
$force = true;
}
// Convert image if adjusted
if ($adjusted['width'] !== $image_info['width'] ||
$adjusted['height'] !== $image_info['height'] ||

View file

@ -107,6 +107,8 @@ $lang->image_autorotate = 'Fix Image Rotation';
$lang->about_image_autorotate = 'correct images that are rotated by mobile devices.';
$lang->image_remove_exif_data = 'Remove EXIF';
$lang->about_image_remove_exif_data = 'remove EXIF data including camera, GPS information, and more in image file for privacy.<br />Even if this option is not used, EXIF data may be removed when the image is converted by other settings.';
$lang->image_always_reencode = 'Always Reencode';
$lang->about_image_always_reencode = 'Reencode images to a constant quality even if they do not meet one of the conditions above. This may help save disk space and traffic.';
$lang->image_autoconv_gif2mp4 = 'Convert GIF to MP4';
$lang->about_image_autoconv_gif2mp4 = 'convert animated GIF images into MP4 videos to save storage and bandwidth.<br />This requires ffmpeg settings below. Videos may not play properly in older browsers.';
$lang->max_video_size = 'Limit Video Size';

View file

@ -107,6 +107,8 @@ $lang->image_autorotate = '이미지 회전 수정';
$lang->about_image_autorotate = '모바일 기기 등에서 잘못 회전된 이미지를 바로잡습니다.';
$lang->image_remove_exif_data = 'EXIF 제거';
$lang->about_image_remove_exif_data = '프라이버시를 위해 이미지 파일에서 카메라, GPS 정보 등이 포함되어 있는 EXIF 데이터를 삭제합니다.<br />이 옵션을 사용하지 않아도 다른 설정에 의해 이미지가 변환될 경우에도 EXIF 데이터가 삭제될 수 있습니다.';
$lang->image_always_reencode = '무조건 재인코딩';
$lang->about_image_always_reencode = '위에서 설정한 조건에 해당되지 않더라도 무조건 일정한 화질로 재인코딩하여 용량과 트래픽을 절약합니다.';
$lang->image_autoconv_gif2mp4 = 'GIF → MP4 변환';
$lang->about_image_autoconv_gif2mp4 = '움직이는 GIF 이미지를 MP4 동영상으로 변환하여 용량 및 트래픽을 절약합니다.<br />아래에서 ffmpeg 설정을 해야 하며, 구형 브라우저에서는 동영상이 재생되지 않을 수도 있습니다.';
$lang->max_video_size = '동영상 크기 제한';

View file

@ -129,6 +129,20 @@
<p class="x_text-info" cond="!function_exists('exif_read_data')">{$lang->msg_cannot_use_exif}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->image_always_reencode}</label>
<div class="x_controls">
<label for="image_always_reencode_Y" class="x_inline">
<input type="radio" name="image_always_reencode" id="image_always_reencode_Y" value="Y" checked="checked"|cond="$config->image_always_reencode === true" />
{$lang->cmd_yes}
</label>
<label for="image_always_reencode_N" class="x_inline">
<input type="radio" name="image_always_reencode" id="image_always_reencode_N" value="N" checked="checked"|cond="$config->image_always_reencode !== true" />
{$lang->cmd_no}
</label>
<p class="x_help-block">{$lang->about_image_always_reencode}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->image_autoconv_gif2mp4}</label>
<div class="x_controls">

View file

@ -296,11 +296,11 @@ class Member extends ModuleObject
// Add columns for IP address
if(!$oDB->isColumnExists("member", "ipaddress"))
{
$oDB->addColumn("member", "ipaddress", "varchar", 120, null, false, 'regdate');
$oDB->addColumn("member", "ipaddress", "varchar", 60, null, false, 'regdate');
}
if(!$oDB->isColumnExists("member", "last_login_ipaddress"))
{
$oDB->addColumn("member", "last_login_ipaddress", "varchar", 120, null, false, 'last_login');
$oDB->addColumn("member", "last_login_ipaddress", "varchar", 60, null, false, 'last_login');
}
if(!$oDB->isIndexExists("member","idx_ipaddress"))
{

View file

@ -36,7 +36,7 @@ $lang->point_group_ratchet = 'Change on Point Reduction';
$lang->point_group_ratchet_yes = 'Maintain current group if point is reduced';
$lang->point_group_ratchet_no = 'Move to lower group if point is reduced';
$lang->about_point_link_group = 'If you specify level for a specific group, users are assigned into the group when they advance to the level by getting points.';
$lang->about_module_point = 'You can set point for each module, and modules which don\'t have any value will use the default point. All points will be restored on acting reverse.';
$lang->about_module_point = 'You can set points for each module, and modules which don\'t have any value will use the default point settings.';
$lang->point_signup = 'Sign Up';
$lang->point_insert_document = 'Writing a post';
$lang->point_delete_document = 'Deleting a post';

View file

@ -21,7 +21,7 @@ $lang->cmd_exp_calc = 'Calcular';
$lang->cmd_exp_reset = 'Restablecer';
$lang->point_link_group = 'Grupo de cambio de nivel';
$lang->about_point_link_group = 'Si especifica nivel para un grupo específico, a los usuarios se les asigna en el grupo cuando se adavnce al nivel por conseguir puntos.';
$lang->about_module_point = 'Usted puede definir los puntos para cada módulo y los módulos que no tengan ningun valor usarán punto predefinido.<br />Todos los puntos serán restituidos al actuar en forma contraria.';
$lang->about_module_point = 'Usted puede definir los puntos para cada módulo y los módulos que no tengan ningun valor usarán punto predefinido.';
$lang->point_signup = 'Fecha del';
$lang->point_insert_document = 'Al escribir documento';
$lang->point_delete_document = 'Al borrar documento';

View file

@ -20,7 +20,7 @@ $lang->cmd_point_recal = 'Restaurer le Point';
$lang->about_cmd_point_recal = 'Tous les points seront recalculés basé seulement sur les points des articles/commentaires/annexes/inscription.<br />Après la restauration, Les membres gagneront le point d\'inscription seulement quand il fait de l\'activité dans le site Web.<br />Utilisez cette fonction seulement quand l\'initialisation complète est necessaire comme le cas de transfert des données etc.';
$lang->point_link_group = 'Changement du Groupe lié à celui du Niveau';
$lang->about_point_link_group = 'Si vous designez un niveau à un groupe particulier, les utilisateur s sont assignés dans le groupe quand ils s\'avancent au groupe en gagnant des points.';
$lang->about_module_point = 'Vous pouvez configurer les points pour chaque module. Le module qui n\'a pas de valeurs utilisera les points par défaut.<br />Tous les points seront restaurés quand on fait de l\'action inverse.';
$lang->about_module_point = 'Vous pouvez configurer les points pour chaque module. Le module qui n\'a pas de valeurs utilisera les points par défaut.';
$lang->point_signup = 'Inscription';
$lang->point_insert_document = 'Écrire';
$lang->point_delete_document = 'Supprimer';

View file

@ -26,7 +26,7 @@ $lang->point_link_group = 'グループ連動';
$lang->point_group_reset_and_add = '設定されたグループを初期化後に新規グループに付与';
$lang->point_group_add_only = '新規グループのみ付与';
$lang->about_point_link_group = 'グループにレベルを指定すると、該当レベルになったときにグループが変更されます。';
$lang->about_module_point = 'モジュール別にポイントを指定することができますが、指定されていないモジュールには、デフォルトポイントが使用されます。すべてのポイントは、反対の行動の行った場合に元に戻ります。';
$lang->about_module_point = 'モジュール別にポイントを指定することができますが、指定されていないモジュールには、デフォルトポイントが使用されます。';
$lang->point_signup = '加入';
$lang->point_insert_document = '書き込み作成';
$lang->point_delete_document = '書き込み削除';

View file

@ -36,7 +36,7 @@ $lang->point_group_ratchet = '포인트 감소 처리 방식';
$lang->point_group_ratchet_yes = '포인트가 감소하더라도 기존 그룹을 유지';
$lang->point_group_ratchet_no = '포인트가 감소하면 하위 그룹으로 이동';
$lang->about_point_link_group = '그룹에 원하는 레벨을 지정하면, 회원의 포인트가 해당 레벨의 포인트에 도달할 때 그룹이 변경됩니다.';
$lang->about_module_point = '모듈별 포인트를 지정할 수 있으며 지정되지 않은 모듈은 기본 포인트를 이용합니다. 모든 점수는 반대 행동을 하였을 경우 원상복구 됩니다.';
$lang->about_module_point = '모듈별 포인트를 설정할 수 있습니다. 설정하지 않은 모듈은 기본 포인트 설정을 적용합니다.';
$lang->point_signup = '가입';
$lang->point_insert_document = '글 작성';
$lang->point_delete_document = '글 삭제';

View file

@ -15,7 +15,7 @@ $lang->level_point = 'Уровень поинтов';
$lang->about_level_point = 'Уровень будет изменен, когда поинты достигают каждого уровня поинтов или падают ниже его';
$lang->disable_download = 'Запретить скачивание';
$lang->about_disable_download = 'Это запретит скачивание файлов, когда не хватает достаточного кол-ва поинтов. (За исключением файлов изображений)';
$lang->about_module_point = 'Вы можете установть поинты для каждого модуля, а модули, не имеющие значения будут использовать значение по умолчанию для поинтов.<br />Все поинты будут восстановлены при обратном действии.';
$lang->about_module_point = 'Вы можете установть поинты для каждого модуля, а модули, не имеющие значения будут использовать значение по умолчанию для поинтов.';
$lang->point_signup = 'Присвоить';
$lang->point_insert_document = 'При написании';
$lang->point_delete_document = 'При удалении';

View file

@ -27,7 +27,7 @@ $lang->point_link_group = 'Seviyeye Göre Grup Değiştirme';
$lang->point_group_reset_and_add = 'Düzenlenmiş grupları sıfırla ve yeni gruplar ekle';
$lang->point_group_add_only = 'Sadece yeni gruplara';
$lang->about_point_link_group = 'Belirli bir grup için seviye belirliyorsanız, kullanıcılar gruba o seviyenin puanına eriştiklerinde atanacaklardır.';
$lang->about_module_point = 'Her modül için puan ayarlayabilirsiniz. Hiçbir değer atanmayan modüller varsayılan puan sistemini kullanacaktır.<br />Tersi hareket durumunda tüm puanlar iade edilecektir.';
$lang->about_module_point = 'Her modül için puan ayarlayabilirsiniz. Hiçbir değer atanmayan modüller varsayılan puan sistemini kullanacaktır.';
$lang->point_signup = 'Kayıt Olmaya';
$lang->point_insert_document = 'Yazıya';
$lang->point_delete_document = 'Silmeye';

View file

@ -27,7 +27,7 @@ $lang->point_link_group = 'Chuyển nhóm với cấp độ';
$lang->point_group_reset_and_add = 'Điểm số để thăng cấp cho nhóm mới.';
$lang->point_group_add_only = 'Chỉ cấp cho nhóm mới';
$lang->about_point_link_group = 'Nếu bạn đặt cấp độ cho một nhóm đặc biệt nào đó, người sử dụng trong nhóm đó khi đạt đến số điểm giới hạn sẽ tự động được chuyển sang nhóm mới.';
$lang->about_module_point = 'Bạn có thể đặt thang điểm riêng cho mỗi Module, Module nào không được đặt sẽ sử dụng sự thiết lập mặc định.<br />Tất cả điểm sẽ khác khi sử dụng chức năng này.';
$lang->about_module_point = 'Bạn có thể đặt thang điểm riêng cho mỗi Module, Module nào không được đặt sẽ sử dụng sự thiết lập mặc định.';
$lang->point_signup = 'Khi đăng kí';
$lang->point_insert_document = 'Khi gửi bài';
$lang->point_delete_document = 'Khi xóa bài';

View file

@ -25,7 +25,7 @@ $lang->point_link_group = '用户组绑定';
$lang->point_group_reset_and_add = '初始化已有用户组重新设置';
$lang->point_group_add_only = '只应用到新用户组';
$lang->about_point_link_group = '即级别绑定用户组。当级别达到指定级别时,会员所属用户组将自动更新为与其相对应的用户组。';
$lang->about_module_point = '可以分别对各模块进行积分设置,没有被设置的模块将使用默认值。<br />所有积分在相反动作下恢复原始值。即发表新帖后再删除得到的积分为0分。';
$lang->about_module_point = '可以分别对各模块进行积分设置,没有被设置的模块将使用默认值。';
$lang->point_signup = '注册';
$lang->point_insert_document = '发表新帖';
$lang->point_delete_document = '删除主题';

View file

@ -25,7 +25,7 @@ $lang->point_link_group = '自動升級';
$lang->point_group_reset_and_add = '重新調整與新增群組';
$lang->point_group_add_only = '只限新群組';
$lang->about_point_link_group = '即群組隨等級變化。當等級達到指定等級時,會員所屬群組將自動更新成相對應的群組。';
$lang->about_module_point = '可以分別對各模組進行點數設置,沒有設置的模組將使用預設值。<br />所有動作在反向操作下將恢復原始值。即:發表主題後再刪除得到的點數為零。';
$lang->about_module_point = '可以分別對各模組進行點數設置,沒有設置的模組將使用預設值。';
$lang->point_signup = '註冊';
$lang->point_insert_document = '發表主題';
$lang->point_delete_document = '刪除主題';

View file

@ -58,10 +58,9 @@ class WidgetModel extends Widget
$widget = $searched_list[$i];
// Wanted information on the Widget
$widget_info = self::getWidgetInfo($widget);
if (!$widget_info)
{
$widget_info = new stdClass();
continue;
}
// get easyinstall remove url
@ -101,9 +100,11 @@ class WidgetModel extends Widget
$widgetStyle = $searched_list[$i];
// Wanted information on the Widget
$widgetStyle_info = self::getWidgetStyleInfo($widgetStyle);
if ($widgetStyle_info)
{
$list[] = $widgetStyle_info;
}
}
return $list;
}

View file

@ -106,10 +106,13 @@ class SecurityTest extends \Codeception\Test\Unit
{
$error_reporting = error_reporting(0);
config('security.check_csrf_token', true);
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['HTTP_REFERER'] = '';
$_SERVER['HTTP_X_CSRF_TOKEN'] = '';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_X_CSRF_TOKEN'] = Rhymix\Framework\Session::createToken();
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
@ -117,6 +120,7 @@ class SecurityTest extends \Codeception\Test\Unit
$_SERVER['HTTP_REFERER'] = '';
$_SERVER['HTTP_X_CSRF_TOKEN'] = '';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_X_CSRF_TOKEN'] = Rhymix\Framework\Session::createToken();
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
@ -124,27 +128,63 @@ class SecurityTest extends \Codeception\Test\Unit
$_SERVER['HTTP_X_CSRF_TOKEN'] = '';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_REFERER'] = 'http://www.rhymix.org/foo/bar';
$_SERVER['HTTP_X_CSRF_TOKEN'] = '';
$_SERVER['HTTP_X_CSRF_TOKEN'] = Rhymix\Framework\Session::createToken();
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_X_CSRF_TOKEN'] = 'invalid value';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_ORIGIN'] = 'http://www.rhymix.org';
$_SERVER['HTTP_REFERER'] = 'http://www.foobar.com';
$_SERVER['HTTP_X_CSRF_TOKEN'] = '';
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_REFERER'] = '';
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_ORIGIN'] = 'http://www.foobar.com';
$_SERVER['HTTP_X_CSRF_TOKEN'] = '0';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
config('security.check_csrf_token', false);
unset($_SERVER['HTTP_X_CSRF_TOKEN']);
$_SERVER['HTTP_REFERER'] = 'https://www.rhymix.org/foo/bar';
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_ORIGIN'] = 'https://www.rhymix.org';
$_SERVER['HTTP_REFERER'] = 'https://www.foobar.com';
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_ORIGIN'] = 'https://www.foobar.com';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_SEC_FETCH_SITE'] = 'same-origin';
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_SEC_FETCH_SITE'] = 'none';
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_SEC_FETCH_SITE'] = 'invalid value';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
unset($_SERVER['HTTP_SEC_FETCH_SITE']);
$_SERVER['HTTP_ORIGIN'] = '';
$_SERVER['HTTP_REFERER'] = '';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_ORIGIN'] = 'null';
$_SERVER['HTTP_REFERER'] = '';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_REFERER'] = '';
$_SERVER['HTTP_X_CSRF_TOKEN'] = '';
$this->assertTrue(Rhymix\Framework\Security::checkCSRF('http://www.rhymix.org/'));
$_SERVER['HTTP_ORIGIN'] = 'null';
$_SERVER['HTTP_REFERER'] = 'https://www.rhymix.org';
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_ORIGIN'] = '';
$_SERVER['HTTP_REFERER'] = 'null';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
$_SERVER['HTTP_ORIGIN'] = '';
$_SERVER['HTTP_REFERER'] = '';
$this->assertTrue(Rhymix\Framework\Security::checkCSRF('https://www.rhymix.org/'));
$_SERVER['HTTP_SEC_FETCH_SITE'] = 'cross-site';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF('https://www.rhymix.org/'));
unset($_SERVER['HTTP_SEC_FETCH_SITE']);
error_reporting($error_reporting);
}