HEX
Server: LiteSpeed
System: Linux cde2.duelhost.dk 4.18.0-553.34.1.lve.el8.x86_64 #1 SMP Thu Jan 9 16:30:32 UTC 2025 x86_64
User: dtptviut (1121)
PHP: 8.0.30
Disabled: exec,system,passthru,shell_exec,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: /home/dtptviut/public_html/wp-content/plugins/comparisonfeed-wp/js/jquery.compareFeed.js
/*!
 * jquery.compareFeed - The Adservice publisher feed jQuery plugin.
 *
 * Copyright (c) 2020 Adservice
 * https://www.adservice.com
 *
 * Version : 1.40
 */
(function ($) {
    $.fn.adserviceFeed = function (options) {
        var obj = this;
        // Get options
        if (!options) options = {};
        var searchForm = options.searchForm || this.attr('data-searchForm');
        var publisherId = options.publisherId || this.attr('data-publisherId');
        var categoryId = options.categoryId || this.attr('data-categoryId');
        var template = options.template || this.attr('data-template');
        var customFeed = options.customFeed || this.attr('data-customFeed');
        var showRating = options.showRating || this.attr('data-showRating') != "false";
        var minRating = options.minRating || this.attr('data-minRating') || 60;
        var cleanLink = options.cleanLink || this.attr('data-cleanLink') || "true";
        var oneItemPerCampaign = options.oneItemPerCampaign || this.attr('data-oneItemPerCampaign') == "true";
        var includeNewCampaigns = options.includeNewCampaigns || this.attr('data-includeNewCampaigns') != "false";
        var sortBy = options.sortBy || this.attr('data-sortBy'); // Data field to sort the rows by. Defaults to customOrder then rating.
        var sortOrder = options.sortOrder || this.attr('data-sortOrder') || 'asc'; // The sort order to use. Must be 'asc' or 'desc'.
        var disableAdblockCheck = options.disableAdBlockCheck || this.attr('data-disableAdBlockCheck') == "true";
        var sub = getHashParam('ascf-sub') || options.sub || this.attr('data-sub');
        var debug = getHashParam('ascf-debug');
        var initialRows = options.initialRows || this.attr('data-initialRows');
        var maxRows = options.maxRows || this.attr('data-maxRows');
        var initialMaxRows = maxRows;
        var lazyLoading = options.lazyLoading || this.attr('data-lazyLoading') != "false";
        var minEpc = options.minEpc || this.attr('data-minEpc');
        var countryCode = options.countryCode || this.attr('data-countryCode');
        var productFeedId = options.productFeedId || this.attr('data-productFeedId');
        var nocache = options.nocache || this.attr('data-nocache');
        var showInteractiveFilter = options.showInteractiveFilter || this.attr('showInteractiveFilter') || 0;
        // If serverUrl is defined as TMPL_VAR then use that
        // otherwise default to feed domain.
        var serverUrlValue = "<TMPL_VAR NAME=serverUrl>";
        var serverUrl = (serverUrlValue.substr(0, 4) === 'http') ?
        serverUrlValue : "https://feedcontentcloud.com";
        // If trackingUrl is defined as TMPL_VAR then use that
        // otherwise default to tracking domain.
        var trackingUrlValue = "<TMPL_VAR NAME=trackingUrl>";
        var trackingUrl = (trackingUrlValue.substr(0, 4) === 'http') ?
            trackingUrlValue : "https://online.adservicemedia.dk";
        var asjsUri = "/cgi-bin/publisher/tools/asjs.pl";
        var predubidUri = "/cgi-bin/publisher/tools/predubid.pl";
        var scriptUri = "/v2/public/publisher/comparisonfeed/data";
        var translationUri = "/v2/public/publisher/comparisonfeed/translations";
        var translations;
        // Workaround to be able to use TMPL_VAR in javascript 
        // while still getting the same result if it's not defined.
        var useTranslationsValue = Number("<TMPL_VAR NAME=useTranslations>");
        var useTranslations = (useTranslationsValue !== 0);
        var useNewFilteringValue = Number("<TMPL_VAR NAME=useNewFiltering>");
        var useNewFiltering = (useNewFilteringValue !== 0);
        var useNewConditionsValue = Number("<TMPL_VAR NAME=useNewConditions>");
        var useNewConditions = (useNewConditionsValue !== 0);
        try {
            var customRating = options.customRating || JSON.parse(jsonFix(this.attr('data-customRating')));
        } catch (e) {
            try {
                var customRating = JSON.parse(jsonFix('{' + this.attr('data-customRating') + '}'));
            } catch (e) {
                if (debug) console.log(e.message);
            }
        }
        try {
            var customOrder = options.customOrder || JSON.parse(jsonFix(this.attr('data-customOrder')));
        } catch (e) {
            try {
                var customOrder = JSON.parse(jsonFix('{' + this.attr('data-customOrder') + '}'));
            } catch (e) {
                if (debug) console.log(e.message);
            }
        }
        try {
            var customData = options.customData || JSON.parse(jsonFix(this.attr('data-customData')));
        } catch (e) {
            if (debug) console.log(e.message);
        }
        try {
            var conditions = options.conditions || JSON.parse(jsonFix(this.attr('data-conditions')));
        } catch (e) {
            if (debug) console.log(e.message);
        }
        try {
            var labels = options.labels || JSON.parse(jsonFix(this.attr('data-labels')));
        } catch (e) {
            try {
                var labels = JSON.parse(jsonFix('{' + this.attr('data-labels') + '}'));
            } catch (e) {
                if (debug) console.log(e.message);
            }
        }
        try {
            var removedItems = options.removedItems || JSON.parse(jsonFix(this.attr('data-removedItems')));
        } catch (e) {
            try {
                var removedItems = JSON.parse(jsonFix('[' + this.attr('data-removedItems') + ']'));
            } catch (e) {
                if (debug) console.log(e.message);
            }
        }
        try {
            var mediaId = options.mediaId || this.attr('data-media');
        } catch (e) {
            if (debug) console.log(e.message);
        }

        // Make sure each object is only parsed once, even if both loading methods are used.
        if (!options.reload) {
            if (obj.attr('loadedFeed') == 'true') return;
            else obj.attr('loadedFeed', 'true');
        }

        // Show loading spinner without having to render template.
        $('#tableHolder').html('<div class="spinner"></div>');

        var tableData = [];
        var latestSearchData = [];
        var removedCampaigns;
        var loadedTmplPlugin = false;
        var loadedFeed = false;
        var loadedTranslations = false;
        var timeoutId;

        // Abort if we don't have any data.
        if ((!categoryId || !publisherId) && !customFeed && !customData && !productFeedId) {
            console.log('Adservice Feed: No data to display when categoryId, publisherId, customData, customFeed or productFeedId parameters are undefined.');
            return;
        }
		
        // Adblock and timeout check
        if (!disableAdblockCheck) {
            timeoutId = window.setTimeout(function () {
                if (tableData.length == 0) {    // only if the table is empty
                    obj.html('<div style="display:block; border: 2px solid #FF0000; text-align:center; font-size: 20px; padding-top:30px; padding-bottom: 30px;">Unable to load content, please check your connection settings or disable your adblocker.</div>');
                }
            }, 10000);
        }

        // Enabled caching for ajax requests.
        // This means getScript won't append a timestamped query parameter on
        // request URLs for cache busting.
        // Docs: https://api.jquery.com/jquery.getscript/
        $.ajaxSetup({
            cache: true
        });

        // Load JsRender.
        if (!($ && $.render)) {
            $.getScript(serverUrl + '/js/jsrender.min.js', function () {
                loadedTmplPlugin = true;
                window.clearTimeout(timeoutId);     // loading successful, clear the fallback warning: no adblock / connection errors
            });
        } else {
            loadedTmplPlugin = true;
        }

        // Load IntersectionObserver polyfills for lazy loading.
        if (lazyLoading && !('IntersectionObserver' in window)) {
            $.getScript('https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver');
        }

        // Store lazy loading option in window for later access.
        window.adserviceLazyLoading = lazyLoading;

        // Load asjs.pl third party tracking script.
        if ($('script[src*="asjs.pl"]').length === 0 && publisherId) {
            $.getScript(serverUrl + asjsUri + '?id=' + publisherId, function () {
                window.clearTimeout(timeoutId);     // loading successful, clear the fallback warning: no adblock / connection errors
            });
        }

        // Listen for 'predubidLoaded' event emitted by predubid.pl.
        $(document.body).on('predubidLoaded', function () {
            checkRejected();
        });

        // Load predubid.pl pre dublet check script.
        if ($('script[src*="' + predubidUri + '"]').length === 0 && categoryId && publisherId) {
            $.getScript(trackingUrl + predubidUri + '?id=' + publisherId + '&cat=' + categoryId + '&src=comparisonfeed');
        } else {
            checkRejected();
        }

        if (Number(initialRows) > 0) {
            // Override maxRows with initialRows.
            maxRows = initialRows;
            // Add click handler for view more button.
            $('#as-feed-view-more-button').on('click', function () {
                $(this).toggleClass('as-expanded');
                $(this).find('span').toggleClass('hidden');

                // Flip value of maxRows.
                maxRows = (maxRows == initialMaxRows) ? initialRows : initialMaxRows;

                // Merge latest search data with conditions to show
                // correct data after filter have been used.
                var searchData = latestSearchData;
                if (!conditions) conditions = [];
                $.merge(searchData, conditions);

                parseData(tableData, searchData);
            });
        }

        // Get adserviceFeed
        if (categoryId && publisherId) {
            if (parseInt(publisherId) > 0) {
                $.getJSON(serverUrl + scriptUri + '?category_id=' + categoryId + '&pid=' + publisherId, function (response) {
                    var data = response.data;
                    // Check if new campaigns should be included or not.
                    if (!includeNewCampaigns && customOrder) {
                        // Filter new campaigns out of data.
                        data = filterNewCampaigns(data, customOrder);
                    }

                    $.merge(tableData, data);

                    // Mark feed data as loaded and render feed if translations are used and has finished loading.
                    loadedFeed = true;
                    if ((useTranslations && loadedTranslations) || (!useTranslations && !loadedTranslations)) {
                        autoFill(searchForm, tableData);
                        window.clearTimeout(timeoutId); // loading successful, clear the fallback warning: no adblock / connection errors
                        parseData(tableData, conditions);
                    }
                });
            } else {
                // Mark feed data as loaded since we didn't have a valid publisher ID.
                loadedFeed = true;
                window.clearTimeout(timeoutId); // no loading, clear the fallback warning: no adblock / connection errors
            }

            // Get translations
            if (useTranslations) {
                loadTranslations();
            }
        }

        // Get product feed products.
        if (parseInt(productFeedId) > 0) {
            // Load DOMPurify for HTML sanitizing.
            $.getScript('https://cdn.jsdelivr.net/npm/dompurify@2.3.1/dist/purify.min.js');

            var cache = (nocache) ? '?nocache=' + nocache : '';
            $.getJSON(serverUrl + '/v2/public/publisher/productfeeds/' + productFeedId + '/products' + cache, function (response) {
                var data = response.data;

                // Map naming to fit CF standards.
                $.each(data, function () {
                    $(this)[0].cleanUrl = $(this)[0].clean_url;
                    $(this)[0].trackingUrl = $(this)[0].tracking_url;
                });

                $.merge(tableData, data);

                // Mark feed data as loaded and render feed.
                loadedFeed = true;
                if (loadedTranslations) {
                    window.clearTimeout(timeoutId); // loading successful, clear the fallback warning: no adblock / connection errors
                    parseData(tableData, conditions);
                }
            });

            // Get translations
            loadTranslations();
        }

        // Get the customFeed
        if (customFeed) {
            $.getJSON(customFeed, function (data) {
                $.merge(tableData, data);
                parseData(tableData, conditions);
                autoFill(searchForm, tableData);
            });
        }

        // add customData
        if (customData) {
            $.merge(tableData, customData);
            parseData(tableData, conditions);
            autoFill(searchForm, tableData);
        }


        if (searchForm) {
            
            $(searchForm).submit(function (e) {
                var searchData = [];

                // loop through the form to generate the searchData
                $(this).find(':input').each(function () {
                    var name = $(this).attr('name');
                    var operator = $(this).attr('operator');
                    var value = $(this).val();
                    var type = $(this).attr('type');
                    var checkboxOrNot = $(this).attr('type') == "checkbox" && $(this).is(':checked') || $(this).attr('type') != "checkbox";

                    if (name && value && operator && checkboxOrNot) {
                        searchData.push({
                            name: name,
                            operator: operator,
                            value: value
                        });
                    }

                    // Look for sorting and store it in case we find one.
                    var sortByVal = $(this).attr('data-sortBy');
                    var sortOrderVal = $(this).attr('data-sortOrder');

                    if (sortByVal !== undefined && sortByVal !== null) {
                        sortBy = sortByVal.toString().trim();
                    }
                    if (sortOrderVal !== undefined && sortOrderVal !== null) {
                        sortOrderVal = sortOrderVal.toString().toLowerCase().trim();
                        if (sortOrderVal === 'asc' || sortOrderVal === 'desc') {
                            sortOrder = sortOrderVal;
                        }
                    }
                });

                latestSearchData = searchData; // Store latest search data.
                if (!conditions) conditions = [];
                $.merge(searchData, conditions);
                parseData(tableData, searchData);

                e.preventDefault();
            });
        }

        function parseData(data, searchData) {
            var tmpData = [];
            // Add SubID
            if (sub) {
                $.each(data, function () {
                    if (($(this)[0].trackingUrl !== undefined && $(this)[0].trackingUrl.indexOf('&sub=' + sub) === -1) &&
                        ($(this)[0].realTrackingUrl === undefined || $(this)[0].realTrackingUrl.indexOf('&sub=' + sub) === -1)) {
                        // Add SubID to tracking URL only if it haven't been done already.
                        $(this)[0].trackingUrl = $(this)[0].trackingUrl + '&sub=' + sub;
                    }
                });
            }

            // Add media ID
            if (mediaId) {
                $.each(data, function () {
                    if (($(this)[0].trackingUrl !== undefined && $(this)[0].trackingUrl.indexOf('&media_id=' + mediaId) === -1) &&
                        ($(this)[0].realTrackingUrl === undefined || $(this)[0].realTrackingUrl.indexOf('&media_id=' + mediaId) === -1)) {
                        // Add media ID to tracking URL only if it haven't been done already.
                        $(this)[0].trackingUrl = $(this)[0].trackingUrl + '&media_id=' + mediaId;
                    }
                });
            }

            // Cleanlinking
            cleanLink = JSON.parse(cleanLink);
            if (cleanLink) {
                $.each(data, function () {
                    if ($(this)[0].cleanUrl && $(this)[0].trackingUrl !== undefined &&
                        $(this)[0].trackingUrl.indexOf($(this)[0].cleanUrl) === -1) {
                        // Store real tracking URL and display clean URL instead.
                        $(this)[0].realTrackingUrl = $(this)[0].trackingUrl;
                        $(this)[0].trackingUrl = $(this)[0].cleanUrl;
                    }
                });

                // a click within the feed
                $(obj).on('mousedown click', function (event) {
                    var target = $(event.target);
                    var parent = $(target).parent();

                    // If target is a link then use that. 
                    // If not then check if the parent is a link with class 'btn-cta' and then use that parent.
                    var link = null;
                    if (target.is('a')) {
                        link = target;
                    } else if (parent.is('a') &&
                        (parent.hasClass('btn-cta') || parent.hasClass('as-link'))) {
                        link = parent;
                    }

                    if (link !== null) {
                        var clickUrl = link.prop('href').toLowerCase();

                        if (clickUrl.indexOf('cgi-bin/click.pl') !== -1) {
                            // Cleanlink have already been switched with the real tracking link.
                            return;
                        }

                        $.each(data, function () {
                            var dataUrl = $(this)[0].trackingUrl.toLowerCase();
                            if (clickUrl.indexOf(dataUrl) !== -1) {
                                link.prop('href', $(this)[0].realTrackingUrl);

                                // change back to the cleanLink
                                setTimeout(function () {
                                    link.prop('href', clickUrl);
                                }, 500);

                                // Return false to break out of the each loop.
                                return false;
                            }
                        });
                    }
                });
            }

            // Add labels to feed data
            if (labels) {
                $.each(data, function () {
                    var label = labels[$(this)[0].id];
                    if (label !== undefined && label !== null) {
                        // Merge label into feed data under 'customLabel' property.
                        // Property name must be unique enough to avoid conflicts with
                        // existing and future data fields.
                        $(this)[0].customLabel = label;
                    }
                });
            }

            // Do low EPC campaign filtering
            // If minEpc is specifically set to 0 then no filtering is done.
            if (parseFloat(minEpc) !== 0) {
                // Make sure removedItems is initialized before using it.
                if (!removedItems) {
                    removedItems = [];
                }
                if (!removedCampaigns) {
                    removedCampaigns = [];
                }

                var lowEPCCampaigns = [];
                var availableCampaigns = [];

                // Make sure items are numbers.
                removedItems = $.map(removedItems, function (n) {
                    return Number(n);
                });
                removedCampaigns = $.map(removedCampaigns, function (n) {
                    return Number(n);
                });

                // Identify low EPC items and removed campaigns.
                $.each(data, function () {
                    // Allow override of EPC limit and fallback to 0 if backend doesn't provide one.
                    var limit = parseFloat(minEpc) || parseFloat($(this)[0].epcLimit) || 0;
                    // Filter campaign if EPC is defined and below limit.
                    if (Number($(this)[0].campEpc) >= 0 && Number($(this)[0].campEpc) < limit) {
                        lowEPCCampaigns.push($(this)[0]);
                    }

                    if (removedCampaigns.indexOf(Number($(this)[0].campId)) === -1
                        && removedItems.indexOf(Number($(this)[0].id)) === -1) {
                        availableCampaigns.push(Number($(this)[0].campId));
                    }
                });

                // Get unique available campaigns.
                availableCampaigns = uniq(availableCampaigns);

                // Sort low EPC campaigns on EPC with lowest first.
                lowEPCCampaigns.sort(function (a, b) {
                    if (a.campEpc < b.campEpc) return -1;
                    if (a.campEpc > b.campEpc) return 1;
                    return 0;
                });

                // Map into array of just camp IDs.
                lowEPCCampaigns = $.map(lowEPCCampaigns, function (o) {
                    return Number(o.campId);
                });

                // Get only unique IDs.
                lowEPCCampaigns = uniq(lowEPCCampaigns);

                // Leave minimum 3 available campaigns.
                var availableForRemoval = Math.max(availableCampaigns.length - 3, 0);
                var end = Math.min(availableForRemoval, lowEPCCampaigns.length);
                // Adjust array to only those campaigns we can remove.
                var campaignsToRemove = lowEPCCampaigns.slice(0, end);

                $.merge(removedCampaigns, campaignsToRemove);
            }

            // If searchData, removedItems or removedCampaigns are defined.
            if (searchData || removedItems || removedCampaigns) {
                // loop through the tableData and searchData to generate a new data output
                $.each(data, function () {
                    var dataObj = $(this)[0];
                    var valid = true;

                    // if searchData
                    if (searchData) {
                        $.each(searchData, function () {
                            var searchObj = $(this)[0];

                            // checking if dataObj's variable is null.
                            // if null means filter failed and return valid false.
                            if (dataObj[searchObj.name] === null || dataObj[searchObj.name] === undefined) {
                                return valid = false;
                            }

                            // Always include data values '-' and '' as we don't know their true value.
                            if (dataObj[searchObj.name].toString().trim() === "-" ||
                                dataObj[searchObj.name].toString().trim() === "") {
                                return valid = true;
                            }

                            // Old plugin versions rely on '0' meaning always valid.
                            if (useNewFiltering) {
                                // Use negative numbers as a way to always include the data in the feed.
                                if (Number(searchObj.value) < 0) {
                                    return valid = true;
                                }
                            } else {
                                if (Number(searchObj.value) == 0) {
                                    return valid = true;
                                }
                            }

                            switch (searchObj.operator) {
                                case "lessThan":
                                    if (useNewConditions) {
                                        // Correctly filter data greater than for 'lessThan'.
                                        if (Number(dataObj[searchObj.name]) > Number(searchObj.value))
                                            return valid = false;
                                    } else {
                                        // Old filtering for backwards compatibility.
                                        if (Number(dataObj[searchObj.name]) < Number(searchObj.value))
                                            return valid = false;
                                    }
                                    break;
                                case "greaterThan":
                                    if (useNewConditions) {
                                        // Correctly filter data less than for 'greaterThan'.
                                        if (Number(dataObj[searchObj.name]) < Number(searchObj.value))
                                            return valid = false;
                                    } else {
                                        // Old filtering for backwards compatibility.
                                        if (Number(dataObj[searchObj.name]) > Number(searchObj.value))
                                            return valid = false;
                                    }
                                    break;
                                case "equals":
                                    if (dataObj[searchObj.name] != searchObj.value)
                                        return valid = false;
                                    break;
                                case "notEquals":
                                    if (dataObj[searchObj.name] == searchObj.value)
                                        return valid = false;
                                    break;
                                case "contains":
                                    if (dataObj[searchObj.name].toLowerCase().indexOf(searchObj.value.toLowerCase()) == -1)
                                        return valid = false;
                                    break;
                                case "notContains":
                                    if (dataObj[searchObj.name].toLowerCase().indexOf(searchObj.value.toLowerCase()) != -1)
                                        return valid = false;
                                    break;
                            }

                            return valid = true;
                        });
                    }

                    // Filter removed items.
                    if (removedItems) {
                        $.each(removedItems, function () {
                            var feedItemId = $(this)[0];
                            if (feedItemId == dataObj.id) return valid = false;
                        });
                    }

                    // Filter removed campaigns.
                    if (removedCampaigns) {
                        $.each(removedCampaigns, function () {
                            var campId = $(this)[0];
                            if (campId == dataObj.campId) return valid = false;
                        });
                    }

                    if (valid) tmpData.push(dataObj);
                });
            } else {
                tmpData = data;
            }
            /*
                Start of feed sorting.
            */
            if (tmpData) {
                // sort the data according to rating and order
                tmpData.sort(function (a, b) {
                    // get and set customRating
                    if (customRating) {
                        var tmpA = customRating[a.id];
                        var tmpB = customRating[b.id];

                        if (tmpA > 0) a.rating = tmpA;
                        if (tmpB > 0) b.rating = tmpB;
                    }

                    // get and set customOrder
                    if (customOrder) {
                        var tmpA = customOrder[a.id];
                        var tmpB = customOrder[b.id];

                        if (tmpA > 0) a.order = tmpA;
                        if (tmpB > 0) b.order = tmpB;
                    }

                    if (!a.rating) a.rating = 0;
                    if (!b.rating) b.rating = 0;
                    if (!a.order) a.order = 0;
                    if (!b.order) b.order = 0;

                    if (sortBy) {
                        // Ordering from filter.
                        if (sortOrder.toString().toLowerCase().trim() === 'desc') {
                            // Get integer value or set to low number for invalid/undefined
                            // values to sort those at the bottom.
                            var tmpA = (isNaN(a[sortBy]) || a[sortBy].trim().length === 0) ?
                                Number.NEGATIVE_INFINITY : Number(a[sortBy]);
                            var tmpB = (isNaN(b[sortBy]) || b[sortBy].trim().length === 0) ?
                                Number.NEGATIVE_INFINITY : Number(b[sortBy]);
                            if (tmpA > tmpB) return -1;
                            if (tmpA < tmpB) return 1;
                        } else {
                            // Always default to ascending even if sortOrder isn't set to 'asc'.
                            // Get integer value or set to low number for invalid/undefined
                            // values to sort those at the bottom.
                            var tmpA = (isNaN(a[sortBy]) || a[sortBy].trim().length === 0) ?
                                Number.POSITIVE_INFINITY : Number(a[sortBy]);
                            var tmpB = (isNaN(b[sortBy]) || b[sortBy].trim().length === 0) ?
                                Number.POSITIVE_INFINITY : Number(b[sortBy]);
                            if (tmpA < tmpB) return -1;
                            if (tmpA > tmpB) return 1;
                        }
                    }

                    // Default ordering.
                    // First based on customOrder, then on rating.
                    if (Number(a.order) > Number(b.order)) return -1;
                    if (Number(a.order) < Number(b.order)) return 1;
                    if (Number(a.rating) > Number(b.rating)) return -1;
                    if (Number(a.rating) < Number(b.rating)) return 1;

                    return 0;
                });

                // If there's only 1 element then sorting didn't run
                // so we will have to apply the custom rating here.
                if (tmpData.length === 1 && customRating) {
                    var element = tmpData[0];
                    var rating = customRating[element.id];
                    element.rating = (rating >= 0) ? rating : element.rating;
                }

                // handle minRating
                if (minRating > 0) {
                    $.each(tmpData, function () {
                        var a = $(this)[0];
                        if (Number(a.rating) < Number(minRating)) a.rating = minRating;
                    });
                }
            }
            /*
                End of feed sorting.
            */

            // Handle only showing one feed item per campaign if enabled.
            // This must happen after all other filtering/sorting have been
            // done to get the correct end result.
            if (oneItemPerCampaign) {
                var filteredData = [];
                var includedCampaigns = [];

                // Loop through data and include only the first feed item for each campaign.
                $.each(tmpData, function () {
                    var dataObj = $(this)[0];
                    var campId = parseInt(dataObj.campId);

                    // Check that campaign haven't been included yet, before adding data.
                    if (includedCampaigns.indexOf(campId) === -1) {
                        filteredData.push(dataObj);
                        includedCampaigns.push(campId);
                    }
                });

                tmpData = filteredData;
            }

            // Save feed size for later so we know whether to display 
            // the 'view more' button or not.
            var feedSize = tmpData.length;
            // Handle maxRows
            if (maxRows) tmpData = tmpData.slice(0, maxRows);

            // wait for the jquery.tmpl plugin to be loaded before using it
            checkReady(function ($) {
                // Register converter for HTML sanitizing.
                $.views.converters("safehtml", function (val) {
                    if (val === undefined || val === null || val.toString().trim().length === 0) return val;

                    if (window.DOMPurify === undefined) {
                        // DOMPurify is not available so we fallback to HTML encoding. 
                        return $.views.converters.html(val);
                    } else {
                        return DOMPurify.sanitize(val);
                    }
                });

                obj.html($(template).render({ data: tmpData }, {
                    numberWithCommas: numberWithCommas,
                    extractUri: function (url) {
                        var uri = /https?:\/\/.*?\/(.*)/g.exec(url);
                        if (uri) uri = uri[1];
                        return uri;
                    },
                    round: function (number) {
                        return Math.round(number);
                    },
                    roundTwoDecimals: roundTwoDecimals,
                    replaceDotWithComma: replaceDotWithComma,
                    getCountryCode: function () {
                        return countryCode;
                    },
                    showRating: function () {
                        return showRating;
                    },
                    getTranslation: getTranslation,
                    calculateExpectedCost: calculateExpectedCost,
                    //Only used in Finland, because Finland has different types of products
                    calculateExpectedCostPrMonth: function(annualPrice, fixedFee) {
                        var estMonthlyCost = Number(fixedFee) + Number(annualPrice) / 12;
                        return estMonthlyCost;
                    },
                    findPrice: function(a, b) {
                        return Math.min(a, b);                        
                    },
                    printInterval: function (lowerValue, upperValue, endToken, replaceDots, roundDecimals,
                        useThousandSeparator, ignoreNaN) {
                        var interval = '-';
                        var ignoreLowerValue = (ignoreNaN && isNaN(lowerValue) && lowerValue.toString().trim() !== '-');
                        var ignoreUpperValue = (ignoreNaN && isNaN(upperValue) && upperValue.toString().trim() !== '-');
                        // Parse values to make sure they are numbers. Skip if we should ignore non-numbers.
                        if (!ignoreLowerValue) lowerValue = parseFloat(lowerValue);
                        if (!ignoreUpperValue) upperValue = parseFloat(upperValue);

                        // Round off decimals.
                        if (roundDecimals && !ignoreLowerValue) lowerValue = roundTwoDecimals(lowerValue);
                        if (roundDecimals && !ignoreUpperValue) upperValue = roundTwoDecimals(upperValue);

                        if ((ignoreLowerValue || !isNaN(lowerValue)) &&
                            (ignoreUpperValue || !isNaN(upperValue))) {
                            if (lowerValue === upperValue) {
                                if (useThousandSeparator) lowerValue = numberWithCommas(lowerValue);
                                interval = lowerValue;
                            } else {
                                if (useThousandSeparator) {
                                    lowerValue = numberWithCommas(lowerValue);
                                    upperValue = numberWithCommas(upperValue);
                                }
                                interval = lowerValue + ' - ' + upperValue;
                            }
                        } else if (ignoreLowerValue || !isNaN(lowerValue)) {
                            if (useThousandSeparator) lowerValue = numberWithCommas(lowerValue);
                            interval = lowerValue;
                        } else if (ignoreUpperValue || !isNaN(upperValue)) {
                            if (useThousandSeparator) upperValue = numberWithCommas(upperValue);
                            interval = upperValue;
                        }

                        if (replaceDots && !useThousandSeparator) interval = replaceDotWithComma(interval);

                        if (endToken && interval !== '-') interval += ' ' + endToken;

                        return interval;
                    },
                    isValidNumber: function (number) {
                        return !isNaN(parseFloat(number));
                    },
                    isValid: function (string) {
                        if (string === null || string === undefined) return false;

                        return (string.toString().trim().length > 0 && string.toString().trim() !== '-');
                    },
                    printOrGetBoolean: function (string, getBoolean) {
                        var result = (getBoolean) ? false : '-';
                        
                        if (string === null || string === undefined) {
                            return result;
                        }
                        if(string == '-') {
                            return string;
                        }

                        if (string == 1 || string.toString().toLowerCase().indexOf('yes') !== -1 ||
                            string.toString().toLowerCase().indexOf('ja') !== -1) {
                            result = (getBoolean) ? true : getTranslation('yes');
                        } else if (string == 0 || string.toString().toLowerCase().indexOf('no') !== -1 ||
                            string.toString().toLowerCase().indexOf('nej') !== -1) {
                            result = (getBoolean) ? false : getTranslation('no');
                        }

                        return result;
                    },
                    printTimeUnit: function (timeUnit, defaultString, dayString, monthString) {
                        if (timeUnit === null || timeUnit === undefined) {
                            return defaultString;
                        }

                        var lowerTimeUnit = timeUnit.toString().toLowerCase();
                        if (lowerTimeUnit.indexOf('day') !== -1 ||
                            lowerTimeUnit.indexOf('dag') !== -1) {
                            return dayString || getTranslation('days');
                        } else if (lowerTimeUnit.indexOf('month') !== -1 ||
                            lowerTimeUnit.indexOf('måned') !== -1) {
                            return monthString || getTranslation('months');
                        } else {
                            return defaultString;
                        }
                    },
                    getSavings: function (price, discountPrice) {
                        price = parseFloat(price);
                        discountPrice = parseFloat(discountPrice);
                        if (price <= 0 || discountPrice <= 0 || discountPrice >= price) return 0;

                        return Math.round(((price - discountPrice) / price) * 100);
                    },
                }));
                // Trigger 'renderedTemplate' event to mark that the feed has finished rendering.
                // This is used to initialize star ratings, tooltips, filter texts and values etc.
                obj.trigger('renderedTemplate');

                // Check if feed size is larger than the number of rows we should display
                // If not then we don't need to display the 'view more' button.
                if (initialRows > 0 && feedSize > initialRows &&
                    (!(Number(initialMaxRows) > 0) || initialMaxRows > initialRows)) {
                    $('#as-feed-view-more-button').css('display', 'block');
                } else {
                    $('#as-feed-view-more-button').css('display', 'none');
                }
            }, function ($) {
                return loadedTmplPlugin;
            });
        }

        function numberWithCommas(x) {
            var parts = x.toString().split(".");
            parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ".");
            return parts.join(",");
        }

        function roundTwoDecimals(number) {
            if (parseFloat(number) === parseInt(number)) {
                // Number doesn't contains decimals so just return it.
                return parseFloat(number);
            } else {
                // Number contains decimals, so round to two.
                return parseFloat(number).toFixed(2);
            }
        }

        function replaceDotWithComma(string) {
            return string.toString().replace(/\./g, ",");
        }

        // Get translation for the given string in the supplied country or in the publishers country
        // if no country is supplied.
        // All possible translations are listed in the translations variable.
        function getTranslation(string, country) {
            var myCountryCode = (country) ? country : countryCode;
            if (translations && translations[string]) {
                return translations[string][myCountryCode];
            } else {
                return '';
            }
        }

        function calculateExpectedCost(price, fixedFee) {
            var asEstUsagePrMonth;
            var selectedPoint;

            var amountOfPeople = $("#minPeople").val() || 1;


            asEstUsagePrMonth = [1000, 1200, 1400, 1600, 1800, 2000, 2200, 2400, 2600, 2800, 3000, 3200, 3400, 3600, 3800, 4000, 4200, 4400, 4600, 4800, 5000, 5200, 5400, 5600, 5800, 6000, 6200, 6400, 6600]
            selectedPoint = asEstUsagePrMonth[$("#estAmount").val()] || asEstUsagePrMonth[0];
            return parseFloat(((parseFloat(price) * ( (parseFloat(amountOfPeople) * 240) + parseFloat(selectedPoint)) / 100)) / 12 + parseFloat(fixedFee)).toFixed(0);
        }

        function autoFill(searchForm, data) {
            if (searchForm) {
                $(searchForm).find('select[autoFill="true"]').each(function () {
                    var select = $(this);

                    /* Build array of values */
                    var values = [];
                    $.each(data, function () {
                        values.push($(this)[0][select.attr('name')]);
                    });
                    values = uniq(values);

                    /* Sort the values */
                    values.sort(function (a, b) {
                        return a - b;
                    });

                    /* append the values to the select */
                    $.each(values, function (e) {
                        select.append("<option value=" + values[e] + ">" + values[e] + "</option>");
                    });
                });
            }
        }

        function checkReady(callback, cond) {
            if (cond()) {
                callback($);
            } else {
                window.setTimeout(function () { checkReady(callback, cond); }, 100);
            }
        }

        function filterNewCampaigns(feedData, customOrder) {
            var filteredData = [];
            var existingCampaigns = Object.keys(customOrder);
            // Only filter data if customOrder is non empty.
            if (existingCampaigns.length > 0) {
                // Loop through feed data and find the entries
                // that are defined in customOrder.
                $.each(feedData, function () {
                    var dataObj = $(this)[0];
                    if (existingCampaigns.indexOf(dataObj.id.toString()) !== -1 || existingCampaigns.indexOf(dataObj.id) !== -1 ) {
                        // Campaign exists in customOrder. Add to filteredData.
                        filteredData.push(dataObj);
                    }
                });
            } else {
                filteredData = feedData;
            }

            return filteredData;
        }

        function checkRejected() {
            // Check if "rejected" set by predubid.pl is non-empty array.
            if (window.rejected && Array.isArray(window.rejected) && window.rejected.length > 0) {
                // Update removedCampaigns so we can hide them later on.
                removedCampaigns = window.rejected;

                // If feed data has already loaded then we most likely need to re-render.
                if (loadedFeed) {
                    parseData(tableData, conditions);
                }
            }
        }

        function loadTranslations() {
            var cache = (nocache) ? '&nocache=' + nocache : '';
            $.getJSON(serverUrl + translationUri + '?category_id=' + categoryId + '&pid=' + publisherId + cache, function (response) {
                if (response.data && response.data.countryCode) {
                    var data = response.data;
                    // Store country code, translations and categoryId internally and globally so they can be accessed with and without jsrender.
                    countryCode = (countryCode) ? countryCode.toString().toUpperCase().trim() : data.countryCode;
                    window.adserviceCountryCode = countryCode;
                    translations = data.translations;
                    window.adserviceTranslations = data.translations;
                    window.adserviceCategoryId = categoryId;
                    // Make getTranslation function available globally so it can be used in filters and other things outside the feed.
                    window.adserviceGetTranslation = getTranslation;

                    // Mark translations as loaded and render feed if data has finished loading.
                    loadedTranslations = true;
                    if (loadedFeed) {
                        parseData(tableData, conditions);
                    }
                }
            });
        }

        // Energy/Electricity feed
        $('input[type=range]').on("input", function() {
            $(".as-energy-container").each(function (index) {
                var pricePoint = parseInt($(this).children().find('.as-energy-price-pr-kwh-value').html());
                var fixedFee = parseInt($(this).children().find('.as-energy-fixed-fee').val());
                $(this).children().find(".as-energy-kwh-price-per-month").html(calculateExpectedCost(pricePoint, fixedFee));
            })
        })
    }

    // automatically create feeds from HTML parameters
    $(document).ready(function () {
        $('[data-adserviceFeed="true"]').each(function () {
            $(this).adserviceFeed();
        });

        var useMinValue = Number("<TMPL_VAR NAME=useMin>");
        var useMin = (useMinValue !== 0);

        // Only add this eventlistener if you want to use the minified javascript,
        // since then we're sure they no longer use compare-theme.js, which contains the same code
        // and causes the 'read more' function to bug out.
        if (useMin) {
            /** Use the trigger for when the template is parsed and loaded to create the star-ratings. */
            $('#tableHolder').on("renderedTemplate", function () {
                $('#loading').remove();
                $('input[type="rating"]').rating({
                    max: 5,
                    readonly: true,
                    showClear: false,
                    showCaption: false,
                    glyphicon: false,
                    size: "xxs"
                });
                initReadMore('.as-read-more');
                initReadMoreMag('.as-read-more2', '.as-read-more-data', '.ads-read-more-icon');

                // Do filter translations.
                $('[data-adserviceTranslation]').each(function () {
                    if (window.adserviceGetTranslation && typeof window.adserviceGetTranslation === 'function') {
                        var translationName = this.getAttribute('data-adserviceTranslation');
                        var translation = adserviceGetTranslation(translationName);

                        // Add the translation as inner html/value on the element.
                        // We must set the value to work with input tags as well.
                        $(this).html(translation);
                        if ($(this).is('input')) {
                            $(this).val(translation);
                        }
                    }
                });

                // Show filter after translations are added and feed is rendered.
                $('#searchTable').css('display', 'block');

                // Initianlize tooltips.   
                if ($.fn.tooltip) {
                    $('[data-toggle="tooltip"]').tooltip({
                        trigger: 'hover focus',
                        viewport: {
                            selector: '#tableHolder',
                            padding: 10
                        }
                    });
                }

                // Find images with class 'as-lazy' for lazy loading.
                var lazyImages = [].slice.call(document.querySelectorAll("img.as-lazy"));

                // Check if any lazy images exists before handling.
                if (lazyImages.length > 0) {
                    // Add 200px bottom buffer for images to load sooner
                    // when scrolling down.
                    var options = {
                        rootMargin: "0px 0px 200px 0px"
                    };

                    if ("IntersectionObserver" in window && window.adserviceLazyLoading) {
                        var lazyImageObserver = new IntersectionObserver(function (entries, observer) {
                            entries.forEach(function (entry) {
                                if (entry.isIntersecting) {
                                    var lazyImage = entry.target;

                                    var newSrc = lazyImage.getAttribute('as-data-src');
                                    if (newSrc !== undefined && newSrc !== null && newSrc.length > 0) {
                                        // Make sure as-data-src contains something before replacing src.
                                        lazyImage.setAttribute('src', newSrc);
                                    }

                                    lazyImage.className.replace(/\bas-lazy\b/g, ''); // Remove 'as-lazy' class.
                                    lazyImageObserver.unobserve(lazyImage);
                                }
                            });
                        }, options);

                        lazyImages.forEach(function (lazyImage) {
                            lazyImageObserver.observe(lazyImage);
                        });
                    } else {
                        // Fall back to no lazy loading.
                        lazyImages.forEach(function (lazyImage) {
                            var newSrc = lazyImage.getAttribute('as-data-src');
                            if (newSrc !== undefined && newSrc !== null && newSrc.length > 0) {
                                // Make sure as-data-src contains something before replacing src.
                                lazyImage.setAttribute('src', newSrc);
                            }
                            lazyImage.className.replace(/\bas-lazy\b/g, '');
                        });
                    }
                }

                // Initialize top 3 sliders.
                $('.comparisson-plg .as-top-3-container').each(function () {
                    // Append slider indicator.
                    $(this).parent().append(
                        '<div class="as-top-3-slider">' +
                        '<a data-id="#as-top-3-id-0" class="selected"></a>' +
                        '<a data-id="#as-top-3-id-1"></a>' +
                        '<a data-id="#as-top-3-id-2"></a>' +
                        '</div>'
                    );

                    // Add click handler.
                    $('.comparisson-plg .as-top-3-slider a').on('click', function (e) {
                        $($(this).attr('data-id'))[0].scrollIntoView({ block: 'nearest', inline: 'nearest' });

                        $(this).siblings().removeClass('selected');
                        $(this).addClass('selected');

                        e.preventDefault();
                        return false;
                    });

                    // Add observer to update indicator when scrolling.
                    if ("IntersectionObserver" in window) {
                        var observer = new IntersectionObserver(function (entries, observer) {
                            // find the entry with the largest intersection ratio
                            var activated = entries.reduce(function (max, entry) {
                                return (entry.intersectionRatio > max.intersectionRatio) ? entry : max;
                            });
                            if (activated.intersectionRatio > 0) {
                                // Fallback to first entry if intersection ratio is less than 0.5
                                // This solves an issue where going from desktop to mobile size would
                                // set the indicator to the 2nd entry because of how IntersectionObserver works.
                                var id = (activated.intersectionRatio >= 0.5) ? activated.target.getAttribute('id') : 'as-top-3-id-0';
                                var indicator = $(activated.target).parent().parent().find('.as-top-3-slider a[data-id="#' + id + '"]');

                                indicator.siblings().removeClass('selected');
                                indicator.addClass('selected');
                            }
                        }, {
                            root: this, threshold: 0.5
                        });

                        $(this).children('.as-top-3-item').each(function () {
                            observer.observe(this);
                        });
                    }
                });

                // Initialize custom styles.
                $('[as-data-style]').each(function () {
                    var style = $(this).attr('as-data-style');
                    if (style !== undefined && style !== null && style.length > 0) {
                        $(this).attr('style', style);
                    }
                });
            });
        }

        // Always update the styles once.
        updateResposiveStyles();

        // Re-update styles if the browser window is resized.
        var resizeTimer;
        $(window).resize(function () {
            clearTimeout(resizeTimer);
            resizeTimer = setTimeout(updateResposiveStyles, 100);
        });


    });

    function updateResposiveStyles() {
        if (750 < $('#tableHolder').width() && $('#tableHolder').width() < 970 && $(document).width() > 960) {
            $('.comparisson-plg').addClass('as-md-style');
            $('.comparisson-plg').removeClass('as-sm-style');
            $('.comparisson-plg').removeClass('as-xs-style');
        } else if (560 < $('#tableHolder').width() && $('#tableHolder').width() <= 750 && $(document).width() > 960) {
            $('.comparisson-plg').addClass('as-sm-style');
            $('.comparisson-plg').removeClass('as-md-style');
            $('.comparisson-plg').removeClass('as-xs-style');
        } else if ($('#tableHolder').width() <= 560 && $(document).width() > 960) {
            $('.comparisson-plg').addClass('as-xs-style');
            $('.comparisson-plg').removeClass('as-md-style');
            $('.comparisson-plg').removeClass('as-sm-style');
        } else {
            $('.comparisson-plg').removeClass('as-md-style');
            $('.comparisson-plg').removeClass('as-sm-style');
            $('.comparisson-plg').removeClass('as-xs-style');
        }
    }

    function initReadMore(el) {
        $(el).on('click', function (e) {
            e.preventDefault();
            jQuerythis = $(this);
            var link = (jQuerythis.is('a')) ? jQuerythis : jQuerythis.find('a');
            openText = link.data('open');
            closeText = link.data('close');

            jQueryitem = $(this).closest('.compare-row');
            if (jQueryitem.hasClass('selected')) {
                jQueryitem.removeClass('selected');
            } else {
                jQueryitem.addClass('selected');
            }

            link.html((link.html() == closeText) ? openText : closeText);
            jQueryitem.find('.as-show-more-content').slideToggle('fast');
        });
    }

    function initReadMoreMag(ell, dataClass, iconClass) {
        $(ell).on('click', function (e) {
            e.preventDefault();
            var jQuerythis = $(this);
            var dataElement = jQuerythis.find(dataClass);
            var openText = dataElement.data('open');
            var closeText = dataElement.data('close');
            var jQueryitem = $(this).closest('.compare-row');

            jQueryitem.find(iconClass).each(function () {
                var icon = $(this);
                if (icon.hasClass('selected')) {
                    icon.removeClass('selected');
                } else {
                    icon.addClass('selected');
                }
            });

            var wrapper = jQueryitem.find('.as-more-wrapper');
            if (wrapper.hasClass('no-padding-top')) {
                wrapper.removeClass('no-padding-top');
            } else {
                wrapper.addClass('no-padding-top');
            }

            jQueryitem.find(dataClass).each(function () {
                $(this).html(($(this).html() == closeText) ? openText : closeText);
            });
            jQueryitem.find('.as-show-more-content').slideToggle('fast');
        });
    }

    function jsonFix(json) {
        return json.replace(/(\d*\.*\d+):/gm, "\"$1\":");
    }

    function uniq($this) {
        for (var i = 0, l = $this.length; i < l; ++i) {
            var item = $this[i];
            var dublicateIdx = $this.indexOf(item, i + 1);
            while (dublicateIdx != -1) {
                $this.splice(dublicateIdx, 1);
                dublicateIdx = $this.indexOf(item, dublicateIdx);
                l--;
            }
        }

        return $this;
    }

    function getHashParam(name) {
        name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
        var regexS = "[\\#&]" + name + "=([^&#]*)";
        var regex = new RegExp(regexS);
        var results = regex.exec(window.location.hash);

        if (results == null)
            return "";
        else
            return results[1];
    }

})(jQuery);