diff --git a/classes/display/HTMLDisplayHandler.php b/classes/display/HTMLDisplayHandler.php index 0246c3a87..c0cc31069 100644 --- a/classes/display/HTMLDisplayHandler.php +++ b/classes/display/HTMLDisplayHandler.php @@ -757,13 +757,20 @@ class HTMLDisplayHandler 'plugins/cookie/js.cookie.min.js', 'plugins/blankshield/blankshield.min.js', 'plugins/uri/URI.min.js', - 'x.js', - 'common.js', - 'js_app.js', - 'xml_handler.js', - 'xml_js_filter.js', ); + if (str_contains($_SERVER['HTTP_USER_AGENT'] ?? '', 'Trident/')) + { + $original_file_list[] = 'polyfills/formdata.min.js'; + $original_file_list[] = 'polyfills/promise.min.js'; + } + + $original_file_list[] = 'x.js'; + $original_file_list[] = 'common.js'; + $original_file_list[] = 'js_app.js'; + $original_file_list[] = 'xml_handler.js'; + $original_file_list[] = 'xml_js_filter.js'; + if(config('view.minify_scripts') === 'none') { Context::loadFile(array('./common/js/jquery-' . $jquery_version . '.js', 'head', '', -1800000000), true); diff --git a/classes/frontendfile/FrontEndFileHandler.class.php b/classes/frontendfile/FrontEndFileHandler.class.php index a530ee3ce..034d190aa 100644 --- a/classes/frontendfile/FrontEndFileHandler.class.php +++ b/classes/frontendfile/FrontEndFileHandler.class.php @@ -430,6 +430,10 @@ class FrontEndFileHandler extends Handler public function unloadFile($fileName, $unused = '', $media = 'all') { $file = $this->getFileInfo($fileName, $unused, $media); + if (!isset($file->key)) + { + return; + } if($file->fileExtension == 'css') { diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index a736136f4..0d5b4d3d8 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -254,15 +254,15 @@ class ModuleObject extends BaseObject // Get privileges(granted) information for target module by of module.xml if(($permission = $this->xml_info->action->{$this->act}->permission) && $permission->check_var) { - // Check parameter - if(empty($check_module_srl = trim(Context::get($permission->check_var)))) + // Ensure that the list of modules to check is the right type and not empty + $check_var = Context::get($permission->check_var); + if (is_scalar($check_var)) { - return false; - } + if (empty($check_module_srl = trim($check_var))) + { + return false; + } - // If value is not array - if(!is_array($check_module_srl)) - { // Convert string to array. delimiter is ,(comma) or |@| if(preg_match('/,|\|@\|/', $check_module_srl, $delimiter) && $delimiter[0]) { @@ -273,6 +273,14 @@ class ModuleObject extends BaseObject $check_module_srl = array($check_module_srl); } } + else + { + $check_module_srl = array_map('trim', $check_var); + if (!count($check_var)) + { + return false; + } + } // Check permission by privileges(granted) information for target module foreach($check_module_srl as $target_srl) @@ -295,7 +303,15 @@ class ModuleObject extends BaseObject } // Check permission based on the grant information for the current module. - $grant = ModuleModel::getInstance()->getGrant($this->module_info, $this->user, $this->xml_info); + if (isset($check_grant)) + { + $grant = $check_grant; + } + else + { + $grant = ModuleModel::getInstance()->getGrant($this->module_info, $this->user, $this->xml_info); + } + if(!$this->checkPermission($grant, $this->user, $failed_requirement)) { $this->stop($this->_generatePermissionError($failed_requirement)); diff --git a/common/constants.php b/common/constants.php index 3589e734d..fde0b485f 100644 --- a/common/constants.php +++ b/common/constants.php @@ -3,7 +3,7 @@ /** * RX_VERSION is the version number of the Rhymix CMS. */ -define('RX_VERSION', '2.1.23'); +define('RX_VERSION', '2.1.25'); /** * RX_MICROTIME is the startup time of the current script, in microseconds since the Unix epoch. diff --git a/common/framework/parsers/template/TemplateParser_v2.php b/common/framework/parsers/template/TemplateParser_v2.php index 501845a2a..399e45d97 100644 --- a/common/framework/parsers/template/TemplateParser_v2.php +++ b/common/framework/parsers/template/TemplateParser_v2.php @@ -185,7 +185,7 @@ class TemplateParser_v2 }, $content); // Inline scripts. - $content = preg_replace_callback('#(?<=\s)(href="javascript:|on[a-z]+=")([^"]*?)"#i', function($match) { + $content = preg_replace_callback('#(?<=\s)(href="javascript:|pattern="|on[a-z]+=")([^"]*?)"#i', function($match) { return $match[1] . 'config->context = \'JS\'; ?>' . $match[2] . 'config->context = \'HTML\'; ?>"'; }, $content); diff --git a/common/js/common.js b/common/js/common.js index 9033e49e2..728085456 100644 --- a/common/js/common.js +++ b/common/js/common.js @@ -262,14 +262,37 @@ Rhymix.isSameHost = function(url) { * Redirect to a URL, but reload instead if the target is the same as the current page * * @param string url + * @param int delay * @return void */ -Rhymix.redirectToUrl = function(url) { - if (this.isCurrentUrl(url)) { - window.location.href = url; - window.location.reload(); +Rhymix.redirectToUrl = function(url, delay) { + const callback = function() { + if (Rhymix.isCurrentUrl(url)) { + window.location.href = url; + window.location.reload(); + } else { + window.location.href = url; + } + }; + if (delay) { + this.pendingRedirect = setTimeout(callback, delay); } else { - window.location.href = url; + callback(); + } +}; + +/** + * Cancel any pending redirect + * + * @return bool + */ +Rhymix.cancelPendingRedirect = function() { + if (this.pendingRedirect) { + clearTimeout(this.pendingRedirect); + this.pendingRedirect = null; + return true; + } else { + return false; } }; @@ -398,16 +421,26 @@ Rhymix.modal.close = function(id) { * * @param string action * @param object params - * @param function success - * @param function error - * @return void + * @param function callback_success + * @param function callback_error + * @return Promise */ -Rhymix.ajax = function(action, params, success, error) { +Rhymix.ajax = function(action, params, callback_success, callback_error) { // Extract module and act let isFormData = params instanceof FormData; - let module, act; - if (!action) { + let module, act, url, promise; + if (action) { + if (typeof action === 'string' && action.match(/^[a-z0-9_]+\.[a-z0-9_]+$/i)) { + let parts = action.split('.'); + params = params || {}; + params.module = module = parts[0]; + params.act = act = parts[1]; + } else { + url = action; + action = null; + } + } else { if (isFormData) { module = params.get('module'); act = params.get('act'); @@ -421,26 +454,22 @@ Rhymix.ajax = function(action, params, success, error) { } else { action = null; } - } else { - action = action.split('.'); - params = params || {}; - params.module = module = action[0]; - params.act = act = action[1]; - action = action.join('.'); } // Add action to URL if the current rewrite level supports it - let url = this.URI(window.request_uri).pathname() + 'index.php'; - if (act) { - url = url + '?act=' + act; + if (!url) { + url = this.URI(window.request_uri).pathname() + 'index.php'; + if (act) { + url = url + '?act=' + act; + } + /* + if (this.getRewriteLevel() >= 2 && action !== null) { + url = url + '_' + action.replace('.', '/'); + } else { + url = url + 'index.php'; + } + */ } - /* - if (this.getRewriteLevel() >= 2 && action !== null) { - url = url + action.replace('.', '/'); - } else { - url = url + 'index.php'; - } - */ // Add a CSRF token to the header, and remove it from the parameters const headers = { @@ -453,175 +482,137 @@ Rhymix.ajax = function(action, params, success, error) { delete params._rx_csrf_token; } - // Generate AJAX parameters - const args = { - type: 'POST', - dataType: 'json', - url: url, - data: isFormData ? params : JSON.stringify(params), - contentType: isFormData ? false : 'application/json; charset=UTF-8', - processData: false, - headers: headers, - success: function(data, textStatus, xhr) { - Rhymix._ajaxSuccessHandler(xhr, textStatus, action, data, params, success, error); - }, - error: function(xhr, textStatus, errorThrown) { - Rhymix._ajaxErrorHandler(xhr, textStatus, action, url, params, success, error); - } - }; + // Create and return a Promise for this AJAX request + return promise = new Promise(function(resolve, reject) { - // Send the AJAX request - try { - $.ajax(args); - } catch(e) { - alert(e); - } -}; + // Define the success wrapper. + const successWrapper = function(data, textStatus, xhr) { -/** - * Default success handler for AJAX requests - * - * @param object xhr - * @param string textStatus - * @param string action - * @param object data - * @param object params - * @param function success - * @param function errror - * @return void - */ -Rhymix._ajaxSuccessHandler = function(xhr, textStatus, action, data, params, success, error) { - - // Add debug information. - if (data._rx_debug) { - data._rx_debug.page_title = "AJAX : " + action; - if (this.addDebugData) { - this.addDebugData(data._rx_debug); - } else { - this.pendingDebugData.push(data._rx_debug); - } - } - - // If the response contains a Rhymix error code, display the error message. - if (typeof data.error !== 'undefined' && data.error != 0) { - - // If an error callback is defined, call it. Abort if it returns false. - if ($.isFunction(error) && error(data, xhr) === false) { - return; - } - - // If an error message was supplied, display it. - if (data.message) { - let msg = data.message.replace(/\\n/g, "\n"); - if (data.errorDetail) { - msg += "\n\n" + data.errorDetail; + // Add debug information. + if (data._rx_debug) { + data._rx_debug.page_title = "AJAX : " + action; + if (Rhymix.addDebugData) { + Rhymix.addDebugData(data._rx_debug); + } else { + Rhymix.pendingDebugData.push(data._rx_debug); + } } - alert(msg); - return; - } - // Rhymix should never return an error code without a message, but if someone does, we handle it here. - let msg = 'AJAX error: ' + (action || 'form submission') + "\n\n" + xhr.responseText; - if (msg.length > 1000) { - msg = msg.substring(0, 1000) + '...'; - } - console.error(msg.trim().replace(/\n+/g, "\n")); - if (this.showAjaxErrors.indexOf('ALL') >= 0 || this.showAjaxErrors.indexOf(xhr.status) >= 0) { - alert(msg.trim()); - } - return; - } + // If the response contains a Rhymix error code, display the error message. + if (typeof data.error !== 'undefined' && data.error != 0) { + return errorWrapper(data, textStatus, xhr); + } - // If a success callback was defined, call it. - if ($.isFunction(success)) { - success(data, xhr); - return; - } + // If a success callback was defined, call it. + if (typeof callback_success === 'function') { + callback_success(data, xhr); + resolve(data); + return; + } - // If the response contains a redirect URL, follow the redirect. - if (data.redirect_url) { - this.redirectToUrl(data.redirect_url.replace(/&/g, '&')); - return; - } -}; + // If the response contains a redirect URL, follow the redirect. + // This can be canceled by Rhymix.cancelPendingRedirect() within 100 milliseconds. + if (data.redirect_url) { + Rhymix.redirectToUrl(data.redirect_url.replace(/&/g, '&'), 100); + } -/** - * Default error handler for AJAX requests - * - * @param object xhr - * @param string textStatus - * @param string action - * @param string url - * @param object params - * @param function success - * @param function errror - * @return void - */ -Rhymix._ajaxErrorHandler = function(xhr, textStatus, action, url, params, success, error) { + // Resolve the promise with the response data. + resolve(data); + }; - // If the user is navigating away, don't do anything. - if (xhr.status == 0 && this.unloading) { - return; - } + // Define the error wrapper. + const errorWrapper = function(data, textStatus, xhr) { - // If the response contains valid JSON, call the success callback instead. - if (xhr.status >= 400 && xhr.responseText) { - let data; - try { - data = JSON.parse(xhr.responseText); - } catch (e) { } - if (data && typeof data.error !== 'undefined') { - this._ajaxSuccessHandler(xhr, textStatus, action, data, params, success, error); - return; - } - } + // If an error callback is defined, call it. + // The promise will still be rejected, but silently. + if (typeof callback_error === 'function') { + callback_error(data, xhr); + promise.catch(function(dummy) { }); + let dummy = new Error('Rhymix.ajax() error already handled by callback function'); + dummy._rx_ajax_error = true; + dummy.cause = data; + dummy.details = ''; + dummy.xhr = xhr; + reject(dummy); + return; + } - // If an error callback is defined, call it. Abort if it returns false. - if ($.isFunction(error)) { - let fakedata = { error: -3, message: textStatus }; - if (error(fakedata, xhr) === false) { - return; - } - } + // Otherwise, generate a generic error message. + let error_message = 'AJAX error: ' + (action || 'form submission'); + let error_details = ''; + if (data.error != 0 && data.message) { + error_message = data.message.replace(/\\n/g, "\n"); + if (data.errorDetail) { + error_details = data.errorDetail; + } + } else if (xhr.status == 0) { + error_details = 'Connection failed: ' + url + "\n\n" + (xhr.responseText || ''); + } else { + error_details = (xhr.responseText || ''); + } + if (error_details.length > 1000) { + error_details = error_details.substring(0, 1000) + '...'; + } - // Otherwise, generate a simple error message. - let error_info, msg; - if (xhr.status == 0) { - error_info = 'Connection failed: ' + url + "\n\n" + (xhr.responseText || ''); - } else { - error_info = 'Response code: ' + xhr.status + "\n\n" + (xhr.responseText || ''); - } - msg = 'AJAX error: ' + (action || 'form submission') + "\n\n" + error_info; - if (msg.length > 1000) { - msg = msg.substring(0, 1000) + '...'; - } + // Reject the promise with an error object. + // If uncaught, this will be handled by the 'unhandledrejection' event listener. + const err = new Error(error_message); + err._rx_ajax_error = true; + err.cause = data; + err.details = error_details; + err.xhr = xhr; + reject(err); + }; - // Print the error message. - console.error(msg.trim().replace(/\n+/g, "\n")); - if (this.showAjaxErrors.indexOf('ALL') >= 0 || this.showAjaxErrors.indexOf(xhr.status) >= 0) { - alert(msg.trim()); - } + // Pass off to jQuery with another wrapper around the success and error wrappers. + // This allows us to handle HTTP 400+ error codes with valid JSON responses. + $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + data: isFormData ? params : JSON.stringify(params), + contentType: isFormData ? false : 'application/json; charset=UTF-8', + processData: false, + headers: headers, + success: successWrapper, + error: function(xhr, textStatus, errorThrown) { + if (xhr.status == 0 && Rhymix.unloading) { + return; + } + if (xhr.status >= 400 && xhr.responseText) { + try { + let data = JSON.parse(xhr.responseText); + if (data) { + successWrapper(data, textStatus, xhr); + return; + } + } catch (e) { } + } + errorWrapper({ error: 0, message: textStatus }, textStatus, xhr); + } + }); + }); }; /** * Submit a form using AJAX instead of navigating away * * @param HTMLElement form - * @param function success - * @param function error + * @param function callback_success + * @param function callback_error * @return void */ -Rhymix.ajaxForm = function(form, success, error) { +Rhymix.ajaxForm = function(form, callback_success, callback_error) { const $form = $(form); // Get success and error callback functions. - if (typeof success === 'undefined') { - success = $form.data('callbackSuccess'); - if (success && $.isFunction(success)) { + if (typeof callback_success === 'undefined') { + callback_success = $form.data('callbackSuccess'); + if (callback_success && typeof callback_success === 'function') { // no-op - } else if (success && window[success] && $.isFunction(window[success])) { - success = window[success]; + } else if (callback_success && window[callback_success] && typeof window[callback_success] === 'function') { + callback_success = window[callback_success]; } else { - success = function(data) { + callback_success = function(data) { if (data.message && data.message !== 'success') { alert(data.message); } @@ -631,17 +622,17 @@ Rhymix.ajaxForm = function(form, success, error) { }; } } - if (typeof error === 'undefined') { - error = $form.data('callbackError'); - if (error && $.isFunction(error)) { + if (typeof callback_error === 'undefined') { + callback_error = $form.data('callbackError'); + if (callback_error && typeof callback_error === 'function') { // no-op - } else if (error && window[error] && $.isFunction(window[error])) { - error = window[error]; + } else if (callback_error && window[callback_error] && typeof window[callback_error] === 'function') { + callback_error = window[callback_error]; } else { - error = null; + callback_error = null; } } - this.ajax(null, new FormData($form[0]), success, error); + this.ajax(null, new FormData($form[0]), callback_success, callback_error); }; /** @@ -653,7 +644,7 @@ Rhymix.ajaxForm = function(form, success, error) { * @return void */ Rhymix.checkboxToggleAll = function(name) { - if (!window[name]) { + if (typeof name === 'undefined') { name='cart'; } let options = { @@ -1012,7 +1003,7 @@ $(function() { e.preventDefault(); e.stopPropagation(); - if (window[Rhymix.loadedPopupMenus[params.menu_id]]) { + if (Rhymix.loadedPopupMenus[params.menu_id]) { return Rhymix.displayPopupMenu(params, response_tags, params); } @@ -1101,6 +1092,20 @@ window.addEventListener('beforeunload', function() { Rhymix.unloading = true; }); +// General handler for unhandled Promise rejections +window.addEventListener('unhandledrejection', function(event) { + if (event.reason && typeof event.reason['_rx_ajax_error'] === 'boolean') { + event.preventDefault(); + const error_message = event.reason.message.trim(); + const error_details = event.reason.details || ''; + const error_xhr = event.reason.xhr || {}; + console.error(error_message.replace(/\n+/g, "\n" + error_details)); + if (Rhymix.showAjaxErrors.indexOf('ALL') >= 0 || Rhymix.showAjaxErrors.indexOf(error_xhr.status) >= 0) { + alert(error_message.trim() + (error_details ? ("\n\n" + error_details) : '')); + } + } +}); + // General handler for popstate events window.addEventListener('popstate', function(event) { // Close modal if it is open @@ -1122,6 +1127,15 @@ window.addEventListener('popstate', function(event) { } }); +// Fix for browsers that don't support the unhandledrejection event +if (typeof Promise._unhandledRejectionFn !== 'undefined') { + Promise._unhandledRejectionFn = function(error) { + if (error['_rx_ajax_error']) { + alert(error.message.trim()); + } + }; +} + /** * ================= * jQuery extensions @@ -1457,12 +1471,16 @@ function popopen(url, target) { * @return void */ function doAddDocumentCart(obj) { - Rhymix.addedDocument.push(obj.value); + if (obj && obj.value) { + Rhymix.addedDocument.push(obj.value); + } setTimeout(function() { - exec_json('document.procDocumentAddCart', { - srls: Rhymix.addedDocument.join(',') - }); - Rhymix.addedDocument = []; + if (Rhymix.addedDocument.length > 0) { + exec_json('document.procDocumentAddCart', { + srls: Rhymix.addedDocument + }); + Rhymix.addedDocument = []; + } }, 100); } diff --git a/common/js/polyfills/formdata.min.js b/common/js/polyfills/formdata.min.js new file mode 100644 index 000000000..f4abd6765 --- /dev/null +++ b/common/js/polyfills/formdata.min.js @@ -0,0 +1,21 @@ +/*! formdata-polyfill. MIT License. Jimmy W?rting */ +;(function(){var h;function l(a){var b=0;return function(){return b>>0)+"_",e=0;return b}); +r("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var b="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),c=0;cf;f++)r(f,o[f])})}function n(e,t){this.name="AggregateError",this.errors=e,this.message=t||""}function r(e){var t=this;return new t(function(r,o){if(!e||"undefined"==typeof e.length)return o(new TypeError("Promise.any accepts an array"));var i=Array.prototype.slice.call(e);if(0===i.length)return o();for(var f=[],u=0;i.length>u;u++)try{t.resolve(i[u]).then(r)["catch"](function(e){f.push(e),f.length===i.length&&o(new n(f,"All promises were rejected"))})}catch(c){o(c)}})}function o(e){return!(!e||"undefined"==typeof e.length)}function i(){}function f(e){if(!(this instanceof f))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=undefined,this._deferreds=[],s(e,this)}function u(e,t){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,f._immediateFn(function(){var n=1===e._state?t.onFulfilled:t.onRejected;if(null!==n){var r;try{r=n(e._value)}catch(o){return void a(t.promise,o)}c(t.promise,r)}else(1===e._state?c:a)(t.promise,e._value)})):e._deferreds.push(t)}function c(e,t){try{if(t===e)throw new TypeError("A promise cannot be resolved with itself.");if(t&&("object"==typeof t||"function"==typeof t)){var n=t.then;if(t instanceof f)return e._state=3,e._value=t,void l(e);if("function"==typeof n)return void s(function(e,t){return function(){e.apply(t,arguments)}}(n,t),e)}e._state=1,e._value=t,l(e)}catch(r){a(e,r)}}function a(e,t){e._state=2,e._value=t,l(e)}function l(e){2===e._state&&0===e._deferreds.length&&f._immediateFn(function(){e._handled||f._unhandledRejectionFn(e._value)});for(var t=0,n=e._deferreds.length;n>t;t++)u(e,e._deferreds[t]);e._deferreds=null}function s(e,t){var n=!1;try{e(function(e){n||(n=!0,c(t,e))},function(e){n||(n=!0,a(t,e))})}catch(r){if(n)return;n=!0,a(t,r)}}n.prototype=Error.prototype;var d=setTimeout;f.prototype["catch"]=function(e){return this.then(null,e)},f.prototype.then=function(e,t){var n=new this.constructor(i);return u(this,new function(e,t,n){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof t?t:null,this.promise=n}(e,t,n)),n},f.prototype["finally"]=e,f.all=function(e){return new f(function(t,n){function r(e,o){try{if(o&&("object"==typeof o||"function"==typeof o)){var u=o.then;if("function"==typeof u)return void u.call(o,function(t){r(e,t)},n)}i[e]=o,0==--f&&t(i)}catch(c){n(c)}}if(!o(e))return n(new TypeError("Promise.all accepts an array"));var i=Array.prototype.slice.call(e);if(0===i.length)return t([]);for(var f=i.length,u=0;i.length>u;u++)r(u,i[u])})},f.any=r,f.allSettled=t,f.resolve=function(e){return e&&"object"==typeof e&&e.constructor===f?e:new f(function(t){t(e)})},f.reject=function(e){return new f(function(t,n){n(e)})},f.race=function(e){return new f(function(t,n){if(!o(e))return n(new TypeError("Promise.race accepts an array"));for(var r=0,i=e.length;i>r;r++)f.resolve(e[r]).then(t,n)})},f._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){d(e,0)},f._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var p=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"function"!=typeof p.Promise?p.Promise=f:(p.Promise.prototype["finally"]||(p.Promise.prototype["finally"]=e),p.Promise.allSettled||(p.Promise.allSettled=t),p.Promise.any||(p.Promise.any=r))}); diff --git a/common/js/xml_js_filter.js b/common/js/xml_js_filter.js index 83459d213..414c175d8 100644 --- a/common/js/xml_js_filter.js +++ b/common/js/xml_js_filter.js @@ -467,13 +467,13 @@ function legacy_filter(filter_name, form, module, act, callback, responses, conf }; if (!hasFile) { - Rhymix.ajax(module + '.' + act, params, callback_wrapper); + exec_json(module + '.' + act, params, callback_wrapper); } else { var fd = new FormData(); for (let key in params) { fd.append(key, params[key]); } - Rhymix.ajax(null, fd, callback_wrapper); + exec_json('raw', fd, callback_wrapper); } }; diff --git a/common/tpl/popup_layout.html b/common/tpl/popup_layout.html index 556864c29..5252377f9 100644 --- a/common/tpl/popup_layout.html +++ b/common/tpl/popup_layout.html @@ -11,7 +11,9 @@ const iframe_sequence = '{{ $iframe_sequence }}'; window.opener = window.parent; window.close = function() { - parent.closeModal('_rx_iframe_' + iframe_sequence); + setTimeout(function() { + parent.closeModal('_rx_iframe_' + iframe_sequence); + }, 100); }; '; $target = 'config->context = \'CSS\'; ?>> body { font-size: 16px; } config->context = \'HTML\'; ?>'; diff --git a/widgets/content/conf/info.xml b/widgets/content/conf/info.xml index ca28e37de..cfef3d17e 100644 --- a/widgets/content/conf/info.xml +++ b/widgets/content/conf/info.xml @@ -246,7 +246,7 @@ UL (list) - + 목록수 目录数 リスト数 @@ -266,7 +266,7 @@ 設置要顯示的目錄數。(預設是5個) Görüntülenecek yazıların sayısını ayarlayabilirsiniz. (varsayılan değer 5'tir) - + 가로 이미지 수 横並びイメージ数 横向图片数