/**
 * Created by Milos Roksandic on 12.1.16..
 */

(function (Handsontable, $) {
    /**
     * Escape HTML for fail-closed display when DOMParser is unavailable or parsing fails.
     */
    function wdtEscapeHtmlPlain(s) {
        return String(s == null ? '' : s)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');
    }

    /**
     * Mirrors WDTTools::isWpdtSafeWebUrl — only http(s) with acceptable host, or root-relative path (no protocol-relative //).
     */
    function wdtIsWpdtSafeWebUrl(url) {
        url = String(url == null ? '' : url).trim();
        if (!url) {
            return false;
        }
        if (url.indexOf('//') === 0) {
            return false;
        }
        if (url.charAt(0) === '/' && url.charAt(1) !== '/') {
            return true;
        }
        var lower = url.toLowerCase();
        if (lower.indexOf('http://') !== 0 && lower.indexOf('https://') !== 0) {
            return false;
        }
        try {
            var u = new URL(url);
            var h = (u.hostname || '').toLowerCase();
            if (!h) {
                return false;
            }
            if (h === 'localhost') {
                return true;
            }
            if (h.charAt(0) === '[' && h.charAt(h.length - 1) === ']') {
                return true;
            }
            if (h.indexOf('.') !== -1) {
                return true;
            }
            if (/^\d{1,3}(\.\d{1,3}){3}$/.test(h)) {
                return true;
            }
            return false;
        } catch (e) {
            return false;
        }
    }

    /**
     * Harden full cell URL for link/image renderers (same allowlist as PHP link/image sanitizers).
     */
    function wdtSanitizeExcelHref(url) {
        url = String(url == null ? '' : url).trim();
        if (!url) {
            return '';
        }
        if (/["'<>]/.test(url) || /[\u0000-\u001F\u007F]/.test(url)) {
            return '';
        }
        return wdtIsWpdtSafeWebUrl(url) ? url : '';
    }

    function wdtSanitizeExcelMailto(addr) {
        addr = String(addr == null ? '' : addr).trim();
        if (!addr || /["'<>]/.test(addr) || /[\u0000-\u001F\u007F]/.test(addr)) {
            return '';
        }
        return addr;
    }

    /**
     * Rich-text href: strip only scriptable schemes / protocol-relative URLs (mirrors PHP cellHtmlRichHrefMustStrip).
     */
    function wdtExcelRichCellHrefMustStrip(val) {
        val = String(val == null ? '' : val).trim();
        if (!val) {
            return true;
        }
        if (val.indexOf('//') === 0) {
            return true;
        }
        var m = val.match(/^([a-z][a-z0-9+.-]*):/i);
        if (!m) {
            return false;
        }
        var s = m[1].toLowerCase();
        if (s === 'javascript' || s === 'vbscript' || s === 'data' || s === 'file') {
            return true;
        }
        return false;
    }

    /**
     * href/src/poster/formaction in rich string cells — mirrors PHP sanitizeCellHtmlUrlAttribute:
     * src|poster|formaction: wdtIsWpdtSafeWebUrl; href: esc_url-style value kept unless scheme is dangerous.
     */
    function wdtSanitizeUrlAttrForExcelCell(val, attrName) {
        val = String(val == null ? '' : val).trim();
        attrName = String(attrName == null ? '' : attrName).toLowerCase();
        if (!val) {
            return '';
        }
        if (/^\s*(javascript|vbscript)\s*:/i.test(val) || /^\s*data\s*:\s*text\/html/i.test(val)) {
            return '';
        }
        if (/["'<>]/.test(val) || /[\u0000-\u001F\u007F]/.test(val)) {
            return '';
        }
        if (attrName === 'href') {
            return wdtExcelRichCellHrefMustStrip(val) ? '' : val;
        }
        if (attrName === 'src' || attrName === 'poster' || attrName === 'formaction') {
            return wdtIsWpdtSafeWebUrl(val) ? val : '';
        }
        return wdtIsWpdtSafeWebUrl(val) ? val : '';
    }

    /**
     * Strip dangerous attributes from allowed HTML in string cells (matches server stripJsAttributes intent).
     */
    function wdtSanitizeExcelRichCellHtml(html) {
        if (typeof html !== 'string' || html.indexOf('<') === -1) {
            return html;
        }
        if (typeof DOMParser === 'undefined') {
            return wdtEscapeHtmlPlain(html);
        }
        try {
            var doc = new DOMParser().parseFromString('<div data-wdt-excel-san="1">' + html + '</div>', 'text/html');
            var wrapper = doc.body && doc.body.firstElementChild;
            if (!wrapper) {
                return wdtEscapeHtmlPlain(html);
            }
            var urlAttrs = ['href', 'src', 'poster', 'formaction'];
            var nodes = wrapper.getElementsByTagName('*');
            var i, j, el, nm, v, safe;
            for (i = nodes.length - 1; i >= 0; i--) {
                el = nodes[i];
                for (j = el.attributes.length - 1; j >= 0; j--) {
                    nm = el.attributes[j].name;
                    if (/^on/i.test(nm)) {
                        el.removeAttribute(nm);
                    }
                }
                if (el.hasAttribute('srcdoc')) {
                    el.removeAttribute('srcdoc');
                }
            }
            nodes = wrapper.getElementsByTagName('*');
            for (i = 0; i < nodes.length; i++) {
                el = nodes[i];
                for (j = 0; j < urlAttrs.length; j++) {
                    nm = urlAttrs[j];
                    if (!el.hasAttribute(nm)) {
                        continue;
                    }
                    v = el.getAttribute(nm);
                    safe = wdtSanitizeUrlAttrForExcelCell(v, nm);
                    if (safe === '') {
                        el.removeAttribute(nm);
                    } else {
                        el.setAttribute(nm, safe);
                    }
                }
            }
            var anchors = wrapper.querySelectorAll('a');
            for (i = 0; i < anchors.length; i++) {
                el = anchors[i];
                if (!String(el.getAttribute('href') || '').trim()) {
                    while (el.firstChild) {
                        el.parentNode.insertBefore(el.firstChild, el);
                    }
                    if (el.parentNode) {
                        el.parentNode.removeChild(el);
                    }
                }
            }
            return wrapper.innerHTML;
        } catch (err) {
            return wdtEscapeHtmlPlain(html);
        }
    }

    //this is used for setting default values in new rows for every cell that has it.
    var baseBeginEditing = Handsontable.editors.BaseEditor.prototype.beginEditing;
    Handsontable.editors.BaseEditor.prototype.beginEditing = function (initialValue, event) {
        var settings = this.instance.getSettings();

        if (settings.wpDataTablesExcelPlugin) {
            if (typeof initialValue == 'undefined' && !settings.readOnly) {
                var idColProp = settings.idColumnKey;
                var idColVal = this.instance.getDataAtRowProp(this.row, idColProp);
                var defaultValue = this.cellProperties.defaultValue;

                if (!idColVal && defaultValue) {
                    initialValue = defaultValue;
                }
            }
        }

        baseBeginEditing.apply(this, arguments);
    };

    //this is fix for valid cells that had invalid background after cancelling edition
    var baseCancelChanges = Handsontable.editors.BaseEditor.prototype.cancelChanges;
    Handsontable.editors.BaseEditor.prototype.cancelChanges = function () {
        var settings = this.instance.getCellMeta(this.row, this.col);

        if (settings.wpDataTablesExcelPlugin) {
            //removing class that indicates that this editor has invalid value, also removing title tag because of tooltip.
            var $editorElement = $(document.activeElement).closest('#' + this.instance.rootElement.id + ' > div.invalid_editor_value')
                .removeClass('invalid_editor_value');

            //closing tooltip if previously not closed.
            if ($editorElement.length > 0 && $editorElement.next('div.tooltip:visible').length) {
                $editorElement.tooltip('destroy');
            }

            var validator = this.instance.getCellValidator(this.row, this.col);
            var value = this.instance.getDataAtCell(this.row, this.col);

            //getting cell validator and making sure that value is actually valid, and if so css class that marks table cell as invalid is removed.
            if (validator) {
                var isValid = false;
                if (typeof validator == 'function') {
                    validator.call(settings, value, function (val) {
                        return isValid = val;
                    });
                } else if (validator instanceof RegExp) {
                    isValid = validator.test(value);
                }

                if (isValid) {
                    var TD = this.instance.getCell(this.row, this.col);
                    var $td = $(TD);

                    var invalidCellClassName = settings.invalidCellClassName;

                    if ($td.hasClass(invalidCellClassName)) {
                        $td.removeClass(invalidCellClassName);
                    }
                }
            }
        }


        baseCancelChanges.apply(this, arguments);
    };

    /**
     * TEXT MULTILINE EDITOR DEFINITION.
     */
    function initWdtMultilineEditor() {
        var ExcelMultilineEditor = Handsontable.editors.TextEditor.prototype.extend();

        ExcelMultilineEditor.prototype.bindEvents = function () {
            //Preventing default editing behaviour when Enter Key pressed, allowing new line character.
            this.eventManager.addEventListener(this.TEXTAREA, 'keydown', function (event) {
                if (event.which == Handsontable.helper.KEY_CODES.ENTER) {
                    Handsontable.Dom.stopImmediatePropagation(event);
                }
            });
        };

        // Put editor in dedicated namespace
        Handsontable.editors.ExcelMultilineEditor = ExcelMultilineEditor;

        // Register alias
        Handsontable.editors.registerEditor('wdt.text_multiline', ExcelMultilineEditor);
    }

    /**
     * CUSTOM DATE EDITOR DEFINITION.
     */
    function initWdtDateEditor() {
        var ExcelDateEditor = Handsontable.editors.TextEditor.prototype.extend();

        ExcelDateEditor.prototype.init = function () {
            Handsontable.editors.TextEditor.prototype.init.apply(this, arguments);

            var table_settings = this.instance.getSettings();
            this.time_format = table_settings.timeFormat;
            var $__9 = this;
            this.instance.addHook('afterDestroy', (function () {
                $__9.parentDestroyed = true;
                $__9.destroyElements();
            }));
        };

        ExcelDateEditor.prototype.createElements = function () {
            Handsontable.editors.TextEditor.prototype.createElements.apply(this, arguments);
            // Create password input and update relevant properties
            this.TEXTAREA = document.createElement('input');
            this.TEXTAREA.className = 'wdt-datepicker';
            this.textareaStyle = this.TEXTAREA.style;
            this.textareaStyle.width = 0;
            this.textareaStyle.height = 0;

            // Replace textarea with password input
            Handsontable.Dom.empty(this.TEXTAREA_PARENT);
            this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
        };

        //this event is needed because of handsontable bug, when entered invalid data, escape was not calling finish editing function
        ExcelDateEditor.prototype.onBeforeKeyDown = function (event) {
            switch (event.keyCode) {
                case Handsontable.helper.KEY_CODES.ESCAPE:
                    var editor = this.getActiveEditor();
                    editor.finishEditing(true);
                    break;
                case Handsontable.helper.KEY_CODES.ARROW_DOWN:
                case Handsontable.helper.KEY_CODES.ARROW_UP:
                    break;
            }
        };

        ExcelDateEditor.prototype.beginEditing = function (e) {
            Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments);
        }

        //this is a fix for bug that when invalid time is tried to be saved, date picker was unable to appear.
        ExcelDateEditor.prototype.discardEditor = function (validationResult) {
            Handsontable.editors.TextEditor.prototype.discardEditor.apply(this, arguments);

            var hotInstance = this.instance;
            var allowInvalid = hotInstance.getCellMeta(this.row, this.col).allowInvalid;

            if (!allowInvalid && !validationResult) {
                if (!this.$timePicker.$node.is(':visible')) {
                    this.$timePicker.open();
                }
            }
        };

        ExcelDateEditor.prototype.open = function () {
            this.instance.addHook('beforeKeyDown', this.onBeforeKeyDown);
            Handsontable.editors.TextEditor.prototype.open.apply(this, arguments);
        };

        ExcelDateEditor.prototype.beginEditing = function () {
            Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments);
        }

        ExcelDateEditor.prototype.focus = function (event) {
            Handsontable.editors.DateEditor.prototype.focus.apply(this, arguments);
        }

        ExcelDateEditor.prototype.close = function () {
            this.instance.removeHook('beforeKeyDown', this.onBeforeKeyDown);
            Handsontable.editors.TextEditor.prototype.close.apply(this, arguments);
        };

        ExcelDateEditor.prototype.finishEditing = function () {
            var isCancelled = arguments[0] !== (void 0) ? arguments[0] : false;
            var ctrlDown = arguments[1] !== (void 0) ? arguments[1] : false;
            if (isCancelled) {
                var value = this.originalValue;
                if (value !== void 0) {
                    this.setValue(value);
                }
            }
            Handsontable.editors.TextEditor.prototype.finishEditing.apply(this, arguments);
        };

        ExcelDateEditor.prototype.prepare = function (row, col, prop, td, originalValue, cellProperties) {
            if (!originalValue || originalValue == '0000-00-00') {
                originalValue = '';
            }

            if (originalValue && !this.same_display_and_edit_format) {
                var table_settings = this.instance.getSettings();
                //values are converted from database format to desired display format
                originalValue = moment(originalValue, table_settings.dataSourceDateFormat).format(table_settings.displayDateFormat);
            }

            // Remember to invoke parent's method
            Handsontable.editors.DateEditor.prototype.prepare.apply(this, arguments);
        };

        ExcelDateEditor.prototype.saveValue = function (val, ctrlDown) {
            if (val[0][0] && !this.same_display_and_edit_format) {
                var table_settings = this.instance.getSettings();
                //values are converted from desired display format to database format in order to save properly
                val[0][0] = moment(val[0][0], table_settings.displayDateFormat).format(table_settings.dataSourceDateFormat);
            }

            // Remember to invoke parent's method
            Handsontable.editors.DateEditor.prototype.saveValue.apply(this, arguments);
        };

        // Put editor in dedicated namespace
        Handsontable.editors.ExcelTimeEditor = ExcelDateEditor;

        // Register alias
        Handsontable.editors.registerEditor('wdt.date', ExcelDateEditor);
    }

    /**
     * DATETIME EDITOR DEFINITION
     */
    function initWdtDateTimeEditor() {
        var ExcelDateTimeEditor = Handsontable.editors.TextEditor.prototype.extend();

        ExcelDateTimeEditor.prototype.init = function () {
            Handsontable.editors.TextEditor.prototype.init.apply(this, arguments);

            var table_settings = this.instance.getSettings();
            this.time_format = table_settings.timeFormat;
            var $__9 = this;
            this.instance.addHook('afterDestroy', (function () {
                $__9.parentDestroyed = true;
                $__9.destroyElements();
            }));
        };

        ExcelDateTimeEditor.prototype.createElements = function () {
            Handsontable.editors.TextEditor.prototype.createElements.apply(this, arguments);
            // Create password input and update relevant properties
            this.TEXTAREA = document.createElement('input');
            this.TEXTAREA.className = 'wdt-datetimepicker';
            this.textareaStyle = this.TEXTAREA.style;
            this.textareaStyle.width = 0;
            this.textareaStyle.height = 0;

            // Replace textarea with password input
            Handsontable.Dom.empty(this.TEXTAREA_PARENT);
            this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
        };

        //this event is needed because of handsontable bug, when entered invalid data, escape was not calling finish editing function
        ExcelDateTimeEditor.prototype.onBeforeKeyDown = function (event) {
            switch (event.keyCode) {
                case Handsontable.helper.KEY_CODES.ESCAPE:
                    var editor = this.getActiveEditor();
                    editor.finishEditing(true);
                    break;
                case Handsontable.helper.KEY_CODES.ARROW_DOWN:
                case Handsontable.helper.KEY_CODES.ARROW_UP:
                    break;
            }
        };

        ExcelDateTimeEditor.prototype.beginEditing = function (e) {
            Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments);
        }

        //this is a fix for bug that when invalid time is tried to be saved, date picker was unable to appear.
        ExcelDateTimeEditor.prototype.discardEditor = function (validationResult) {
            Handsontable.editors.TextEditor.prototype.discardEditor.apply(this, arguments);

            var hotInstance = this.instance;
            var allowInvalid = hotInstance.getCellMeta(this.row, this.col).allowInvalid;

            if (!allowInvalid && !validationResult) {
                if (!this.$timePicker.$node.is(':visible')) {
                    this.$timePicker.open();
                }
            }
        };

        ExcelDateTimeEditor.prototype.open = function () {
            this.instance.addHook('beforeKeyDown', this.onBeforeKeyDown);
            Handsontable.editors.TextEditor.prototype.open.apply(this, arguments);
        };

        ExcelDateTimeEditor.prototype.beginEditing = function () {
            Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments);
        }

        ExcelDateTimeEditor.prototype.focus = function (event) {
            Handsontable.editors.DateEditor.prototype.focus.apply(this, arguments);
        }

        ExcelDateTimeEditor.prototype.close = function () {
            this.instance.removeHook('beforeKeyDown', this.onBeforeKeyDown);
            Handsontable.editors.TextEditor.prototype.close.apply(this, arguments);
        };

        ExcelDateTimeEditor.prototype.finishEditing = function () {
            var isCancelled = arguments[0] !== (void 0) ? arguments[0] : false;
            var ctrlDown = arguments[1] !== (void 0) ? arguments[1] : false;
            if (isCancelled) {
                var value = this.originalValue;
                if (value !== void 0) {
                    this.setValue(value);
                }
            }
            Handsontable.editors.TextEditor.prototype.finishEditing.apply(this, arguments);
        };

        ExcelDateTimeEditor.prototype.prepare = function (row, col, prop, td, originalValue, cellProperties) {
            if (!originalValue || originalValue == '0000-00-00 00:00:00') {
                originalValue = '';
            }

            if (originalValue && !this.same_display_and_edit_format) {
                var table_settings = this.instance.getSettings();
                //values are converted from database format to desired display format
                originalValue = moment(originalValue, table_settings.dataSourceDateFormat + ' hh:mm:ss')
                    .format(table_settings.displayDateFormat + ' ' + table_settings.momentTimeFormat);
            }

            // Remember to invoke parent's method
            Handsontable.editors.DateEditor.prototype.prepare.apply(this, arguments);
        };

        ExcelDateTimeEditor.prototype.saveValue = function (val, ctrlDown) {
            if (val[0][0] && !this.same_display_and_edit_format) {
                var table_settings = this.instance.getSettings();
                //values are converted from desired display format to database format in order to save properly
                val[0][0] = moment(val[0][0], table_settings.displayDateFormat + ' ' + table_settings.momentTimeFormat)
                    .format(table_settings.dataSourceDateFormat + ' H:mm:ss');
            }
            // Remember to invoke parent's method
            Handsontable.editors.DateEditor.prototype.saveValue.apply(this, arguments);
        };

        // Put editor in dedicated namespace
        Handsontable.editors.ExcelTimeEditor = ExcelDateTimeEditor;

        // Register alias
        Handsontable.editors.registerEditor('wdt.datetime', ExcelDateTimeEditor);
    }

    /**
     * TIME EDITOR DEFINITION
     */
    function initWdtTimeEditor() {
        var ExcelTimeEditor = Handsontable.editors.TextEditor.prototype.extend();

        ExcelTimeEditor.prototype.init = function () {
            Handsontable.editors.TextEditor.prototype.init.apply(this, arguments);

            var table_settings = this.instance.getSettings();
            this.time_format = table_settings.timeFormat;
            var $__9 = this;
            this.instance.addHook('afterDestroy', (function () {
                $__9.parentDestroyed = true;
                $__9.destroyElements();
            }));
        };

        ExcelTimeEditor.prototype.createElements = function () {
            Handsontable.editors.TextEditor.prototype.createElements.apply(this, arguments);
            // Create password input and update relevant properties
            this.TEXTAREA = document.createElement('input');
            this.TEXTAREA.className = 'wdt-timepicker';
            this.textareaStyle = this.TEXTAREA.style;
            this.textareaStyle.width = 0;
            this.textareaStyle.height = 0;

            // Replace textarea with password input
            Handsontable.Dom.empty(this.TEXTAREA_PARENT);
            this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
        };

        //this event is needed because of handsontable bug, when entered invalid data, escape was not calling finish editing function
        ExcelTimeEditor.prototype.onBeforeKeyDown = function (event) {
            switch (event.keyCode) {
                case Handsontable.helper.KEY_CODES.ESCAPE:
                    var editor = this.getActiveEditor();
                    editor.finishEditing(true);
                    break;
                case Handsontable.helper.KEY_CODES.ARROW_DOWN:
                case Handsontable.helper.KEY_CODES.ARROW_UP:
                    break;
            }
        };

        ExcelTimeEditor.prototype.beginEditing = function (e) {
            Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments);
        }

        //this is a fix for bug that when invalid time is tried to be saved, date picker was unable to appear.
        ExcelTimeEditor.prototype.discardEditor = function (validationResult) {
            Handsontable.editors.TextEditor.prototype.discardEditor.apply(this, arguments);

            var hotInstance = this.instance;
            var allowInvalid = hotInstance.getCellMeta(this.row, this.col).allowInvalid;

            if (!allowInvalid && !validationResult) {
                if (!this.$timePicker.$node.is(':visible')) {
                    this.$timePicker.open();
                }
            }
        };

        ExcelTimeEditor.prototype.open = function () {
            this.instance.addHook('beforeKeyDown', this.onBeforeKeyDown);
            Handsontable.editors.TextEditor.prototype.open.apply(this, arguments);
        };

        ExcelTimeEditor.prototype.beginEditing = function () {
            Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments);
        }

        ExcelTimeEditor.prototype.focus = function (event) {
            Handsontable.editors.DateEditor.prototype.focus.apply(this, arguments);
        }

        ExcelTimeEditor.prototype.close = function () {
            this.instance.removeHook('beforeKeyDown', this.onBeforeKeyDown);
            Handsontable.editors.TextEditor.prototype.close.apply(this, arguments);
        };

        ExcelTimeEditor.prototype.finishEditing = function () {
            var isCancelled = arguments[0] !== (void 0) ? arguments[0] : false;
            var ctrlDown = arguments[1] !== (void 0) ? arguments[1] : false;
            if (isCancelled) {
                var value = this.originalValue;
                if (value !== void 0) {
                    this.setValue(value);
                }
            }
            Handsontable.editors.TextEditor.prototype.finishEditing.apply(this, arguments);
        };

        ExcelTimeEditor.prototype.prepare = function (row, col, prop, td, originalValue, cellProperties) {
            if (!originalValue) {
                originalValue = '';
            } else {
                var table_settings = this.instance.getSettings();
                originalValue = moment(originalValue, 'H:mm:ss')
                    .format(table_settings.momentTimeFormat);
            }

            // Remember to invoke parent's method
            Handsontable.editors.TextEditor.prototype.prepare.apply(this, arguments);
        };

        ExcelTimeEditor.prototype.saveValue = function (val, ctrlDown) {
            // Format time back to MySQL format
            if (val[0][0]) {
                var table_settings = this.instance.getSettings();
                val[0][0] = moment(val[0][0], table_settings.momentTimeFormat)
                    .format('H:mm:ss');
            }

            // Remember to invoke parent's method
            Handsontable.editors.TextEditor.prototype.saveValue.apply(this, arguments);
        };

        // Put editor in dedicated namespace
        Handsontable.editors.ExcelTimeEditor = ExcelTimeEditor;

        // Register alias
        Handsontable.editors.registerEditor('wdt.time', ExcelTimeEditor);
    }

    /**
     * TinyMCE EDITOR DEFINITION
     */

    /**
     * ATTACHMENT EDITOR DEFINITION.
     */
    function initWdtAttachmentEditor() {
        var ExcelAttachmentEditor = Handsontable.editors.BaseEditor.prototype.extend();

        ExcelAttachmentEditor.prototype.init = function () {
            //this is wordpress media library for handling file uploads.
            this.wdtCustomUploader = wp.media({
                title: wpdatatables_excel_strings.select_upload_file_excel,
                button: {
                    text: wpdatatables_excel_strings.choose_file_excel
                },
                multiple: false
            });

            var editor = this;
            var htInstance = this.instance;
            this.wdtCustomUploader.on('select', function () {
                var attachment = editor.wdtCustomUploader.state().get('selection').first().toJSON();
                var cellRenderer = htInstance.getCellRenderer(editor.row, editor.col).name;
                var value = attachment.url;

                //if file is for image column(we presume it is image file), there is a check if thumbnail exists and with and height properties are saved as a query string part of image url.
                if (cellRenderer == 'wdtImageRenderer') {
                    if (attachment.type != 'image') {
                        value = '';
                    } else {
                        var imgUri = new URI(value);
                        imgUri.setQuery({img_width: attachment.width, img_height: attachment.height});

                        value = imgUri.toString();

                        if (attachment.sizes.thumbnail) {
                            var thumbUri = new URI(attachment.sizes.thumbnail.url);
                            thumbUri.setQuery({
                                img_width: attachment.sizes.thumbnail.width,
                                img_height: attachment.sizes.thumbnail.height
                            });
                            value = thumbUri.toString() + '||' + value;
                        }
                    }
                }

                //saving value
                htInstance.setDataAtCell(editor.row, editor.col, value);
            });

            $browseButton = $('<button class="button-primary">' + wpdatatables_excel_strings.browse_file_excel + '</button>');
            $($browseButton).click(function (e) {
                e.preventDefault();

                // Open the uploader dialog
                htInstance.getActiveEditor().wdtCustomUploader.open();
            });

            this.BROWSE_BUTTON = $browseButton[0];
            this.INPUT_FILE_URL = $('<input type="hidden" />')[0];
            this.UPLOADED_FILES_ELEMENTS = document.createElement('p');


            $editorCnt = $('<div class="wdtAttachmentEditorHolder" ></div>')
                .append(this.UPLOADED_FILES_ELEMENTS, this.BROWSE_BUTTON, this.INPUT_FILE_URL)
                .hide();

            this.EDITOR_CONTENT = $editorCnt[0];
            this.editorContainerStyle = this.EDITOR_CONTENT.style;

            this.instance.rootElement.appendChild(this.EDITOR_CONTENT);
        };

        ExcelAttachmentEditor.prototype.getValue = function () {
            return this.INPUT_FILE_URL.value;
        };

        ExcelAttachmentEditor.prototype.setValue = function (newValue) {
            this.INPUT_FILE_URL.value = newValue;

            var uploaded_file = '';
            var htInstance = this.instance;

            if (newValue != '') {
                var link = newValue.split('||')[0];
                var uploaded_file;
                //if( !/\.(jpg|jpeg|png|gif)$/i.test( link ) ) {
                //    //TODO: add empty file icon if not image
                //}

                if (htInstance.getCellRenderer(this.row, this.col).name == 'wdtImageRenderer') {
                    uploaded_file = $('<img src="' + link + '" />').css({
                        'max-width': '100px',
                        'max-height': '50px'
                    })[0].outerHTML;
                } else {
                    //only file name is displayed
                    uploaded_file = newValue.split('/').pop();
                }

                uploaded_file += '<span class="delete_file">[<a href="#">' + wpdatatables_excel_strings.detach_file_excel + '</a>]</span>';
            }

            $(this.UPLOADED_FILES_ELEMENTS).html(uploaded_file);

            $(this.UPLOADED_FILES_ELEMENTS).on('click', 'a', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();

                var editor = htInstance.getActiveEditor();
                editor.setValue('', false);
                editor.finishEditing(false, false);
            });
        };

        ExcelAttachmentEditor.prototype.open = function () {
            this.refreshDimensions();
        };

        ExcelAttachmentEditor.prototype.close = function () {
            this.editorContainerStyle.display = 'none';
        };

        ExcelAttachmentEditor.prototype.focus = function () {
            this.BROWSE_BUTTON.focus();
        };

        //this is taken from TextEditor.prototype.getEditedCell and modified
        //this is probably needed because of fixed headers, columns, rows, etc...
        ExcelAttachmentEditor.prototype.getEditedCell = function () {
            var editorSection = this.checkEditorSection(),
                editedCell;
            switch (editorSection) {
                case 'top':
                    editedCell = this.instance.view.wt.wtOverlays.topOverlay.clone.wtTable.getCell({
                        row: this.row,
                        col: this.col
                    });
                    this.editorContainerStyle.zIndex = 101;
                    break;
                case 'top-left-corner':
                    editedCell = this.instance.view.wt.wtOverlays.topLeftCornerOverlay.clone.wtTable.getCell({
                        row: this.row,
                        col: this.col
                    });
                    this.editorContainerStyle.zIndex = 103;
                    break;
                case 'bottom-left-corner':
                    editedCell = this.instance.view.wt.wtOverlays.bottomLeftCornerOverlay.clone.wtTable.getCell({
                        row: this.row,
                        col: this.col
                    });
                    this.editorContainerStyle.zIndex = 103;
                    break;
                case 'left':
                    editedCell = this.instance.view.wt.wtOverlays.leftOverlay.clone.wtTable.getCell({
                        row: this.row,
                        col: this.col
                    });
                    this.editorContainerStyle.zIndex = 102;
                    break;
                case 'bottom':
                    editedCell = this.instance.view.wt.wtOverlays.bottomOverlay.clone.wtTable.getCell({
                        row: this.row,
                        col: this.col
                    });
                    this.editorContainerStyle.zIndex = 102;
                    break;
                default:
                    editedCell = this.instance.getCell(this.row, this.col);
                    this.editorContainerStyle.zIndex = '';
                    break;
            }
            return editedCell != -1 && editedCell != -2 ? editedCell : void 0;
        };

        //this is taken from TextEditor.prototype.refreshDimensions and modified
        //this is probably needed because of fixed headers, columns, rows, etc...
        ExcelAttachmentEditor.prototype.refreshDimensions = function () {
            if (this.state !== Handsontable.EditorState.EDITING) {
                return;
            }
            this.TD = this.getEditedCell();
            if (!this.TD) {
                this.close(true);
                return;
            }
            var currentOffset = Handsontable.Dom.offset(this.TD),
                containerOffset = Handsontable.Dom.offset(this.instance.rootElement),
                scrollableContainer = Handsontable.Dom.getScrollableElement(this.TD),
                totalRowsCount = this.instance.countRows(),
                editTop = currentOffset.top - containerOffset.top - 1 - (scrollableContainer.scrollTop || 0),
                editLeft = currentOffset.left - containerOffset.left - 1 - (scrollableContainer.scrollLeft || 0),
                settings = this.instance.getSettings(),
                colHeadersCount = settings.colHeaders ? 1 : 0,
                editorSection = this.checkEditorSection(),
                //backgroundColor = this.TD.style.backgroundColor,
                cssTransformOffset;
            switch (editorSection) {
                case 'top':
                    cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtOverlays.topOverlay.clone.wtTable.holder.parentNode);
                    break;
                case 'left':
                    cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtOverlays.leftOverlay.clone.wtTable.holder.parentNode);
                    break;
                case 'top-left-corner':
                    cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtOverlays.topLeftCornerOverlay.clone.wtTable.holder.parentNode);
                    break;
                case 'bottom-left-corner':
                    cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtOverlays.bottomLeftCornerOverlay.clone.wtTable.holder.parentNode);
                    break;
                case 'bottom':
                    cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtOverlays.bottomOverlay.clone.wtTable.holder.parentNode);
                    break;
            }
            if (colHeadersCount && this.instance.getSelected()[0] === 0 || (settings.fixedRowsBottom && this.instance.getSelected()[0] === totalRowsCount - settings.fixedRowsBottom)) {
                editTop += 1;
            }
            if (this.instance.getSelected()[1] === 0) {
                editLeft += 1;
            }
            if (cssTransformOffset && cssTransformOffset != -1) {
                this.editorContainerStyle[cssTransformOffset[0]] = cssTransformOffset[1];
            } else {
                Handsontable.Dom.resetCssTransform(this.EDITOR_CONTENT);
            }
            this.editorContainerStyle.top = editTop + 'px';
            this.editorContainerStyle.left = editLeft + 'px';
            var cellTopOffset = this.TD.offsetTop - this.instance.view.wt.wtOverlays.topOverlay.getScrollPosition(),
                cellLeftOffset = this.TD.offsetLeft - this.instance.view.wt.wtOverlays.leftOverlay.getScrollPosition();
            var width = Handsontable.Dom.innerWidth(this.TD) - 8;//?
            var maxWidth = this.instance.view.maximumVisibleElementWidth(cellLeftOffset) - 9;//?
            var height = this.TD.scrollHeight + 1;//?
            //var maxHeight = Math.max(this.instance.view.maximumVisibleElementHeight(cellTopOffset) - 2, 23);
            //this.editorContainerStyle.backgroundColor = backgroundColor ? backgroundColor : Handsontable.Dom.getComputedStyle(this.EDITOR_CONTENT).backgroundColor;

            //this.editorContainerStyle.height = height + 'px';
            this.editorContainerStyle.width = width + 'px';
            this.editorContainerStyle.maxWidth = maxWidth + 'px';

            this.editorContainerStyle.display = 'block';
        };

        // Put editor in dedicated namespace
        Handsontable.editors.ExcelAttachmentEditor = ExcelAttachmentEditor;

        // Register alias
        Handsontable.editors.registerEditor('wdt.attachment', ExcelAttachmentEditor);
    }

    /**
     * CUSTOM DROPDOWN EDITOR DEFINITION.
     */
    function initWdtDropdownEditor() {
        var ExcelDropdownEditor = Handsontable.editors.DropdownEditor.prototype.extend();

        ExcelDropdownEditor.confirmWdtDropdownCellValidate = function (isValid, value, row, prop, source) {
            var col_index = this.propToCol(prop);
            var cell_meta = this.getCellMeta(row, col_index);

            //for wdt.dropdown column type, validator is defined by default that checks if selected value is in possible values list.
            //also this plugin has functionality that additional validator also can be defined via 'cell_validator' property.
            //it is automatically assigned depending of the renderer type(e.g. numeric or link).
            //this validator is called here for checking if data is in good format.
            if (isValid && typeof cell_meta.cell_validator == 'function') {
                var valid_value = false;
                cell_meta.cell_validator(value, function (valid) {
                    return valid_value = valid;
                });

                return valid_value;
            } else {
                return isValid;
            }
        };

        ExcelDropdownEditor.prototype.open = function () {
            Handsontable.editors.DropdownEditor.prototype.open.apply(this, arguments);

            this.instance.addHook('afterValidate', ExcelDropdownEditor.confirmWdtDropdownCellValidate);
        };

        ExcelDropdownEditor.prototype.close = function () {
            Handsontable.editors.DropdownEditor.prototype.close.apply(this, arguments);

            this.instance.removeHook('afterValidate', ExcelDropdownEditor.confirmWdtDropdownCellValidate);
        };

        // Put editor in dedicated namespace
        Handsontable.editors.ExcelDropdownEditor = ExcelDropdownEditor;

        // Register alias
        Handsontable.editors.registerEditor('wdt.dropdown', ExcelDropdownEditor);
    }

    /**
     * CUSTOM MULTI-SELECT DROPDOWN EDITOR DEFINITION.
     */
    function initWdtMultipleSelectEditor() {
        var ExcelMultiSelectEditor = Handsontable.editors.ExcelDropdownEditor.prototype.extend();

        ExcelMultiSelectEditor.onBeforeKeyDown = function (event) {
            //on ENTER we want to select dropdown item
            switch (event.keyCode) {
                case Handsontable.helper.KEY_CODES.ENTER:
                    var editor = this.getActiveEditor();
                    var choicesListHot = editor.htEditor.getInstance();
                    var selectedCellsRange = choicesListHot.getSelected();

                    if (selectedCellsRange) {
                        var selectedTD = choicesListHot.getCell(selectedCellsRange[0], selectedCellsRange[1]);
                        ExcelMultiSelectEditor.selectDropdownItem(selectedTD, choicesListHot, editor);
                    }

                    event.stopImmediatePropagation(); // prevent EditorManager from processing this event
                    event.preventDefault(); // prevent browser from scrolling the page up
                    break;
            }
        };

        //marking selected values in dropdown and add them in textarea.
        ExcelMultiSelectEditor.selectDropdownItem = function (selectedTD, choicesListHot, editor) {
            var $td = $(selectedTD);
            var cell_value = $td.text();
            if ($td.children('strong:first-child').length) {
                $td.html(cell_value);
            } else {
                $td.html('<strong>' + cell_value + '</strong>');
            }

            var value = [];
            $(choicesListHot.rootElement).find('td strong:first-child').each(function () {
                value.push($(this).text());
            });

            editor.setValue(value.join());
        };

        ExcelMultiSelectEditor.prototype.open = function () {
            // register listener
            this.instance.addHook('beforeKeyDown', ExcelMultiSelectEditor.onBeforeKeyDown);

            Handsontable.editors.ExcelDropdownEditor.prototype.open.apply(this, arguments);

            var choicesListHot = this.htEditor.getInstance();
            var editor = this;
            var choicesListHotSettings = choicesListHot.getSettings();

            //Overriding previous inner handsontable object functionality
            //this was the only way to get afterRenderer function
            choicesListHot.removeHook('afterRenderer', choicesListHot.pluginHookBucket.afterRenderer.slice(-1)[0]);
            choicesListHot.removeHook('afterOnCellMouseDown', choicesListHotSettings.afterOnCellMouseDown);

            choicesListHot.updateSettings({
                afterRenderer: function (TD, row, col, prop, value) {
                    //taken from autocmplete editor and modified
                    //marking values that are entered in textarea.
                    var caseSensitive = this.getCellMeta(row, col).filteringCaseSensitive === true,
                        indexOfMatch,
                        value = Handsontable.helper.stringify(value);
                    if (value) {
                        indexOfMatch = caseSensitive ? $.inArray(value, editor.query.split(',')) : $.inArray(value.toLowerCase(), editor.query.toLowerCase().split(','));
                        if (indexOfMatch != -1) {
                            var safe = Handsontable.helper.stringify(value);
                            var escSpan = document.createElement('span');
                            escSpan.textContent = safe;
                            TD.innerHTML = '<strong>' + escSpan.innerHTML + '</strong>';
                        }
                    }
                },
                afterOnCellMouseDown: function (event, coords, TD) {
                    //taken from handsontable editor and modified
                    //on mouse click on a select box handsontable element
                    var cell_value = this.getValue();
                    if (cell_value !== void 0) {
                        ExcelMultiSelectEditor.selectDropdownItem(TD, choicesListHot, editor);
                    }

                    editor.focus();
                }
            });
        };

        //overriding from handsontable editor because it was taking only one selected item
        ExcelMultiSelectEditor.prototype.finishEditing = function (isCancelled, ctrlDown) {
            if (this.htEditor && this.htEditor.isListening()) {
                this.instance.listen();
            }

            if (this.htEditor) {
                var value = [];
                $(this.htEditor.getInstance().rootElement).find('td strong:first-child').each(function () {
                    value.push($(this).text());
                });

                //this.setValue(value.join());
                this.setValue(this.TEXTAREA.value);
            }

            return Handsontable.editors.TextEditor.prototype.finishEditing.apply(this, arguments);
        };

        ExcelMultiSelectEditor.prototype.close = function () {
            Handsontable.editors.ExcelDropdownEditor.prototype.close.apply(this, arguments);
            // remove listener
            this.instance.removeHook('beforeKeyDown', ExcelMultiSelectEditor.onBeforeKeyDown);
        };

        // Put editor in dedicated namespace
        Handsontable.editors.ExcelMultiSelectEditor = ExcelMultiSelectEditor;

        // Register alias
        Handsontable.editors.registerEditor('wdt.multi-select', ExcelMultiSelectEditor);
    }

    /**
     * LINK RENDERER DEFINITION.
     */
    function wdtLinkRenderer(instance, td, row, col, prop, value, cellProperties) {
        //check if link is empty
        //also check if editor type is attachment
        Handsontable.TextRenderer.apply(this, arguments);

        if (!value) {
            return td;
        }

        var link_parts = value.split('||');
        var link = wdtSanitizeExcelHref(link_parts[0]);

        if (link.length == 0) {
            $(td).empty();
            td.appendChild(document.createTextNode(String(link_parts[1] != null ? link_parts[1] : link_parts[0])));
            return td;
        }

        var content = (link_parts.length > 1) ? link_parts[1] : link;
        var table_settings = instance.getSettings();
        var class_name = 'wdt_link';
        var titleText = '';

        if (!table_settings.readOnly) {
            class_name += '_editable';
            titleText = 'ctrl+click to open hyperlink:' + link;
        }

        var $a = $('<a/>', {
            'class': class_name,
            target: '_blank',
            rel: 'noopener noreferrer',
            href: link
        });
        if (titleText) {
            $a.attr('title', titleText);
        }
        $a.text(content);
        $(td).empty().append($a);
    }

    /**
     * E-MAIL RENDERER DEFINITION.
     */
    function wdtEmailRenderer(instance, td, row, col, prop, value, cellProperties) {
        Handsontable.TextRenderer.apply(this, arguments);

        if (!value) {
            return td;
        }

        var email_parts = value.split('||');
        var email = wdtSanitizeExcelMailto(email_parts[0]);

        if (email.length == 0) {
            $(td).empty();
            td.appendChild(document.createTextNode(String(email_parts[1] != null ? email_parts[1] : email_parts[0])));
            return td;
        }

        var content = (email_parts.length > 1) ? email_parts[1] : email;
        var table_settings = instance.getSettings();
        var class_name = 'wdt_email';
        var titleText = '';

        if (!table_settings.readOnly) {
            class_name += '_editable';
            titleText = 'ctrl+click to send email to:' + email;
        }

        var $a = $('<a/>', {
            'class': class_name,
            href: 'mailto:' + email
        });
        if (titleText) {
            $a.attr('title', titleText);
        }
        $a.text(content);
        $(td).empty().append($a);
    }

    /**
     * IMAGE RENDERER DEFINITION.
     */
    function wdtImageRenderer(instance, td, row, col, prop, value, cellProperties) {
        Handsontable.TextRenderer.apply(this, arguments);

        if (!value) {
            return td;
        }

        var image_parts = value.split('||');
        var raw_thumb = image_parts[0];

        if (raw_thumb.length == 0) {
            return td;
        }

        //image width and height data are saved with image url(in editing cell process) as query params ('img_width' and 'img_height') in order to fast retrieve them.
        //without presetting image dimensions, handsontable had troubles to render table dimensions properly if image column is present.
        var imageUri = new URI(raw_thumb);
        var imageQuery = imageUri.query(true);
        var img_width = (imageUri.hasQuery('img_width')) ? imageQuery.img_width : 0;
        var img_height = (imageUri.hasQuery('img_height')) ? imageQuery.img_height : 0;

        var thumb_src = wdtSanitizeExcelHref(imageUri.toString());
        if (!thumb_src) {
            $(td).empty();
            return td;
        }

        var img_style = '';

        if (img_width > 0) {
            img_style += 'width:' + img_width + 'px; '
        }

        if (img_height > 0) {
            img_style += 'height:' + img_height + 'px;'
        }

        if (img_style.length > 0) {
            img_style = 'style="' + img_style + '"';
        }

        //if( !/\.(jpg|jpeg|png|gif)$/i.test( link ) ) {
        //    //TODO: add empty file icon if not image
        //}

        var html = '';

        if (image_parts.length > 1 && image_parts[1] != '') {
            var tableSettings = instance.getSettings();
            var class_name = 'wdt_link';
            var title_attr = '';

            var fullSizeImgUri = new URI(image_parts[1]);
            fullSizeImgUri.removeQuery(['img_width', 'img_height']);
            var full_size_image = wdtSanitizeExcelHref(fullSizeImgUri.toString());
            if (!full_size_image) {
                html = '<img class="wpdt-thumb" src="' + thumb_src + '" ' + img_style + ' />';
            } else {
                if (!tableSettings.readOnly) {
                    class_name += '_editable';
                    title_attr = 'title="ctrl+click to open hyperlink:' + full_size_image + '"';
                }

                var image_html = '<img class="wpdt-thumb" src="' + thumb_src + '" ' + img_style + ' />';

                html = '<a class="' + class_name + '" href="' + full_size_image + '" target="_blank" rel="noopener noreferrer" ' + title_attr + '>' + image_html + '</a>';
            }
        } else {
            html = '<img class="wpdt-thumb" src="' + thumb_src + '" ' + img_style + ' />';
        }

        td.innerHTML = html;
    }

    /**
     * CUSTOM TEXT RENDERER DEFINITION.
     */
    function wdtTextRenderer(instance, td, row, col, prop, value, cellProperties) {
        var escaped = Handsontable.helper.stringify(value);
        escaped = wdtStripTags(escaped, '<br/><br><b><strong><h1><h2><h3><a><i><em><ol><ul><li><img><blockquote><div><hr><p><span><select><option><sup><sub>'); //be sure you only allow certain HTML tags to avoid XSS threats (you should also remove unwanted HTML attributes)
        escaped = wdtSanitizeExcelRichCellHtml(escaped);
        td.innerHTML = escaped;
        Handsontable.SearchCellDecorator.apply(this, arguments);
        return td;
    }

    /**
     * CUSTOM DATE RENDERER DEFINITION.
     */
    function wdtDateRenderer(instance, td, row, col, prop, value, cellProperties) {
        if (!value || value == '0000-00-00') {
            value = '';
            $(td).html(value);
            Handsontable.AutocompleteRenderer(instance, td, row, col, prop, value, cellProperties);
            return td;
        }

        var table_settings = instance.getSettings();

        if (table_settings.dataSourceDateFormat != table_settings.displayDateFormat) {
            //values are converted from database format to desired display format
            value = moment(value, table_settings.dataSourceDateFormat).format(table_settings.displayDateFormat);
        }

        Handsontable.AutocompleteRenderer(instance, td, row, col, prop, value, cellProperties);
    }

    /**
     * Custom DateTime renderer
     */
    function wdtDateTimeRenderer(instance, td, row, col, prop, value, cellProperties) {
        if (!value || value == '0000-00-00 00:00:00') {
            value = '';
            $(td).html(value);
            Handsontable.AutocompleteRenderer(instance, td, row, col, prop, value, cellProperties);
            return td;
        }

        var table_settings = instance.getSettings();

        if (table_settings.dataSourceDateFormat != table_settings.displayDateFormat) {
            //values are converted from database format to desired display format
            value = moment(
                value,
                table_settings.dataSourceDateFormat + ' HH:mm:ss'
            ).format(
                table_settings.displayDateFormat + ' ' + table_settings.momentTimeFormat
            );
        } else {
            value = moment(
                value,
                table_settings.dataSourceDateFormat + ' HH:mm:ss'
            ).format(
                table_settings.dataSourceDateFormat + ' ' + table_settings.momentTimeFormat
            );
        }

        Handsontable.AutocompleteRenderer(instance, td, row, col, prop, value, cellProperties);
    }

    /**
     * Custom Time renderer
     */
    function wdtTimeRenderer(instance, td, row, col, prop, value, cellProperties) {
        if (!value) {
            value = '';
            $(td).html(value);
            Handsontable.AutocompleteRenderer(instance, td, row, col, prop, value, cellProperties);
            return td;
        }

        var table_settings = instance.getSettings();

        value = moment(value, 'HH:mm:ss').format(table_settings.momentTimeFormat);

        Handsontable.AutocompleteRenderer(instance, td, row, col, prop, value, cellProperties);
    }


    //registers custom renderers
    function registerRenderers() {
        Handsontable.renderers.registerRenderer('text', wdtTextRenderer);
        Handsontable.renderers.registerRenderer('wdt.date', wdtDateRenderer);
        Handsontable.renderers.registerRenderer('wdt.datetime', wdtDateTimeRenderer);
        Handsontable.renderers.registerRenderer('wdt.time', wdtTimeRenderer);
        Handsontable.renderers.registerRenderer('wdt.link', wdtLinkRenderer);
        Handsontable.renderers.registerRenderer('wdt.email', wdtEmailRenderer);
        Handsontable.renderers.registerRenderer('wdt.image', wdtImageRenderer);
    }

    //initializes and registers custom editors
    function registerEditors() {
        initWdtMultilineEditor();
        initWdtDateEditor();
        initWdtDateTimeEditor();
        initWdtTimeEditor();
        initWdtAttachmentEditor();
        initWdtDropdownEditor();
        initWdtMultipleSelectEditor();
    }

    //defines and registers custom cell types
    function registerCellTypes() {
        Handsontable.wdtDateCell = {
            editor: 'wdt.date',
            validator: Handsontable.wdtDateValidator,
            renderer: 'wdt.date'
        };

        Handsontable.wdtTextCell = {
            editor: 'text',
            renderer: 'text'
        };

        Handsontable.wdtDateTimeCell = {
            editor: 'wdt.datetime',
            validator: Handsontable.wdtDateTimeValidator,
            renderer: 'wdt.datetime'
        };

        Handsontable.wdtTimeCell = {
            editor: 'wdt.time',
            validator: Handsontable.wdtTimeValidator,
            renderer: 'wdt.time'
        };

        Handsontable.wdtMultiSelectCell = {
            editor: 'wdt.multi-select',
            validator: Handsontable.wdtMultiSelectValidator,
            renderer: 'text'
        };

        Handsontable.cellTypes['text'] = Handsontable.wdtTextCell;
        Handsontable.cellTypes['wdt.date'] = Handsontable.wdtDateCell;
        Handsontable.cellTypes['wdt.datetime'] = Handsontable.wdtDateTimeCell;
        Handsontable.cellTypes['wdt.time'] = Handsontable.wdtTimeCell;
        Handsontable.cellTypes['wdt.multi-select'] = Handsontable.wdtMultiSelectCell;
    }

    /**
     * LINK VALIDATOR DEFINITION.
     */
    Handsontable.wdtLinkValidator = function (value, callback) {
        if (value === null) {
            value = '';
        }

        var is_valid = false;

        if (value == '') {
            is_valid = true;
        } else {
            var regex = /^([a-z]([a-z]|\d|\+|-|\.)*):(\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?((\[(|(v[\da-f]{1,}\.(([a-z]|\d|-|\.|_|~)|[!\$&'\(\)\*\+,;=]|:)+))\])|((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=])*)(:\d*)?)(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*|(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)|((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)|((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)){0})(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?(||.*)?$/i;

            is_valid = regex.test(value);
        }

        callback(is_valid);
    };

    /**
     * E-MAIL VALIDATOR DEFINITION.
     */
    Handsontable.wdtEmailValidator = function (value, callback) {
        if (value === null) {
            value = '';
        }

        var is_valid = false;

        if (value == '') {
            is_valid = true;
        } else {
            //took from wdtValidateEmail, in wpdatatables.func.js, maybe move these helper functions somewhere that every library can use them
            var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+(||.*)?$/;

            is_valid = regex.test(value);
        }

        callback(is_valid);
    };

    /**
     * CUSTOM DATE VALIDATOR DEFINITION.
     */
    Handsontable.wdtDateValidator = function (value, callback) {
        if (value === null) {
            value = '';
        }

        if (value == '') {
            callback(true);
        } else {
            Handsontable.DateValidator.call(this, value, callback);
        }
    };

    /**
     * CUSTOM DATETIME VALIDATOR DEFINITION
     */
    Handsontable.wdtDateTimeValidator = function (value, callback) {
        if (value === null) {
            value = '';
        }

        var table_settings = this.instance.getSettings();

        var isValidDate = moment(new Date(value)).isValid();
        var isValidFormat = moment(
            value,
            table_settings.dataSourceDateFormat + ' H:mm:ss',
            true
        ).isValid();

        if (value == '') {
            callback(true);
        } else {
            callback(isValidDate && isValidFormat);
        }
    };

    /**
     * CUSTOM TIME VALIDATOR DEFINITION
     */
    Handsontable.wdtTimeValidator = function (value, callback) {
        if (value === null) {
            value = '';
        }

        var table_settings = this.instance.getSettings();

        var isValidTime = moment(
            value,
            'H:mm:ss',
            true
        ).isValid();

        if (value == '') {
            callback(true);
        } else {
            callback(isValidTime);
        }
    };

    /**
     * MULTI-SELECT VALIDATOR DEFINITION.
     * Checking if entered values are in possible values.
     */
    Handsontable.wdtMultiSelectValidator = function (value, callback) {
        var isValid = true;

        if (value === null) {
            value = '';
        }

        if (this.source) {
            var selected = value.toLowerCase().split(',');
            var choices = this.source.join(',').toLowerCase().split(',');

            for (var i = 0; i < selected.length; i++) {
                if (choices.indexOf(selected[i]) == -1) {
                    isValid = false;
                    break;
                }
            }
        } else if (value) {
            //if there is no select box and value is not empty
            isValid = false;
        }

        callback(isValid);
    };

    registerEditors();
    registerRenderers();
    registerCellTypes();

})(Handsontable, jQuery);

if (typeof wpDataTablesExcelOptions == 'undefined') {
    window.wpDtExcelTables = {};
}

(function ($) {
    $(function () {
        $('div.wpExcelTable').each(function () {
            $(this).handsontable({
                wpDataTablesExcelPlugin: true
            });
        })
    })
})(jQuery);

function wdtStripTags(input, allowed) {
    var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
        commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;

    // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
    allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');

    return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
        return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
    });
}
