/*jslint white: true, plusplus: true, bitwise: true */
/*eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/
/*!
 * Project: WordPress IP Geo Block
 * Copyright (c) 2013-2019 tokkonopapa (tokkonopapa@yahoo.com)
 * Copyright (c) 2021 DarkoG (dg@darkog.com)
 * This software is released under the MIT License.
 */
(function ($, window, document, undefined) {
    // External variables
    var skip_error = false,
        timer_stack = [],
        window_width = $(window).width(),
        ip_location_block = IP_LOCATION_BLOCK,
        ip_location_block_auth = IP_LOCATION_BLOCK_AUTH;

    function ID(selector, id) {
        var keys = {
            '.': '.ip-location-block-',
            '#': '#ip-location-block-',
            '@': '#ip_location_block_settings_',
            '$': 'ip-location-block-',
            '%': 'ip_location_block_',
            '!': 'ip_location_block_settings_'
        };
        return id !== undefined ? keys[selector] + id : keys.$ + selector;
    }

    function escapeHTML(str) {
        return str.toString().replace(/[&<>"']/g, function (match) { //'"
            return {
                '&': '&amp;',
                '<': '&lt;',
                '>': '&gt;',
                '"': '&quot;',
                "'": '&#39;'
            }[match];
        }).replace(/&amp;(#\d{2,4}|\w{4,7});/g, "&$1;"); // revert html character entity
    }

    function stripTag(str) {
        return str ? escapeHTML(str.toString().replace(/(<([^>]+)>)/ig, '')) : '';
    }

    function onresize(name, callback) {
        var w = $(window).width();
        if (w !== window_width) {
            window_width = w;
            if (timer_stack[name] === undefined) {
                timer_stack[name] = {id: false, callback: callback};
            }
            $(window).off('resize').on('resize', function (/*event*/) {
                if (false !== timer_stack[name].id) {
                    window.clearTimeout(timer_stack[name].id);
                }
                timer_stack[name].time = window.setTimeout(timer_stack[name].callback, 200);
                return false;
            });
        }
    }

    function loading(id, flag) {
        if (flag) {
            $(ID('#', id)).addClass(ID('loading'));
        } else {
            $(ID('#', id)).removeClass(ID('loading'));
        }
    }

    function confirm(msg, callback) {
        if (window.confirm(stripTag(msg))) {
            callback();
        }
    }

    function warning(status, msg, cmd) {
        window.alert(stripTag(msg || ip_location_block.msg[11].replace('%s', cmd) + ' (' + status + ')'));
    }

    function notice_html5() {
        warning(null, stripTag(ip_location_block.msg[9]));
    }

    function redirect(page, tab) {
        if (-1 !== window.location.href.indexOf(page)) {
            window.location = stripTag(page) + (tab ? '&' + stripTag(tab) : '') + '&' + (ip_location_block_auth.key ? (ip_location_block_auth.key + '=' + ip_location_block_auth.nonce) : '');
        }
    }

    function ajax_post(id, request, callback, objs) {
        if (id) {
            loading(id, true);
        }

        request.action = ip_location_block.action;
        request.nonce = ip_location_block.nonce;

        var params = {
            type: 'POST',
            url: ip_location_block.url,
            data: request,
            dataType: 'json',
        };

        $.ajax(params)
            .done(function (data/*, textStatus, jqXHR*/) {
                if (callback) {
                    callback(data);
                }
            })
            .fail(function (jqXHR /*,textStatus, errorThrown*/) {
                if (!skip_error) {
                    warning(jqXHR.status, jqXHR.responseText, request.action);
                }
            })
            .always(function () {
                if (id) {
                    if ('object' === typeof objs) { // deferred object
                        $.when.apply($, objs).then(function () {
                            loading(id, false);
                        });
                    } else {
                        loading(id, false);
                    }
                }
            });
    }

    // Clear statistics, cache, logs
    function ajax_clear(cmd, type) {
        ajax_post(cmd, {
            cmd: 'clear-' + cmd,
            which: type
        }, function (data) {
            redirect(data.page, data.tab);
        });
    }

    // Manipulate DB table for validation logs
    function ajax_table(cmd) {
        ajax_post(cmd, {
            cmd: cmd
        }, function (data) {
            redirect(data.page, data.tab);
        });
    }

    // Prevent event propergation
    function stopPropergation(event) {
        event.stopImmediatePropagation();
        return false;
    }

    // Show/Hide description of WP-ZEP
    function show_description(select) {
        var data, desc = ID('.', 'desc');
        select.next(desc).empty();
        data = select.children('option:selected').data('desc');
        if (data) {
            select.next(desc).html($.parseHTML(data)); // jQuery 1.8+
        }
    }

    // Fold the contents
    function fold_elements(obj, stat) { // obj: ul object
        if (stat) {
            obj.removeClass('folding-disable');
        } else {
            obj.children(ID('.', 'hide')).hide();
            obj.addClass('folding-disable');
            obj.removeClass(ID('dropdown')).addClass(ID('dropup'));
        }
    }

    // Show/Hide descendant elements
    function show_descendants($this, $elem, mask) {
        var prop = $this.prop('type') || '',
            stat = (0 === prop.indexOf('checkbox') && $this.is(':checked')) ||
                (0 === prop.indexOf('select') && '0' !== $this.val());

        // checkbox
        $this.siblings('input[name^="' + ID('%', 'settings') + '"]:checkbox').prop('disabled', !stat);

        // folding the list
        if ($.isArray($elem)) {
            $.each($elem, function (i, elm) {
                $(elm).nextAll(ID('.', 'settings-folding')).each(function (j, obj) {
                    fold_elements($(obj), stat && mask[i]);
                });
            });
        } else {
            $elem.nextAll(ID('.', 'settings-folding')).each(function (j, obj) {
                fold_elements($(obj), stat && mask);
            });
        }
    }

    // Show / Hide Exceptions
    function show_folding_ajax(elem) {
        var id = ID('@', 'validation_ajax_');
        fold_elements(
            elem.closest('ul').next(),
            $(id + '1').is(':checked') || $(id + '2').is(':checked')
        );
    }

    // Encode/Decode to prevent blocking before post ajax
    function base64_encode(str) {
        return window.btoa(str);
    }

    function base64_decode(str) {
        return window.atob(str);
    }

    // Equivalent for PHP's str_rot13
    // @link http://phpjs.org/functions/str_rot13/
    function str_rot13(str) {
        return String(str).replace(/[a-z]/gi, function (s) {
            return String.fromCharCode(s.charCodeAt(0) + (s.toLowerCase() < 'n' ? 13 : -13)); //'
        });
    }

    // Wrapper for encode/decode strings
    function encode_str(str) {
        return base64_encode(str_rot13(str));
    }

    function decode_str(str) {
        return str_rot13(base64_decode(str));
    }

    // File Reader
    function readfile(file, callback) {
        var reader = new window.FileReader();
        reader.onload = function (event) {
            if (callback) {
                callback(event.target.result);
            }
        };
        reader.onerror = function (event) {
            warning('Error', event.target.error.code);
        };
        reader.readAsText(file);
    }

    // Enable / Disable at front-end target settings
    function set_front_end($this) {
        var checked = $this.is(':checked'),
            select = $(ID('@', 'public_target_rule')),
            behave = $(ID('@', 'public_behavior')),
            parent = $this.closest('tr').nextAll('tr');

        // Enable / Disable descendent items
        parent.find('[name^="' + ID('%', 'settings') + '"]').prop('disabled', !checked);

        // Enable / Disable description
        parent.find(ID('.', 'desc')).css('opacity', checked ? 1.0 : 0.5);

        // Show / Hide validation target
        show_descendants($this, [select, behave], ['1' === select.val() ? true : false, behave.val()]);

        // Render descendant element
        if (checked) {
            behave.change();
        }
    }

    function is_blocked(result) {
        return -1 === result.indexOf('pass');
    }

    /**
     * jQuery deserialize plugin based on https://gist.github.com/nissuk/835256
     *
     * usage: $('form').deserialize({'name':'value', ...});
     */
    $.fn.deserialize = function (json/*, options*/) {
        return this.each(function () {
            var key, name, value,
                self = this,
                data = {};

            for (key in json) {
                if (json.hasOwnProperty(key)) {
                    try {
                        name = stripTag(decodeURIComponent(key)); // URIError: malformed URI sequence
                        value = stripTag(decodeURIComponent(json[key]));

                        if (!data.hasOwnProperty(name)) { // !(name in data)
                            data[name] = [];
                        }

                        data[name].push(value);
                    } catch (e) {
                    }
                }
            }

            $.each(data, function (name, val) {
                key = $('[name="' + name + '"]:input', self).val(val);
                if (key.attr('type') !== 'hidden') { // version
                    key.before('<span style="color:red">*</span>');
                }
            });
        });
    };

    function deserialize_json(json, clear) {
        if (json) {
            // Set fields on form
            if ('string' === typeof json) {
                json = JSON.parse(json);
            }

            // reset all checkboxes
            if (clear) {
                $('input[type="checkbox"]').prop('checked', false).change();
                $('input[name*=providers]').prop('disabled', false).change();
            }

            // deserialize to the form
            $(ID('#', 'import')).closest('form').deserialize(json);

            // update textfield, checkbox (Exceptions, Mimetype)
            $(ID('@', 'exception_admin') + ',' + ID('@', 'validation_mimetype')).change();

            // update selection
            $('select[name*="' + ID('%', 'settings') + '"]').change();

            // folding list at Login form
            $(ID('@', 'validation_login')).change();

            // Public facing pages
            set_front_end($(ID('@', 'validation_public')));

            // Admin ajax/post
            show_folding_ajax($(ID('@', 'validation_ajax_1')));

            // Additional edge case (it should be optimized except IPInfoDB)
            if (clear) {
                /*$('input[name*=providers]').each(function(i, elm) {
                    elm = $(elm);
                    elm.prop('checked', json[elm.prop('name')] ? false : true);
                });*/
                clear = ID('%', 'settings[providers][IPInfoDB]');
                $(ID('@', 'providers_IPInfoDB')).prop('checked', json[clear] ? true : false);
            }
        }
    }

    function inArray(val, arr) {
        var i, n = arr.length;
        val = val.replace('…', '');

        for (i = 0; i < n; ++i) {
            if (arr.hasOwnProperty(i) && 0 === arr[i].label.indexOf(val)) {
                return i;
            }
        }

        return -1;
    }

    // Split an array into chunks
    function array_chunk(arr, size) {
        var n = Math.ceil(arr.length / size),
            r = [], i, j;

        for (i = 0; i < n; ++i) {
            j = i * size;
            r.push(arr.slice(j, j + size)); // IE >= 9
        }

        return r;
    }

    // google chart
    var chart = {
        // Pie Chart
        dataPie: [],
        viewPie: [],
        drawPie: function (id) {
            var i, data;
            if (chart.dataPie[id] === undefined) {
                i = chart.dataPie[id] = new window.google.visualization.DataTable();
                i.addColumn('string', 'Country');
                i.addColumn('number', 'Requests');
                data = $.parseJSON($('#' + id).attr('data-' + id));
                chart.dataPie[id].addRows(data);
            }

            if (chart.viewPie[id] === undefined) {
                chart.viewPie[id] = new window.google.visualization.PieChart(
                    document.getElementById(id)
                );
            }

            if (chart.dataPie[id] !== undefined &&
                chart.viewPie[id] !== undefined &&
                0 < (i = $('#' + id).width())) {
                chart.viewPie[id].draw(chart.dataPie[id], {
                    backgroundColor: {fill: 'transparent'},
                    chartArea: {
                        left: 0,
                        top: '5%',
                        width: '100%',
                        height: '90%'
                    },
                    sliceVisibilityThreshold: 0.015
                });
            }
        },

        // Line Chart
        dataLine: [],
        viewLine: [],
        drawLine: function (id, datetype) {
            var i, n, data;
            if (chart.dataLine[id] === undefined) {
                i = chart.dataLine[id] = new window.google.visualization.DataTable();
                i.addColumn(datetype, 'Date');
                i.addColumn('number', 'comment');
                i.addColumn('number', 'xmlrpc');
                i.addColumn('number', 'login');
                i.addColumn('number', 'admin');
                i.addColumn('number', 'public');
                data = $.parseJSON($('#' + id).attr('data-' + id));
                n = data.length;
                for (i = 0; i < n; ++i) {
                    data[i][0] = new Date(data[i][0] * 1000); // [sec] to [msec]
                }
                chart.dataLine[id].addRows(data);
            }

            if (chart.viewLine[id] === undefined) {
                chart.viewLine[id] = new window.google.visualization.LineChart(
                    document.getElementById(id)
                );
            }

            if (chart.dataLine[id] !== undefined &&
                chart.viewLine[id] !== undefined &&
                0 < (i = $('#' + id).width())) {
                chart.viewLine[id].draw(chart.dataLine[id], {
                    legend: {position: 'bottom'},
                    backgroundColor: {fill: 'transparent'},
                    hAxis: {format: 'MM/dd' + ('datetime' === datetype ? ' HH:mm' : '')},
                    vAxis: {textPosition: (i > 320 ? 'out' : 'in')},
                    chartArea: {
                        left: (i > 320 ? '10%' : 0),
                        top: '5%',
                        width: '100%',
                        height: '75%'
                    }
                });
            }
        },

        // Stacked bar
        dataStacked: [],
        viewStacked: [],
        drawStacked: function (id) {
            var i, w, data, range, $id = $('#' + stripTag(id));

            if (chart.dataStacked[id] === undefined) {
                data = $.parseJSON($id.attr('data-' + id));
                if (data) {
                    data.unshift(['site', 'comment', 'xmlrpc', 'login', 'admin', 'public', {role: 'link'}]);
                    chart.dataStacked[id] = window.google.visualization.arrayToDataTable(data);
                }
            }

            if (chart.viewStacked[id] === undefined) {
                chart.viewStacked[id] = new window.google.visualization.BarChart(
                    document.getElementById(id)
                );

                // process for after animation
                window.google.visualization.events.addListener(chart.viewStacked[id], 'animationfinish', function (/*event*/) {
                    // Make an array of the title in each row.
                    var i, a, p,
                        info = [],
                        data = chart.dataStacked[id],
                        n = data.getNumberOfRows(),
                        mode = $(ID('#', 'open-new')).prop('checked');

                    for (i = 0; i < n; i++) {
                        info.push({
                            label: /*escapeHTML(*/data.getValue(i, 0)/*)*/,
                            link:  /*escapeHTML(*/data.getValue(i, 6)/*)*/
                        });
                    }

                    // https://stackoverflow.com/questions/12701772/insert-links-into-google-charts-api-data
                    n = 'http://www.w3.org/1999/xlink';
                    $id.find('text').each(function (i, elm) {
                        p = elm.parentNode;
                        if ('g' === p.tagName.toLowerCase() && -1 !== (i = inArray(elm.textContent, info))) {
                            a = document.createElementNS('http://www.w3.org/2000/svg', 'a');
                            a.setAttributeNS(n, 'xlink:href', info[i].link);
                            a.setAttributeNS(n, 'title', info[i].label);
                            a.setAttribute('target', mode ? '_blank' : '_self');
                            a.setAttribute('class', 'site');
                            a.appendChild(p.removeChild(elm));
                            p.appendChild(a);
                            info.splice(i, 1); // for speeding up
                        }
                    });
                });
            }

            if (0 < (w = $id.width()) &&
                chart.dataStacked[id] !== undefined &&
                chart.viewStacked[id] !== undefined) {

                i = ID('range');
                range = $.parseJSON($('.' + i).attr('data-' + i));

                data = chart.dataStacked[id];
                i = 40 * data.getNumberOfRows();

                chart.viewStacked[id].draw(data, {
                    width: w,
                    height: i + 80,
                    allowHtml: true,
                    isStacked: true,
                    legend: {position: 'top'},
                    chartArea: {
                        top: 50,
                        left: 90,
                        width: '100%',
                        height: i
                    },
                    hAxis: {
                        minValue: 0,
                        maxValue: range[1]
                    },
                    backgroundColor: {fill: 'transparent'},
                    animation: {
                        startup: true,
                        duration: 200,
                        easing: 'out'
                    }
                });
            }
        },
        ajaxStacked: function (duration, row, col, page) {
            duration = Math.max(0, Math.min(4, duration));
            row = Math.max(1, Math.min(5, row)) * 5;

            ajax_post(null, {
                cmd: 'restore-network',
                which: duration,
                offset: row * col * page,
                length: row
            }, function (data) {
                var i, j, n, id, dt;

                data = array_chunk(data, row);

                $(ID('.', 'network')).each(function (index, obj) {
                    if (data[index] !== undefined) {
                        id = $(obj).attr('id');
                        dt = chart.dataStacked[id];
                        n = Math.min(row, data[index].length);
                        for (i = 0; i < n; ++i) {
                            // [0]:site, [1]:comment, [2]:xmlrpc, [3]:login, [4]:admin, [5]:public, [6]:link
                            for (j = 1; j <= 5; j++) {
                                dt.setValue(i, j, data[index][i][j]);
                            }
                        }
                        chart.drawStacked(id);
                    }
                });
            });
        }
    };

    // google chart
    function drawChart(tabNo) {
        if ('object' === typeof window.google) {
            if (1 === tabNo) {
                if ($(ID('#', 'chart-countries')).length) {
                    chart.drawPie(ID('chart-countries'));
                    chart.drawLine(ID('chart-daily'), 'date');
                }
            } else if (5 === tabNo) {
                $(ID('.', 'network')).each(function (i, obj) {
//					chart.drawLine($(obj).attr('id'), 'datetime');
                    chart.drawStacked($(obj).attr('id'));
                });
            }
        }
    }

    function initChart(tabNo) {
        if ('object' === typeof window.google) {
            var packages = ['corechart'];
            if (5 === tabNo) {
                packages.push('bar');
            }
            window.google.charts.load('current', {
                packages: packages,
                callback: function () {
                    drawChart(tabNo);
                }
            });
            onresize('draw-chart.' + tabNo, function () {
                drawChart(tabNo);
            });
        }
    }

    // Load / Save cookie using wpCookies in wp-includes/js/utils.js
    function loadCookie(tabNo) {
        var i, cookie = (wpCookies !== undefined && wpCookies.getHash('ip-location-block')) || [];

        for (i in cookie) {
            if (cookie.hasOwnProperty(i)) {
                cookie[i] = cookie[i].replace(/[^ox\d]/ig, '').split(''); // string (ooo...) to array (n)
            }
        }

        if (cookie[tabNo] === undefined) {
            cookie[tabNo] = [];
        }

        return cookie;
    }

    // cookie[tabNo][n] = 1 charactor or 'o' if empty
    function saveCookie(cookie) {
        var j, n, c = [];

        $.each(cookie, function (i, obj) {
            c[i] = '';
            if (obj !== undefined) {
                n = obj.length;
                if (n) {
                    c[i] = (obj[0] || 'o').toString();
                    for (j = 1; j < n; ++j) {
                        c[i] += (obj[j] || 'o').toString();
                    }
                }
            }
        });

        // setHash( name, value, expires, path, domain, secure )
        if (wpCookies !== undefined) {
            wpCookies.setHash(
                'ip-location-block', c, new Date(Date.now() + 2592000000),
                // `/wp-admin/`, `/wp-admin/network/` or `/wp/wp-admin/`
                window.location.pathname.replace(/\\/g, '/').match(/.*\//) || ip_location_block_auth.home + ip_location_block_auth.admin
            );
        }
    }

    // Click event handler to show/hide form-table
    function toggleSection(title, tabNo, cookie) {
        var index = title.closest('fieldset').data('section'),
            body = title.parent().nextAll('.panel-body').toggle(), border;

        // Show/Hide
        title.toggleClass(ID('dropup')).toggleClass(ID('dropdown'));

        border = title.hasClass(ID('dropdown'));
        if (border) {
            body.addClass(ID('border')).trigger(ID('show-body'));
        } else {
            body.removeClass(ID('border'));
        }

        cookie[tabNo][index] = border ? 'o' : 'x';
        saveCookie(cookie);

        // redraw google chart
        drawChart(tabNo);
    }

    function manageSection(tabNo) {
        var cookie = loadCookie(tabNo);

        // Click event handler to show/hide form-table
        $(document).on('click', 'form>h2,h3', function (/*event*/) {
            toggleSection($(this), tabNo, cookie);
            return false;
        });

        // Toggle all
        $(ID('#', 'toggle-sections')).on('click', function (/*event*/) {
            var $this,
                title = $(ID('.', 'field')).find('h2,h3'),
                m = [ID('dropdown'), ID('dropup')],
                n = title.filter('.' + m[0]).length;

            // update cookie
            title.each(function (i) {
                $this = $(this);
                $this.removeClass(m.join(' ')).addClass(n ? m[1] : m[0]);
                $this = $this.parent().nextAll('.panel-body').toggle(n ? false : true);
                if (n) {
                    $this.removeClass(ID('border'));
                } else {
                    $this.addClass(ID('border')).trigger('show-body');
                }
                cookie[tabNo][i] = n ? 'x' : 'o';
            });

            // Save cookie
            saveCookie(cookie);

            // redraw google chart
            drawChart(tabNo);

            return false;
        });

        return cookie;
    }

    // form for export / import
    function add_hidden_form(cmd) {
        $('body').append(
            '<div style="display:none">' +
            '<form method="POST" id="' + ID('export-form') + '" action="' + ip_location_block.url.replace('ajax.php', 'post.php') + '">' +
            '<input type="hidden" name="action" value="' + ip_location_block.action + '" />' +
            '<input type="hidden" name="nonce" value="' + ip_location_block.nonce + '" />' +
            '<input type="hidden" name="cmd" value="' + cmd + '" />' +
            '<input type="hidden" name="data" value="" id="' + ID('export-data') + '"/>' +
            '<input type="submit" value="submit" />' +
            '</form>' +
            '<input type="file" name="settings" id="' + ID('file-dialog') + '" />' +
            '</div>'
        );
    }

    // Icons for 'Toggle section' and 'Toggle with non logged-in user'
    function add_icon(dfn, span, title, icon) {
        var i, j;
        i = dfn.cloneNode(false);
        i.setAttribute('title', stripTag(title));
        j = span.cloneNode(false);
        j.setAttribute('class', 'dashicons dashicons-' + icon);
        i.appendChild(j);
        return i;
    }

    /*--------------------------------------------------
     * DataTables for tab 1 (Statistics) and 4 (Logs)
     *--------------------------------------------------*/
    function initTable(tabNo, control, options, cookie) {
        // get page length from cookie
        var length = (Number(cookie[tabNo][1 === tabNo ? 3 : 2]) || 0);
        length = [10, 25, 50, 100][length];

        $.extend(true, $.fn.dataTable.defaults, options, {
            // DOM position (t:table, l:length menu, p:pagenate)
            dom: 'tlp',

            // Client behavior
            serverSide: false,
            autoWidth: false,
            processing: true,
            deferRender: true,

            // Language
            language: {
                emptyTable: ip_location_block.i18n[1],
                loadingRecords: ip_location_block.i18n[0],
                processing: ip_location_block.i18n[0],
                zeroRecords: ip_location_block.i18n[2],
                lengthMenu: '_MENU_',
                paginate: {
                    first: '&laquo;',
                    last: '&raquo;',
                    next: '&rsaquo;',
                    previous: '&lsaquo;'
                }
            },

            // Responsive
            responsive: {
                details: {
                    type: 'column',
                    target: 'td:nth-child(n+2)'
                }
            },

            columnDefs: [
                {width: '1.25em', targets: 0},
                {orderable: false, targets: 0},
                {searchable: false, targets: 0},
                {
                    targets: [0],
                    data: null,
                    defaultContent: '<input type="checkbox">'
                }
            ],

            // Pagenation, Page length (initial)
            pagingType: 'full_numbers', // or 'simple_numbers'
            lengthMenu: [10, 25, 50, 100],
            pageLength: length,

            // scroller
            scroller: true,
            scrollY: 10000, // prevent to change column width on click
            scrollCollapse: true, // fit the height of table

            // draw callback
            drawCallback: function (settings) {
                var elm = $(ID('#', control.tableID)).find('td.dataTables_empty'),
                    n = 'restore-logs' === control.ajaxCMD ? 3 : 2; // 2:restore-cache

                // avoid recursive call for ajax source
                // 1: thead, 2: empty tbody, 3: after loading data
                if (n > settings.iDraw) {
                    elm.html(ip_location_block.i18n[0]);
                } else if (n === settings.iDraw) {
                    // 'No data available in table'
                    elm.html(ip_location_block.i18n[1]);

                    elm = $(ID('@', 'search_filter'));
                    if (elm.val()) { // if a filter value exists in the text field
                        elm.trigger('keyup'); // then search the text
                    }
                }
            }
        });

        // Instantiate DataTables
        var table = $(ID('#', control.tableID)).DataTable({
                ajax: {
                    url: ip_location_block.url,
                    type: 'POST',
                    data: {
                        cmd: control.ajaxCMD,
                        action: ip_location_block.action,
                        nonce: ip_location_block.nonce
                    }
                },
                mark: true // https://github.com/julmot/datatables.mark.js/
            }),

            // redraw when column size changed
            redraw = function () {
                table.columns.adjust().responsive.recalc().draw(false); // full-hold
            };

        // draw when window is resized
        onresize('draw-table.' + tabNo, redraw);

        // Re-calculate the widths after panel-body is shown
        $(ID('#', control.sectionID)).find('.panel-body').off(ID('show-body')).on(ID('show-body'), function (/*event*/) {
            redraw();
            return false;
        })

            // Handle the event of checkbox in the title header for bulk action
            .off('change').on('change', 'th>input[type="checkbox"]', function (/*event*/) {
            var prop = $(this).prop('checked');
            $(ID('#', control.tableID)).find('td>input[type="checkbox"]').prop('checked', prop);
            return false;
        });

        // Select target (radio button)
        $(ID('#', 'select-target')).off('change').on('change', function (/*event*/) {
            var val = $(this).find('input[name="' + ID('target') + '"]:checked').val();
            // search only the specified column for selecting "Target"
            table.columns(control.targetColumn).search('all' !== val ? val : '').draw();
            return false;
        }).trigger('change');

        // Bulk action
        $(ID('#', 'bulk-action')).off('click').on('click', function (/*event*/) {
            var cmd = $(this).prev().val(),         // value of selected option
                texp = /(<([^>]+)>)/ig,              // regular expression to strip tag
                hexp = /data-hash=[\W]([\w]+)[\W]/i, // regular expression to extract hash
                data = {IP: [], AS: []},           // IP address and AS number
                hash, cell, cells = table.$('input:checked'); // checked entry in table

            if (!cmd) {
                return false;
            } else if (!cells.length) {
                warning(null, ip_location_block.msg[10]);
                return false;
            }

            cells.each(function (/*index*/) {
                cell = table.cell(this.parentNode).data();

                // hash for anonymized IP address
                // ex:＜span＞＜a href="#!" data-hash="abcdef0123456789"＞123.456.789.***＜/a＞＜/span＞
                if ('bulk-action-remove' === cmd || 'bulk-action-ip-erase' === cmd) {
                    hash = cell[control.columnIP].match(hexp);
                    hash = hash ? ',' + hash[1] : '';
                } else {
                    hash = '';
                }

                data.IP.push(cell[control.columnIP].replace(texp, '') + hash);
                data.AS.push(cell[control.columnAS].replace(texp, ''));
            });

            if (data.IP.length) {
                ajax_post('loading', {
                    cmd: cmd,
                    which: data
                }, function (data) {
                    if (data.page !== undefined) {
                        redirect(data.page, 'tab=' + tabNo);
                    } else if (data) {
                        table.ajax.reload();
                        $(ID('#', control.tableID)).find('th input[type="checkbox"]').prop('checked', false);
                    }
                });
            }

            return false;
        });

        // Search filter
        $(ID('@', 'search_filter')).off('keyup').on('keyup', function (/*event*/) {
            table.search(this.value, false, true, !/[A-Z]/.test(this.value)).draw();
            return false;
        });

        // Reset filter
        $(ID('#', 'reset-filter')).off('click').on('click', function (/*event*/) {
            $(ID('@', 'search_filter')).val('');
            table.search('').draw();
            return false;
        });

        // Clear all
        $(ID('@', 'clear_all')).off('click').on('click', function (/*event*/) {
            confirm(ip_location_block.msg[0], function () {
                ajax_clear(tabNo === 1 ? 'cache' : 'logs', null);
            });
            return false;
        });

        // Jump to search tab with opening a new window
        // @note: `click` cannot be `off` because it's a root.
        $('table' + ID('.', 'dataTable') + ' tbody').on('click', 'a', function (/*event*/) {
            var p = window.location.search.slice(1).split('&'),
                n = p.length, q = {}, i, j;

            for (i = 0; i < n; ++i) {
                j = p[i].split('=');
                q[j[0]] = j[1];
            }

            // additional query
            q.tab = tabNo === 1 ? 4 : 2;
            q.s = $(this).text().replace(/[^\w\.\:\*]/, '');

            j = [];
            for (i in q) {
                if (q.hasOwnProperty(i)) {
                    j.push(i + '=' + q[i]);
                }
            }

            window.open(window.location.pathname + '?' + j.join('&'), '_blank');
            return false;
        });

        // save cookie when length menu is changed
        $(ID('#', control.tableID)).on('length.dt', function (e, settings, len) {
            cookie[tabNo][1 === tabNo ? 3 : 2] = ({10: 0, 25: 1, 50: 2, 100: 3}[len] || 0);
            saveCookie(cookie);
        });

        return table;
    }

    $(function () {
        // Get tab number
        var tabNo = ip_location_block && ip_location_block.hasOwnProperty('tab') ? Number(ip_location_block.tab) : -1;
        // Attach event handler and manage cookie
        var cookie = manageSection(tabNo);

        // Inhibit to submit by return key
        $(ID('.', 'inhibit')).on('submit', function () {
            return false;
        });

        // Prevent warning when page transition happens during ajax
        $(window).on('beforeunload', function (/*event*/) {
            skip_error = true;
            return;
        });

        // Register event handler at specific tab
        switch (tabNo) {
            /*----------------------------------------
             * Settings
             *----------------------------------------*/
            case 0:
                // Name of base class
                var name = ID('%', 'settings');

                /*--------------------------------
                 * Validation rules and behavior
                 *--------------------------------*/
                // Scan your country code
                $('[id^="' + ID('scan-') + '"]').on('click', function (/*event*/) {
                    var $this = $(this),
                        id = $this.attr('id'),
                        parent = $this.parent();

                    ajax_post(id.replace(/^.*(?:scan)/, 'scanning'), {
                        cmd: 'scan-code',
                        which: id.replace(ID('scan-'), '')
                    }, function (data) {
                        if (!parent.children('ul').length) {
                            parent.append('<ul id="' + ID('code-list') + '"></ul>');
                        }
                        parent = parent.children('ul').empty();

                        var key, val;
                        for (key in data) {
                            if (data.hasOwnProperty(key)) {
                                key = stripTag(key);
                                if ('string' === typeof data[key]) {
                                    val = stripTag(data[key]);
                                } else {
                                    val = stripTag(data[key].code);
                                    key = '<abbr title="' + stripTag(data[key].type) + '">' + key + '</abbr>';
                                }
                                parent.append('<li>' + key + ' : <span class="' + ID('notice') + '">' + val + '</span></li>');
                            }
                        }
                        parent.show('slow');
                    });

                    return false; //
                });

                // Matching rule
                $(ID('@', 'matching_rule')).on('change', function () {
                    var value = this.value;
                    $(ID('@', 'white_list')).closest('tr').toggle(value === '0');
                    $(ID('@', 'black_list')).closest('tr').toggle(value === '1');
                    return false;
                }).change();

                // CIDR calculator
                $(ID('.', 'icon-cidr')).on('click', function () {
                    var src = $(ID('#', 'admin-styles-css')).get(0).href,
                        win = window.open('about:blank', '', 'width=560,height=170'); // menubar=no,toolbar=no,location=no
                    src = src.slice(0, src.lastIndexOf('css/'));
                    win.document.write(
                        '<!DOCTYPE html>' +
                        '<html lang=en>' +
                        '<meta charset=utf-8>' +
                        '<title>CIDR calculator for IPv4 / IPv6</title>' +
                        '<link href="' + src + 'css/cidr.min.css?v=.1" rel=stylesheet>' +
                        '<div class="row container"><div class=row id=i><fieldset class="col span_11"><legend>Range <input id=a type=button value=Clear tabindex=1></legend><textarea id=c name=range placeholder="192.168.0.0 - 192.168.255.255" rows=5 wrap=off tabindex=2></textarea></fieldset><ul class="col span_2" id=h><li class=row><input id=e type=button value=&rarr; class="col span_24" tabindex=3><li class=row><input id=f type=button value=&larr; class="col span_24" tabindex=6></ul><fieldset class="col span_11"><legend>CIDR <input id=b type=button value=Clear tabindex=4></legend><textarea id=d name=cidr placeholder=192.168.0.0/16 rows=5 wrap=off tabindex=5></textarea></fieldset></div><div class=row id=j><span class=col id=g> </span></div></div>' +
                        /*					'<div class="container row"><div class="row" id="top"><fieldset class="col span_11"><legend>Range <input type="button" id="r_clear" value="Clear" tabindex="1" /></legend><textarea name="range" id="r_text" rows="5" wrap="off" placeholder="192.168.0.0 - 192.168.255.255" tabindex="2"></textarea></fieldset><ul class="col span_2" id="b_conv"><li class="row"><input class="col span_24" type="button" id="r_conv" value="&rarr;" tabindex="3" /></li><li class="row"><input class="col span_24" type="button" id="c_conv" value="&larr;" tabindex="6" /></li></ul><fieldset class="col span_11"><legend>CIDR <input type="button" id="c_clear" value="Clear" tabindex="4" /></legend><textarea name="cidr" id="c_text" rows="5" wrap="off" placeholder="192.168.0.0/16" tabindex="5"></textarea></fieldset></div><div class="row" id="bottom"><span class="col" id="msg">&nbsp;</span></div></div>' +*/
                        '<script src="' + src + 'js/cidr.min.js?v=.1"></script>'
                    );
                    win.document.close();
                    return false;
                });

                // Show/Hide folding list at prevent malicious upload
                $(ID('@', 'validation_mimetype')).on('change', function (event) {
                    var $this = $(this),
                        stat = Number($this.val());
                    $this.nextAll(ID('.', 'settings-folding')).each(function (i, obj) {
                        fold_elements($(obj), (stat === i + 1) || (stat && 2 === i));
                    });
                    return stopPropergation(event);
                }).change();

                // Response message and Redirect URL
                $('select[name*="response_code"]').on('change', function (event) {
                    var $this = $(this),
                        res = parseInt($this.val() / 100, 10),
                        elm = $this.closest('tr').nextAll('tr');

                    // only for Front-end target settings
                    if (0 <= $this.attr('name').indexOf('public')) {
                        if (-1 === Number($(ID('@', 'public_matching_rule')).val())) {
                            elm.each(function (index) {
                                if (1 >= index) {
                                    $(this).hide();
                                }
                            });
                            return stopPropergation(event);
                        }
                    }

                    if (res <= 3) { // 2xx, 3xx
                        elm.each(function (index) {
                            if (0 === index) {
                                $(this).show();
                            } // redirect_uri
                            else if (1 === index) {
                                $(this).hide();
                            } // response_msg
                        });
                    } else { // 4xx, 5xx
                        elm.each(function (index) {
                            if (0 === index) {
                                $(this).hide();
                            } // redirect_uri
                            else if (1 === index) {
                                $(this).show();
                            } // response_msg
                        });
                    }
                    return stopPropergation(event);
                }).change();

                // Decode
                $(ID('#', 'decode')).on('click', function (/*event*/) {
                    var elm = $(ID('@', 'signature')),
                        str = elm.val();
                    if (str.search(/,/) === -1) {
                        elm.val(decode_str(str));
                    } else {
                        elm.val(encode_str(str));
                    }
                    return false;
                });

                /*---------------------------
                 * Back-end target settings
                 *---------------------------*/
                // Show/Hide folding list at Login form
                $(ID('@', 'validation_login')).on('change', function (event) {
                    var $this = $(this);
                    show_descendants($this, $this, name, true);
                    return stopPropergation(event);
                }).change();

                // Exceptions for Admin ajax/post
                ajax_post(null, {
                    cmd: 'get-actions'
                }, function (data) {
                    var i, j, id, key, $this = $(ID('#', 'list-admin')),
                        li = document.createElement('li'),
                        input = document.createElement('input'),
                        label = document.createElement('label'),
                        dfn = document.createElement('dfn'),
                        span = document.createElement('span');

                    for (key in data) {
                        if (data.hasOwnProperty(key)) {
                            key = stripTag(key);
                            id = ID('!', 'exception_admin_' + key.replaceAll('/', '_'));
                            if (!$this.find('#' + id).size()) {
                                i = input.cloneNode(false);
                                i.setAttribute('id', id);
                                i.setAttribute('value', key);
                                i.setAttribute('type', 'checkbox');
                                j = li.cloneNode(false);
                                j.appendChild(i);

                                i = label.cloneNode(false);
                                i.setAttribute('for', id);
                                i.appendChild(document.createTextNode(key));
                                j.appendChild(i);

                                if (1 & data[key]) {
                                    j.appendChild(add_icon(dfn, span, ip_location_block.msg[5], 'lock'));
                                }
                                if (2 & data[key]) {
                                    j.appendChild(add_icon(dfn, span, ip_location_block.msg[6], 'unlock'));
                                }

                                $this.append(j);
                            }
                        }
                    }

                    // Admin ajax/post: `Toggle non logged-in user` at `Exceptions`
                    $(ID('.', 'icon-unlock')).on('click', function (/*event*/) {
                        $(ID('#', 'list-admin') + '>li').filter(function (/*i, elm*/) {
                            return !$(this).find('.dashicons-unlock').length;
                        }).toggle();
                        return false;
                    });

                    // Admin ajax/post: Handle text field for actions
                    $(ID('@', 'exception_admin')).on('change', function (event) {
                        var actions = $.grep($(this).val().split(','), function (e) {
                            return '' !== e.replace(/^\s+|\s+$/g, ''); // remove empty element
                        });

                        $(ID('#', 'list-admin')).find('input').each(function (/*i, obj*/) {
                            var $this = $(this),
                                action = $this.val();
                            $this.prop('checked', -1 !== $.inArray(action, actions));
                        });

                        return stopPropergation(event);
                    }).change();

                    // Admin ajax/post: Candidate actions
                    $(ID('#', 'list-admin')).on('click', 'input', function (/*event*/) {
                        var i,
                            $this = $(this),
                            $text = $(ID('@', 'exception_admin')),
                            action = $this.val(),
                            actions = $.grep($text.val().split(','), function (e) {
                                return '' !== e.replace(/^\s+|\s+$/g, ''); // remove empty element
                            });

                        // find the action
                        i = $.inArray(action, actions);

                        if (-1 === i) {
                            actions.push(action);
                        } else {
                            actions.splice(i, 1);
                        }

                        $text.val(actions.join(',')).change();
                    });

                    // Admin ajax/post: Find the blocked request in logs
                    $(ID('.', 'icon-find')).on('click', function (/*event*/) {
                        var $this = $(this),
                            list = [], n = 0, key, ext, id, s,
                            title = stripTag(ip_location_block.msg[8]),
                            target = stripTag($this.data('target')); // `admin`, `plugins`, `themes`

                        // show description
                        $(ID('#', 'find-' + target)).empty();
                        $this.next().children(ID('.', 'find-desc')).show();

                        // make list of target
                        $this = $(ID('#', 'list-' + target));
                        $this.children('li').each(function (i, obj) {
                            list.push($(obj).find('input').val());
                        });

                        ajax_post('find-' + target, {
                            cmd: 'find-' + target
                        }, function (data) {
                            var val;
                            for (val in data) {
                                if (data.hasOwnProperty(val)) {
                                    ++n;
                                    key = stripTag(data[val]);  // page, action, plugins, themes
                                    val = stripTag(val);        // slug of target
                                    ext = $.inArray(val, list); // slug already exists
                                    id = ID('!', 'exception_' + target + '_' + val);

                                    // make an anchor tab with search query
                                    s = 'admin' === target ? (key + ' ' + val) : ('/' + key + '/' + val + '/');
                                    s = '<a class="ip-location-block-icon ip-location-block-icon-alert" href="' + ip_location_block_auth.sites[0] + ip_location_block_auth.admin + 'options-general.php' + // only main site
                                        '?page=ip-location-block&tab=4&s=' + encodeURIComponent(s) + '" title="' + title.replace('%s', s) + '" target="_blank"><span></span></a>';

                                    // add a new list when not found in existent key
                                    if (ext < 0) {
                                        list.push(val);
                                        $this.prepend(
                                            '<li><input id="' + id + '" value="' + val + '" type="checkbox" '
                                            + ('admin' === target ? '/>' : 'name=ip_location_block_settings[exception][' + target + '][' + val + '] />')
                                            + '<label for="' + id + '">' + val + '</lable>' + s + '</li>'
                                        );
                                    }

                                    // append button when found in existent key
                                    else {
                                        id = $this.find('#' + id).parent();
                                        if (!id.find('a').length) {
                                            id.append(s);
                                        }
                                    }
                                }
                            }

                            // update status of checkbox
                            $(ID('@', 'exception_' + target)).trigger('change');
                            $(ID('#', 'find-' + target)).append(
                                ' ' + '<span class="ip-location-block-warn">' + stripTag(ip_location_block.msg[7].replace('%d', n)) + '</span>'
                            );
                        });

                        return false;
                    });
                });

                // Admin ajax/post: Enable / Disable Exceptions
                $('input[id^="' + ID('!', 'validation_ajax_') + '"]').on('change', function (/*event*/) {
                    show_folding_ajax($(this));
                }).change();

                /*---------------------------
                 * Front-end target settings
                 *---------------------------*/
                // Enable / Disable for Public facing pages
                $(ID('@', 'validation_public')).on('change', function (event) {
                    set_front_end($(this));
                    return stopPropergation(event);
                }).change();

                // Matching rule on front-end
                $(ID('@', 'public_matching_rule')).on('change', function (event) {
                    var value = this.value;
                    $(ID('@', 'public_white_list')).closest('tr').toggle(value === '0');
                    $(ID('@', 'public_black_list')).closest('tr').toggle(value === '1');
                    $(ID('@', 'public_response_code')).change().closest('tr').toggle(value !== '-1');
                    return stopPropergation(event);
                }).change();

                // Badly-behaved bots and crawlers
                $(ID('@', 'public_behavior')).on('change', function (event) {
                    var $this = $(this);
                    fold_elements($this.siblings('ul'), $this.prop('checked'));
                    return stopPropergation(event);
                }).change();

                /*---------------------------
                 * Local database settings
                 *---------------------------*/
                // Update local database
                $(ID('@', 'update')).on('click', function (/*event*/) {
                    ajax_post('download', {
                        cmd: 'download'
                    }, function (res) {
                        var api, key, data;
                        for (api in res) {
                            if (res.hasOwnProperty(api)) {
                                data = res[api];
                                for (key in data) { // key: ipv4, ipv6
                                    if (data.hasOwnProperty(key)) {
                                        key = stripTag(key);
                                        if (data[key].filename) {
                                            $(ID('@', api + '_' + key + '_path')).val(stripTag(data[key].filename));
                                        }
                                        if (data[key].message) {
                                            $(ID('#', api + '-' + key)).text(stripTag(data[key].message));
                                        }
                                    }
                                }
                            }
                        }
                    });

                    return false;
                });

                /*--------------------------------
                 * Privacy and record settings
                 *--------------------------------*/
                $(ID('@', 'restrict_api')).on('change', function (/*event*/) {
                    $('input[class*="remote"]').prop('disabled', $(this).prop('checked'));
                }).trigger('change');

                $(ID('@', 'save_statistics')).on('change', function (/*event*/) {
                    $(ID('@', 'validation_recdays')).prop('disabled', !$(this).prop('checked'));
                    return false;
                }).trigger('change');

                $(ID('@', 'validation_reclogs')).on('change', function (/*event*/) {
                    var $this = $(this);
                    $this.parent().parent().nextAll().find('input[id*="validation"]').prop('disabled', 0 === Number($this.prop('selectedIndex')));
                }).trigger('change');

                $(ID('@', 'cache_hold')).on('change', function (/*event*/) {
                    var checked = $(this).prop('checked');
                    $('input[name*="[cache_time]"]').prop('disabled', !checked);
                    $('select[id*="login_fails"]').prop('disabled', !checked);
                }).trigger('change');

                /*---------------------------
                 * Submission settings
                 *---------------------------*/
                $(ID('@', 'comment_pos')).on('change', function (/*event*/) {
                    var $this = $(this);
                    $this.nextAll('input[type="text"]').prop('disabled', 0 === Number($this.prop('selectedIndex')));
                }).trigger('change');

                /*---------------------------
                 * Plugin settings
                 *---------------------------*/
                // Export / Import settings
                add_hidden_form('validate');

                // Export settings
                $(ID('#', 'export')).on('click', function (/*event*/) {
                    if (JSON === undefined) {
                        notice_html5();
                        return false;
                    }

                    var id = name, json = {};
                    $.each($(this).closest('form').serializeArray(), function (i, obj) {
                        if (-1 !== obj.name.indexOf(id)) {
                            json[obj.name] = obj.value;
                        }
                    });

                    json[id += '[signature]'] = encode_str(json[id]);
                    $(ID('#', 'export-data')).val(JSON.stringify(json));
                    $(ID('#', 'export-form')).submit();

                    return false;
                });

                // Import settings
                $(ID('#', 'file-dialog')).on('change', function (event) {
                    if (window.FileReader === undefined) {
                        notice_html5();
                        return false;
                    }

                    var id, file = event.target.files[0];
                    if (file) {
                        readfile(file, function (data) {
                            data = JSON.parse(data);
                            id = name + '[signature]';
                            if (data[id] !== undefined) {
                                data[id] = encode_str(data[id]);
                            }
                            ajax_post('export-import', {
                                cmd: 'validate',
                                data: JSON.stringify(data)
                            }, function (data) {
                                deserialize_json(data, true);
                            });
                        });
                    }

                    return false;
                });

                $(ID('#', 'import')).on('click', function (/*event*/) {
                    $(ID('#', 'file-dialog')).click();
                    return false;
                });

                // Import pre-defined settings
                $(ID('#', 'default')).on('click', function (/*event*/) {
                    confirm(ip_location_block.msg[0], function () {
                        ajax_post('pre-defined', {
                            cmd: 'import-default'
                        }, function (data) {
                            deserialize_json(data, true);
                        });
                    });
                    return false;
                });

                $(ID('#', 'preferred')).on('click', function (/*event*/) {
                    confirm(ip_location_block.msg[0], function () {
                        ajax_post('pre-defined', {
                            cmd: 'import-preferred'
                        }, function (data) {
                            deserialize_json(data, false);
                        });
                    });
                    return false;
                });

                // Reset data source for live log
                $(ID('@', 'reset_live')).on('click', function (/*event*/) {
                    ajax_post('reset-live', {
                        cmd: 'reset-live'
                    });
                    return false;
                });

                // Emergency login link
                $(ID('#', 'login-link')).on('click', function (/*event*/) {
                    var $this = $(this), type = ID('$', 'primary');
                    if ($this.hasClass(type)) {
                        ajax_post('login-loading', {
                            cmd: 'generate-link'
                        }, function (data) {
                            $this.text(ip_location_block.msg[3]);
                            $this.removeClass(type).nextAll(ID('.', 'desc')).remove();
                            $('<p class="ip-location-block-desc"></p>')
                                .appendTo($this.parent())
                                .append(
                                    ip_location_block.msg[4],
                                    '<a href="' + data.link + '" title="' + ip_location_block.msg[1] + '" target=_blank>' + data.link + '</a></p>'
                                );
                        });
                    } else {
                        confirm(ip_location_block.msg[0], function () {
                            ajax_post('login-loading', {
                                cmd: 'delete-link'
                            }, function (/*data*/) {
                                $this.text(ip_location_block.msg[2]);
                                $this.addClass(type).nextAll(ID('.', 'desc')).remove();
                            });
                        });
                    }
                    return false;
                });

                // Emergency login link
                $(ID('#', 'migrate')).on('click', function (/*event*/) {
                    var $this = $(this), type = ID('$', 'primary');

                    ajax_post('migrate-loading', {
                        cmd: 'migrate-from-legacy'
                    }, function (data) {
                        if (data.hasOwnProperty('message')) {
                            alert(data.message);
                            if (data.success) {
                                window.location.href = window.location.href;
                            }
                        } else {
                            alert('HTTP Error.');
                        }
                    });

                    return false;
                });

                // Manipulate DB table for validation logs
                $(ID('@', 'diag_tables')).on('click', function (/*event*/) {
                    confirm(ip_location_block.msg[0], function () {
                        ajax_table('diag-tables');
                    });
                    return false;
                });

                // Show WordPress installation info
                $(ID('#', 'show-info')).on('click', function (/*event*/) {
                    $(ID('#', 'wp-info')).empty();
                    ajax_post('wp-info', {
                        cmd: 'show-info'
                    }, function (data) {
                        var key, res = [];
                        for (key in data) {
                            if (data.hasOwnProperty(key)) {
                                res.push('- ' + key + ' ' + data[key]);
                            }
                        }

                        // response should be escaped at server side
                        $(ID('#', 'wp-info')).html('<textarea class="regular-text code" rows="' + res.length + '">' + /*stripTag*/(res.join("\n")) + '</textarea>').find('textarea').select();
                        return false;
                    });
                });

                /*---------------------------
                 * Common event handler
                 *---------------------------*/
                // Show/Hide description (this change event hander should be at the last)
                $('select[name^="' + name + '"]').on('change', function (/*event*/) {
                    var $this = $(this);
                    show_description($this);
                    show_descendants($this, $this, name, true);
                    return false;
                }).change();

                // Toggle checkbox
                $(ID('.', 'icon-cycle')).on('click', function (/*event*/) {
                    var $that = $(this).nextAll('li'), actions,
                        text = $that.find(ID('@', 'exception_admin')),
                        cbox = $that.find('input:checkbox').filter(':visible'),
                        stat = cbox.filter(':checked').length;

                    cbox.prop('checked', !stat);

                    if (text.length) {
                        if (stat) {
                            text.val('');
                        } else {
                            actions = [];
                            cbox.each(function (i, obj) {
                                actions.push($(obj).val());
                            });
                            text.val(actions.join(','));
                        }
                    }

                    $(this).blur(); // unfocus anchor tag
                    return false;
                });

                // Folding list
                $(ID('.', 'settings-folding>dfn')).on('click', function (/*event*/) {
                    var drop = ID('drop'),
                        $this = $(this).parent();
                    $this.children(ID('.', 'hide')).toggle();
                    $this.toggleClass(drop + 'up').toggleClass(drop + 'down');

                    if ($this.hasClass(drop + 'up')) {
                        $this.children('div').hide();
                    }

                    return false;
                });

                // Submit
                $('#submit').on('click', function (/*event*/) {
                    var elm = $(ID('@', 'signature')), str = elm.val();
                    if (str.indexOf(',') !== -1) {
                        elm.val(encode_str(str));
                    }
                    return true;
                });
                break;

            /*----------------------------------------
             * Statistics
             *----------------------------------------*/
            case 1:
                // https://developers.google.com/loader/#Dynamic
                initChart(tabNo);

                // Toggle sorting order
                var order = 0;
                $(ID('#', 'sort-slug')).on('click', function (/*event*/) {
                    var $ol = $(this).closest('ol'),
                        $li = $ol.children('li');

                    // toggle sorting
                    order = !order;
                    if (order) {
                        $li.sort(function (a, b) {
                            return $(a).text() > $(b).text();
                        });
                    } else {
                        $li.sort(function (a, b) {
                            return Number($(a).text().replace(/^.*\((\d+)\)$/, '$1')) <= Number($(b).text().replace(/^.*\((\d+)\)$/, '$1'));
                        });
                    }

                    $ol.children('li').remove();
                    $li.appendTo($ol);
                });

                // Statistics of validation
                $(ID('@', 'clear_statistics')).on('click', function (/*event*/) {
                    confirm(ip_location_block.msg[0], function () {
                        ajax_clear('statistics', null);
                    });
                    return false;
                });

                // Statistics in logs
                $(ID('@', 'clear_logs')).on('click', function (/*event*/) {
                    confirm(ip_location_block.msg[0], function () {
                        ajax_clear('logs', null);
                    });
                    return false;
                });

                // IP address in cache
                initTable(tabNo, {
                    tableID: 'statistics-cache',
                    ajaxCMD: 'restore-cache',
                    sectionID: 'section-2',
                    targetColumn: 4,
                    columnIP: 1,
                    columnAS: 3
                }, {
                    columns: [
                        {title: '<input type="checkbox">'}, // 0 checkbox
                        {title: ip_location_block.i18n[3]}, // 1 IP address
                        {title: ip_location_block.i18n[4]}, // 2 Country code
                        {title: ip_location_block.i18n[5]}, // 3 AS number
                        {title: ip_location_block.i18n[6]}, // 4 Host name
                        {title: ip_location_block.i18n[7]}, // 5 Target
                        {title: ip_location_block.i18n[8]}, // 6 Login fail/Call
                        {title: ip_location_block.i18n[9]}  // 7 Elapsed[sec]
                    ],
                    columnDefs: [
                        {responsivePriority: 0, targets: 0}, // checkbox
                        {responsivePriority: 1, targets: 1}, // IP address
                        {responsivePriority: 2, targets: 2}, // Country code
                        {responsivePriority: 6, targets: 3}, // AS number
                        {responsivePriority: 7, targets: 4}, // Host name
                        {responsivePriority: 3, targets: 5}, // Target
                        {responsivePriority: 4, targets: 6}, // Login fail/Call
                        {responsivePriority: 5, targets: 7}, // Elapsed[sec]
                        {className: "all", targets: [0, 1, 2, 5]} // always visible
                    ]
                }, cookie);

                // Export cache
                add_hidden_form('export-cache');

                // Export cache
                $(ID('#', 'export-cache')).on('click', function (/*event*/) {
                    $(ID('#', 'export-form')).submit();
                    return false;
                });
                break;

            /*----------------------------------------
             * Logs
             *----------------------------------------*/
            case 4:
                // Validation logs
                var control = {
                        tableID: 'validation-logs',
                        sectionID: 'section-0',
                        targetColumn: 8,
                        columnIP: 3,
                        columnAS: 7
                    },

                    options = {
                        columns: [
                            {title: '<input type=\"checkbox\">'}, //  0 checkbox
                            {title: ''}, //  1 Time (raw)
                            {title: ip_location_block.i18n[12]}, //  2 Date
                            {title: ip_location_block.i18n[3]}, //  3 IP address
                            {title: ip_location_block.i18n[4]}, //  4 Country code
                            {title: ip_location_block.i18n[5]}, //  5 City
                            {title: ip_location_block.i18n[6]}, //  6 State
                            {title: ip_location_block.i18n[7]}, //  7 AS number
                            {title: ip_location_block.i18n[9]}, //  8 Target
                            {title: ip_location_block.i18n[13]}, //  9 Result
                            {title: ip_location_block.i18n[14]}, //  10 Request
                            {title: ip_location_block.i18n[15]}, //  11 User agent
                            {title: ip_location_block.i18n[16]}, // 12 HTTP headers
                            {title: ip_location_block.i18n[17]}  // 13 $_POST data
                        ],
                        columnDefs: [
                            {responsivePriority: 11, targets: 0}, // checkbox
                            {responsivePriority: 0, targets: 1}, // Time (raw)
                            {responsivePriority: 1, targets: 2}, // Date
                            {responsivePriority: 2, targets: 3}, // IP address
                            {responsivePriority: 3, targets: 4}, // Country code
                            {responsivePriority: 4, targets: 5}, // City
                            {responsivePriority: 5, targets: 6}, // State
                            {responsivePriority: 6, targets: 7}, // AS number
                            {responsivePriority: 4, targets: 8}, // Target
                            {responsivePriority: 5, targets: 9}, // Result
                            {responsivePriority: 7, targets: 10}, // Request
                            {responsivePriority: 8, targets: 11}, // User agent
                            {responsivePriority: 9, targets: 12}, // HTTP headers
                            {responsivePriority: 10, targets: 13}, // $_POST data
                            {visible: false, targets: 1}, // always hidden
                            {className: "all", targets: parseInt(ip_location_block.native) ? [0, 2, 3, 4, 5, 6] : [0, 2, 3, 4]}, // always visible
                            {className: "none", targets: parseInt(ip_location_block.native) ? [10, 11, 12, 13] : [5, 6, 10, 11, 12, 13]}  // always hidden
                        ]
                    },

                    // Timer for Live update and DataTables
                    timer_start = null,
                    timer_pause = null,
                    $timer_pause = $(ID('#', 'live-loading')),
                    table = null,

                    // Controler functions for Live update
                    clear_timer = function () {
                        if (timer_start) {
                            window.clearTimeout(timer_start);
                            timer_start = null;
                        }
                        if (timer_pause) {
                            $timer_pause.removeClass(ID('live-timer'));
                            window.clearTimeout(timer_pause);
                            timer_pause = null;
                        }
                    },
                    live_start = function (cmd) {
                        clear_timer();
                        ajax_post(cmd === undefined ? 'live-loading' : null, {
                            cmd: 'live-start'
                        }, function (res) {
                            if (res.error) {
                                warning(null, res.error);
                            } else if (res.data.length) {
                                var i, n = res.data.length;
                                for (i = 0; i < n; i++) {
                                    table.row.add(res.data[i]); // `raws.add()` doesn't work because it needs js literal object.
                                }
                                table.draw(false); // the current page will still be shown.
                            }
                            if (cmd === undefined) {
                                // keep updating
                                timer_start = window.setTimeout(live_start, ip_location_block.interval * 1000);
                            }
                        });
                    },
                    live_stop = function (cmd, callback) {
                        live_start(false); // once read out the buffer
                        ajax_post(null, {
                            cmd: cmd || 'live-stop',
                            callback: callback
                        });
                    },
                    live_pause = function () {
                        live_stop('live-pause', function () {
                            $timer_pause.addClass(ID('live-timer'));
                            timer_pause = window.setTimeout(function () {
                                $(ID('#', 'live-log-stop')).prop('checked', true);
                                live_stop();
                            }, ip_location_block.timeout * 1000);
                        });
                    },

                    // animation on update
                    new_class = ID(''),
                    add_class = function (row, data, prefix) {
                        if (is_blocked(data[9 /* result */])) {
                            $(row).addClass(new_class + prefix + 'blocked');
                        } else {
                            $(row).addClass(new_class + prefix + 'passed');
                        }
                    },

                    // Live update controler and mode
                    $cntl = $(ID('#', 'live-log')),
                    $mode = $(ID('#', 'live-update'));

                // Remove animation class when animation finished
                $(ID('#', 'validation-logs')).on('animationend', function (/*event*/) {
                    $(this).find('tr[class*="' + new_class + 'new' + '"]').each(function (/*index, obj*/) {
                        var $this = $(this);
                        if (-1 !== $this.prop('class').indexOf('passed')) {
                            $this.addClass(new_class + 'passed').removeClass(new_class + 'new-passed');
                        } else {
                            $this.addClass(new_class + 'blocked').removeClass(new_class + 'new-blocked');
                        }
                    });
                    return false;
                });

                // Control of live update
                $cntl.on('change', function (/*event*/) {
                    switch ($('input[name="' + ID('live-log') + '"]:checked').val()) {
                        case 'start':
                            live_start();
                            break;
                        case 'pause':
                            live_pause();
                            break;
                        case 'stop':
                            live_stop();
                            break;
                    }
                });

                // on change mode
                $mode.on('change', function (/*event*/) {
                    var $tr = $cntl.closest('tr'),
                        mode = $mode.prop('checked'); // checked in `display_plugin_admin_page()`

                    cookie[tabNo][1] = mode ? 'o' : 'x';
                    saveCookie(cookie);

                    // Delete old DataTables
                    if (table) {
                        table.clear().destroy();
                    }

                    if (mode) {
                        $tr.show().next().next().next().nextAll().hide();
                        control.ajaxCMD = 'live-stop';
                        options.order = [1, 'desc']; // sort by `Time (raw)`
                        options.createdRow = function (row, data/*, index*/) {
                            add_class(row, data, 'new-');
                        };
                    } else {
                        $tr.hide().next().next().next().nextAll().show();
                        control.ajaxCMD = 'restore-logs';
                        options.order = [0, ''];
                        options.createdRow = function (row, data/*, index*/) {
                            add_class(row, data, '');
                        };
                    }

                    // Re-initialize DataTables
                    $(ID('#', 'live-log-stop')).trigger('click');
                    table = initTable(tabNo, control, options, cookie);
                    return false;
                }).trigger('change');

                // Preset filters
                $(ID('#', 'logs-preset')).on('click', 'a', function (/*event*/) {
                    var value = $(this).data('value');
                    $(ID('@', 'search_filter')).val(value);
                    table.search(value, false, true, !/[A-Z]/.test(value)).draw();
                    return false;
                });

                // Export logs
                add_hidden_form('export-logs');

                // Export logs
                $(ID('#', 'export-logs')).on('click', function (/*event*/) {
                    $(ID('#', 'export-form')).submit();
                    return false;
                });
                break;

            /*----------------------------------------
             * Search
             *----------------------------------------*/
            case 2:

                // Initialize the leaflet map.
                var LocationMapMarkers = [];
                var LocationMap = L.map(ID('#', 'map').replace('#', '')).setView([0, 0], 1);
                L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
                }).addTo(LocationMap);


                // Set selected provider to cookie
                var options = [];
                $('select[id^="' + ID('!', 'service') + '"]').on('change', function (/*event*/) {
                    // make selected options in cookie
                    $(this).children('option').each(function (i, elm) {
                        options[$(elm).text()] = i;
                        cookie[tabNo][3 + i] = $(elm).prop('selected') ? 'o' : 'x';
                    });

                    // if selected options does not include the focused index, update it
                    if ('o' !== cookie[tabNo][3 + (Number(cookie[tabNo][2]) || 0)]) {
                        cookie[tabNo][2] = $(this).prop('selectedIndex');
                    }

                    saveCookie(cookie);
                }).change();

                // Search Geolocation
                $(ID('@', 'get_location')).on('click', function (/*event*/) {
                    var whois = $(ID('#', 'whois')),
                        apis = $(ID('#', 'apis')),
                        list = $(ID('@', 'service')).val(), obj;

                    var ip = $(ID('@', 'ip_address')).val();
                    ip = ip ? ip.trim() : ip;

                    if (ip && list) {
                        // Anonymize IP address
                        if ($(ID('@', 'anonymize')).prop('checked')) {
                            if (/[^0-9a-f\.:]/.test(ip)) {
                                warning(null, 'illegal format.');
                                return false;
                            } else if (ip.indexOf('.') !== -1) { // in case of IPv4
                                ip = ip.replace(/\.\w+$/, '.0');
                            } else { // in case of IPv6
                                ip = ip.split(':'); // 2001:db80:abcd:0012:0000:0000:0000:0000
                                ip = ip.splice(0, 4).join(':');
                                if (-1 === ip.indexOf('::')) {
                                    ip += '::'; // mask the interface ID
                                }
                                ip = ip.replace(/:{3,}/, '::');
                            }
                            $(ID('@', 'ip_address')).val(ip);
                        }

                        whois.hide().empty();
                        apis.hide().empty();

                        // Reset map and markers
                        LocationMap.setView([0, 0], 1);
                        for (var i in LocationMapMarkers) {
                            LocationMap.removeLayer(LocationMapMarkers[i])
                        }
                        LocationMapMarkers = [];

                        // Get whois data
                        obj = $.whois(ip, function (data) {
                            var i, str = '';
                            for (i = 0; i < data.length; ++i) {
                                str +=
                                    '<tr>' +
                                    '<td>' + data[i].name + '</td>' +
                                    '<td>' + data[i].value + '</td>' +
                                    '</tr>';
                            }
                            whois.html(
                                '<fieldset id="' + ID('section-1') + '" class="' + ID('field') + ' panel panel-default" data-section="1">' +
                                '<legend class="panel-heading"><h3 id="' + ID('whois-title') + '" class="' + ID('dropdown') + '">Whois</h3></legend>' +
                                '<div class="panel-body ' + ID('border') + '"><table class="' + ID('table') + '">' + str + '</table></div>' +
                                '</fieldset>'
                            ).fadeIn('slow');

                            // keep closing
                            if ('x' === cookie[tabNo][1]) {
                                $(ID('#', 'whois-title')).trigger('click');
                            }
                        });


                        // Show map
                        ajax_post('loading', {
                            cmd: 'search',
                            ip: ip,
                            which: list
                        }, function (data) {
                            var key, tabs = '',
                                c = Number(cookie[tabNo][2]) || 0;

                            for (key in data) {
                                if (data.hasOwnProperty(key)) {
                                    tabs += '<a href="#!" class="nav-tab' + (options[key] === c ? ' nav-tab-active' : '')
                                        + '" data-index="' + options[key] + '" data-api=\'' + stripTag(JSON.stringify(data[key]))
                                        + '\'>' + key + '</a>';
                                }
                            }

                            apis.html(
                                '<div class="nav-tab-wrapper">' + tabs + '</div>' +
                                '<div id="ip-location-block-geoinfo"></div>'
                            ).fadeIn('slow')

                                // change APIs tab
                                .on('click', 'a.nav-tab', function () {
                                    var $this = $(this),
                                        key, json = $(this).data('api'), info = '',
                                        latitude = json ? stripTag(json.latitude) : '0',
                                        longitude = json ? stripTag(json.longitude) : '0',
                                        zoom = json && (json.latitude || json.longitude) ? 7 : 2;

                                    $this.parent().children('a').removeClass('nav-tab-active');
                                    $this.addClass('nav-tab-active');

                                    // last focused index
                                    cookie[tabNo][2] = $this.data('index');
                                    saveCookie(cookie);

                                    for (key in json) {
                                        if (json.hasOwnProperty(key)) {
                                            key = stripTag(key);
                                            json[key] = stripTag(json[key]);
                                            if ('AS number' === key && 0 === json[key].indexOf('AS')) {
                                                json[key] = json[key].replace(/^(AS\d+)/, '<a href="https://ipinfo.io/$1" title="search on ipinfo.io">$1</a>');
                                            }
                                            info +=
                                                '<li>' +
                                                '<span class="' + ID('title') + '">' + key + ' : </span>' +
                                                '<span class="' + ID('result') + '">' + json[key] + '</span>' +
                                                '</li>';
                                        }
                                    }

                                    if (json.latitude && json.longitude) {
                                        var marker = L.marker([json.latitude, json.longitude]).addTo(LocationMap);
                                        LocationMap.setView([json.latitude, json.longitude], 11);
                                        marker.bindPopup('<h4>' + ip + '</h4><ul>' + info + '</ul>');
                                        LocationMapMarkers.push(marker);
                                    }

                                }).find('.nav-tab-active').trigger('click');
                        }, [obj]);
                    }

                    return false;
                });

                // Enter key in textbox
                $(ID('@', 'ip_address')).on('keypress', function (event) {
                    if ((event.which && event.which === 13) || (event.keyCode && event.keyCode === 13)) {
                        $(ID('@', 'get_location')).click();
                        return false;
                    }
                });

                // Preset IP address
                if ($(ID('@', 'ip_address')).val()) {
                    $(ID('@', 'get_location')).click();
                }
                break;

            /*----------------------------------------
             * Sites
             *----------------------------------------*/
            case 5:
                // https://developers.google.com/loader/#Dynamic
                initChart(tabNo);

                // Duration to retrieve
                // [0]:Section, [1]:Open a new window, [2]:Duration to retrieve, [3]:Row, [4]:Column
                $('input[name=' + ID('duration') + ']:radio').on('click', function (/*event*/) {
                    var page = $('div[class*="paginate"]').find('a[class*="current"]').text();
                    cookie[tabNo][2] = $(this).val() || 0; // Duration to retrieve
                    cookie[tabNo][3] = cookie[tabNo][3] || 2; // Rows
                    cookie[tabNo][4] = cookie[tabNo][4] || 1; // Columns
                    saveCookie(cookie);
                    chart.ajaxStacked(cookie[tabNo][2], cookie[tabNo][3], cookie[tabNo][4], page - 1);
                });

                // Open a new window
                $(ID('#', 'open-new')).on('change', function (/*event*/) {
                    var mode = $(this).prop('checked');
                    cookie[tabNo][1] = mode ? 'o' : 'x';
                    saveCookie(cookie);
                    $(ID('#', 'section-0 svg')).find('a').each(function (/*index, obj*/) {
                        this.setAttribute('target', mode ? '_blank' : '_self');
                    });
                });

                // Chart display layout
                $(ID('#', 'apply-layout')).on('click', function (/*event*/) {
                    var $select = $(ID('#', 'select-layout'));
                    cookie[tabNo][3] = $select.find('select[name="rows"] option:selected').val();
                    cookie[tabNo][4] = $select.find('select[name="cols"] option:selected').val();
                    saveCookie(cookie);
                });

                // Correct the `current` class because of the structure of sub manu.
                $('ul.wp-submenu>li.wp-first-item').removeClass('current').next().addClass('current');
                break;
        }

    }); // document.ready()


    $(function () {
        $(document).on('click', ID('#', 'providers-compare-toggle'), function (e) {
            e.preventDefault();
            var $wrap = $(this).closest(ID('.', 'providers'));
            var compactClass = ID('.', 'providers-compact');
            compactClass = compactClass ? compactClass.replace('.', '') : compactClass;
            if ($wrap.hasClass(compactClass)) {
                $wrap.removeClass(compactClass)
            } else {
                $wrap.addClass(compactClass);
            }
        })
    })

    $(function(){
        $(document).on('click', ID('.', 'notice-intro button.notice-dismiss'), function(e){
            var notice_id = $(this).closest('.notice').data('notice');
            ajax_post(null, {
                cmd: 'dismiss-notice',
                notice_id: notice_id,
            }, function (data) {
                if (data.hasOwnProperty('message')) {
                    console.log(data.message);
                } else {
                    alert('Unable to dismiss nonce.');
                }
            });
        })
    })

}(jQuery, window, document));

document.addEventListener("DOMContentLoaded", function(){
    let button = document.querySelector('.ip-location-block-provider-meta');
    if(button) {
        button.addEventListener('click', function(e){
            e.preventDefault();
            console.log('here we go');
            let wrapper = button.closest('.ip-location-block-meta-wrapper');
            if(wrapper.classList.contains('ip-location-block-meta-wrapper-expanded')) {
                wrapper.classList.remove('ip-location-block-meta-wrapper-expanded');
            } else {
                wrapper.classList.add('ip-location-block-meta-wrapper-expanded');
            }
        });
    }
});
