PageSourceSearch

https://www.zimra.co.zw/media/vendor/debugbar/debugbar.js

js zimra.co.zw collected 2026-09-24 18:11:50 UTC 49,658 bytes, 1,517 lines download raw bytes

1if (typeof(PhpDebugBar) == 'undefined') {
2    // namespace
3    var PhpDebugBar = {};
4    PhpDebugBar.$ = jQuery;
5}
6
7(function($) {
8
9    if (typeof(localStorage) == 'undefined') {
10        // provide mock localStorage object for dumb browsers
11        localStorage = {
12            setItem: function(key, value) {},
13            getItem: function(key) { return null; }
14        };
15    }
16
17    if (typeof(PhpDebugBar.utils) == 'undefined') {
18        PhpDebugBar.utils = {};
19    }
20
21    /**
22     * Returns the value from an object property.
23     * Using dots in the key, it is possible to retrieve nested property values
24     *
25     * @param {Object} dict
26     * @param {String} key
27     * @param {Object} default_value
28     * @return {Object}
29     */
30    var getDictValue = PhpDebugBar.utils.getDictValue = function(dict, key, default_value) {
31        var d = dict, parts = key.split('.');
32        for (var i = 0; i < parts.length; i++) {
33            if (!d[parts[i]]) {
34                return default_value;
35            }
36            d = d[parts[i]];
37        }
38        return d;
39    }
40
41    /**
42     * Counts the number of properties in an object
43     *
44     * @param {Object} obj
45     * @return {Integer}
46     */
47    var getObjectSize = PhpDebugBar.utils.getObjectSize = function(obj) {
48        if (Object.keys) {
49            return Object.keys(obj).length;
50        }
51        var count = 0;
52        for (var k in obj) {
53            if (obj.hasOwnProperty(k)) {
54                count++;
55            }
56        }
57        return count;
58    }
59
60    /**
61     * Returns a prefixed css class name
62     *
63     * @param {String} cls
64     * @return {String}
65     */
66    PhpDebugBar.utils.csscls = function(cls, prefix) {
67        if (cls.indexOf(' ') > -1) {
68            var clss = cls.split(' '), out = [];
69            for (var i = 0, c = clss.length; i < c; i++) {
70                out.push(PhpDebugBar.utils.csscls(clss[i], prefix));
71            }
72            return out.join(' ');
73        }
74        if (cls.indexOf('.') === 0) {
75            return '.' + prefix + cls.substr(1);
76        }
77        return prefix + cls;
78    };
79
80    /**
81     * Creates a partial function of csscls where the second
82     * argument is already defined
83     *
84     * @param  {string} prefix
85     * @return {Function}
86     */
87    PhpDebugBar.utils.makecsscls = function(prefix) {
88        var f = function(cls) {
89            return PhpDebugBar.utils.csscls(cls, prefix);
90        };
91        return f;
92    }
93
94    var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-');
95
96
97    // ------------------------------------------------------------------
98
99    /**
100     * Base class for all elements with a visual component
101     *
102     * @param {Object} options
103     * @constructor
104     */
105    var Widget = PhpDebugBar.Widget = function(options) {
106        this._attributes = $.extend({}, this.defaults);
107        this._boundAttributes = {};
108        this.$el = $('<' + this.tagName + ' />');
109        if (this.className) {
110            this.$el.addClass(this.className);
111        }
112        this.initialize.apply(this, [options || {}]);
113        this.render.apply(this);
114    };
115
116    $.extend(Widget.prototype, {
117
118        tagName: 'div',
119
120        className: null,
121
122        defaults: {},
123
124        /**
125         * Called after the constructor
126         *
127         * @param {Object} options
128         */
129        initialize: function(options) {
130            this.set(options);
131        },
132
133        /**
134         * Called after the constructor to render the element
135         */
136        render: function() {},
137
138        /**
139         * Sets the value of an attribute
140         *
141         * @param {String} attr Can also be an object to set multiple attributes at once
142         * @param {Object} value
143         */
144        set: function(attr, value) {
145            if (typeof(attr) != 'string') {
146                for (var k in attr) {
147                    this.set(k, attr[k]);
148                }
149                return;
150            }
151
152            this._attributes[attr] = value;
153            if (typeof(this._boundAttributes[attr]) !== 'undefined') {
154                for (var i = 0, c = this._boundAttributes[attr].length; i < c; i++) {
155                    this._boundAttributes[attr][i].apply(this, [value]);
156                }
157            }
158        },
159
160        /**
161         * Checks if an attribute exists and is not null
162         *
163         * @param {String} attr
164         * @return {[type]} [description]
165         */
166        has: function(attr) {
167            return typeof(this._attributes[attr]) !== 'undefined' && this._attributes[attr] !== null;
168        },
169
170        /**
171         * Returns the value of an attribute
172         *
173         * @param {String} attr
174         * @return {Object}
175         */
176        get: function(attr) {
177            return this._attributes[attr];
178        },
179
180        /**
181         * Registers a callback function that will be called whenever the value of the attribute changes
182         *
183         * If cb is a jQuery element, text() will be used to fill the element
184         *
185         * @param {String} attr
186         * @param {Function} cb
187         */
188        bindAttr: function(attr, cb) {
189            if (Array.isArray(attr)) {
190                for (var i = 0, c = attr.length; i < c; i++) {
191                    this.bindAttr(attr[i], cb);
192                }
193                return;
194            }
195
196            if (typeof(this._boundAttributes[attr]) == 'undefined') {
197                this._boundAttributes[attr] = [];
198            }
199            if (typeof(cb) == 'object') {
200                var el = cb;
201                cb = function(value) { el.text(value || ''); };
202            }
203            this._boundAttributes[attr].push(cb);
204            if (this.has(attr)) {
205                cb.apply(this, [this._attributes[attr]]);
206            }
207        }
208
209    });
210
211
212    /**
213     * Creates a subclass
214     *
215     * Code from Backbone.js
216     *
217     * @param {Array} props Prototype properties
218     * @return {Function}
219     */
220    Widget.extend = function(props) {
221        var parent = this;
222
223        var child = function() { return parent.apply(this, arguments); };
224        $.extend(child, parent);
225
226        var Surrogate = function() { this.constructor = child; };
227        Surrogate.prototype = parent.prototype;
228        child.prototype = new Surrogate;
229        $.extend(child.prototype, props);
230
231        child.__super__ = parent.prototype;
232
233        return child;
234    };
235
236    // ------------------------------------------------------------------
237
238    /**
239     * Tab
240     *
241     * A tab is composed of a tab label which is always visible and
242     * a tab panel which is visible only when the tab is active.
243     *
244     * The panel must contain a widget. A widget is an object which has
245     * an element property containing something appendable to a jQuery object.
246     *
247     * Options:
248     *  - title
249     *  - badge
250     *  - widget
251     *  - data: forward data to widget data
252     */
253    var Tab = Widget.extend({
254
255        className: csscls('panel'),
256
257        render: function() {
258            this.$tab = $('<a />').addClass(csscls('tab'));
259            this.$icon = $('<i />').appendTo(this.$tab);
260            this.bindAttr('icon', function(icon) {
261                if (icon) {
262                    this.$icon.attr('class', 'phpdebugbar-fa phpdebugbar-fa-' + icon);
263                } else {
264                    this.$icon.attr('class', '');
265                }
266            });
267
268            this.bindAttr('title', $('<span />').addClass(csscls('text')).appendTo(this.$tab));
269
270            this.$badge = $('<span />').addClass(csscls('badge')).appendTo(this.$tab);
271            this.bindAttr('badge', function(value) {
272                if (value !== null) {
273                    this.$badge.text(value);
274                    this.$badge.addClass(csscls('visible'));
275                } else {
276                    this.$badge.removeClass(csscls('visible'));
277                }
278            });
279
280            this.bindAttr('widget', function(widget) {
281                this.$el.empty().append(widget.$el);
282            });
283
284            this.bindAttr('data', function(data) {
285                if (this.has('widget')) {
286                    this.get('widget').set('data', data);
287                    this.$tab.attr('data-empty', $.isEmptyObject(data) || data.count === 0);
288                }
289            })
290        }
291
292    });
293
294    // ------------------------------------------------------------------
295
296    /**
297     * Indicator
298     *
299     * An indicator is a text and an icon to display single value information
300     * right inside the always visible part of the debug bar
301     *
302     * Options:
303     *  - icon
304     *  - title
305     *  - tooltip
306     *  - data: alias of title
307     */
308    var Indicator = Widget.extend({
309
310        tagName: 'span',
311
312        className: csscls('indicator'),
313
314        render: function() {
315            this.$icon = $('<i />').appendTo(this.$el);
316            this.bindAttr('icon', function(icon) {
317                if (icon) {
318                    this.$icon.attr('class', 'phpdebugbar-fa phpdebugbar-fa-' + icon);
319                } else {
320                    this.$icon.attr('class', '');
321                }
322            });
323
324            this.bindAttr('link', function(link) {
325                if (link) {
326                    this.$el.on('click', () => {
327                        this.get('debugbar').showTab(link);
328                    }).css('cursor', 'pointer')
329                } else {
330                    this.$el.off('click', false).css('cursor', '')
331                }
332            });
333
334            this.bindAttr(['title', 'data'], $('<span />').addClass(csscls('text')).appendTo(this.$el));
335
336            this.$tooltip = $('<span />').addClass(csscls('tooltip disabled')).appendTo(this.$el);
337            this.bindAttr('tooltip', function(tooltip) {
338                if (tooltip) {
339                    var dl = $('<dl />');
340                    if (Array.isArray(tooltip) || typeof tooltip === 'object') {
341                        $.each(tooltip, function(key, value) {
342                            $('<dt />').text(key).appendTo(dl);
343                            $('<dd />').text(value).appendTo(dl);
344                        });
345                        this.$tooltip.html(dl).removeClass(csscls('disabled'));
346                    } else {
347                        this.$tooltip.text(tooltip).removeClass(csscls('disabled'));
348                    }
349                } else {
350                    this.$tooltip.addClass(csscls('disabled'));
351                }
352            });
353        }
354
355    });
356
357
358    /**
359     * Displays datasets in a table
360     *
361     */
362    var Settings = Widget.extend({
363
364        tagName: 'form',
365
366        className: csscls('settings'),
367
368        settings: {},
369
370        initialize: function(options) {
371            this.set(options);
372
373            var debugbar = this.get('debugbar');
374            this.settings = JSON.parse(localStorage.getItem('phpdebugbar-settings')) || {};
375
376            $.each(debugbar.options, (key, value)=>  {
377                if (key in this.settings) {
378                    debugbar.options[key] = this.settings[key];
379                }
380
381                // Theme requires dark/light mode detection
382                if (key === 'theme') {
383                    debugbar.setTheme(debugbar.options[key]);
384                } else {
385                    debugbar.$el.attr('data-' + key, debugbar.options[key]);
386                }
387            })
388        },
389
390        clearSettings: function() {
391            var debugbar = this.get('debugbar');
392
393            // Remove item from storage
394            localStorage.removeItem('phpdebugbar-settings');
395            localStorage.removeItem('phpdebugbar-ajaxhandler-autoshow');
396            this.settings = {};
397
398            // Reset options
399            debugbar.options = { ...debugbar.defaultOptions };
400
401            // Reset ajax handler
402            if (debugbar.ajaxHandler) {
403                var autoshow = debugbar.ajaxHandler.defaultAutoShow;
404                debugbar.ajaxHandler.setAutoShow(autoshow);
405                this.set('autoshow', autoshow);
406                if(debugbar.controls['__datasets']) {
407                    debugbar.controls['__datasets'].get('widget').set('autoshow', $(this).is(':checked'));
408                }
409            }
410
411            this.initialize(debugbar.options);
412        },
413
414        storeSetting: function(key, value) {
415            this.settings[key] = value;
416
417            var debugbar = this.get('debugbar');
418            debugbar.options[key] = value;
419            if (key !== 'theme') {
420                debugbar.$el.attr('data-' + key, value);
421            }
422
423            localStorage.setItem('phpdebugbar-settings', JSON.stringify(this.settings));
424        },
425
426        render: function() {
427            this.$el.empty();
428
429            var debugbar = this.get('debugbar');
430            var self = this;
431
432            var fields = {};
433
434            // Set Theme
435            fields["Theme"] = $('<select>' +
436                '<option value="auto">Auto (System preference)</option>' +
437                '<option value="light">Light</option>' +
438                '<option value="dark">Dark</option>' +
439                '</select>')
440                .val(debugbar.options.theme)
441                .on('change', function() {
442                    self.storeSetting('theme', $(this).val())
443                    debugbar.setTheme($(this).val());
444                });
445
446            fields["Open Button Position"] = $('<select>' +
447                '<option value="bottomLeft">Bottom Left</option>' +
448                '<option value="bottomRight">Bottom Right</option>' +
449                '<option value="topLeft">Top Left</option>' +
450                '<option value="topRight">Top Right</option>' +
451                '</select>')
452                .val(debugbar.options.openBtnPosition)
453                .on('change', function() {
454                    self.storeSetting('openBtnPosition', $(this).val())
455                });
456
457            this.$hideEmptyTabs = $('<input type=checkbox>')
458                .prop('checked', debugbar.options.hideEmptyTabs)
459                .on('click', function() {
460                    self.storeSetting('hideEmptyTabs', $(this).is(':checked'));
461                });
462
463            fields["Hide Empty Tabs"] = $('<label/>').append(this.$hideEmptyTabs, 'Hide empty tabs until they have data');
464
465            this.$autoshow = $('<input type=checkbox>')
466                .prop('checked', debugbar.ajaxHandler && debugbar.ajaxHandler.autoShow)
467                .on('click', function() {
468                    if (debugbar.ajaxHandler) {
469                        debugbar.ajaxHandler.setAutoShow($(this).is(':checked'));
470                    }
471                    if (debugbar.controls['__datasets']) {
472                        debugbar.controls['__datasets'].get('widget').set('autoshow', $(this).is(':checked'));
473                    }
474                });
475
476            this.bindAttr('autoshow', function() {
477                this.$autoshow.prop('checked', this.get('autoshow')).closest('.form-row').show();
478            })
479            fields["Autoshow"] = $('<label/>').append(this.$autoshow, 'Automatically show new incoming Ajax requests');
480
481            fields["Reset to defaults"] = $('<button>Reset settings</button>').on('click', function(e) {
482                e.preventDefault();
483                self.clearSettings();
484                self.render();
485            })
486
487            $.each(fields, function(key, value) {
488                $('<div />').addClass(csscls('form-row')).append(
489                    $('<div />').addClass(csscls('form-label')).text(key),
490                    $('<div />').addClass(csscls('form-input')).html(value)
491                ).appendTo(self.$el);
492
493            })
494
495            if (!this.ajaxHandler) {
496                this.$autoshow.closest('.form-row').hide();
497            }
498        },
499
500    });
501
502    // ------------------------------------------------------------------
503
504    /**
505     * Dataset title formater
506     *
507     * Formats the title of a dataset for the select box
508     */
509    var DatasetTitleFormater = PhpDebugBar.DatasetTitleFormater = function(debugbar) {
510        this.debugbar = debugbar;
511    };
512
513    $.extend(DatasetTitleFormater.prototype, {
514
515        /**
516         * Formats the title of a dataset
517         *
518         * @this {DatasetTitleFormater}
519         * @param {String} id
520         * @param {Object} data
521         * @param {String} suffix
522         * @return {String}
523         */
524        format: function(id, data, suffix, nb) {
525            if (suffix) {
526                suffix = ' ' + suffix;
527            } else {
528                suffix = '';
529            }
530
531            var nb = nb || getObjectSize(this.debugbar.datasets) ;
532
533            if (typeof(data['__meta']) === 'undefined') {
534                return "#" + nb + suffix;
535            }
536
537            var uri = data['__meta']['uri'].split('/'), filename = uri.pop();
538
539            // URI ends in a trailing /, avoid returning an empty string
540            if (!filename) {
541                filename = (uri.pop() || '') + '/'; // add the trailing '/' back
542            }
543
544            // filename is a number, path could be like /action/{id}
545            if (uri.length && !isNaN(filename)) {
546                filename = uri.pop() + '/' + filename;
547            }
548
549            // truncate the filename in the label, if it's too long
550            var maxLength = 150;
551            if (filename.length > maxLength) {
552                filename = filename.substr(0, maxLength) + '...';
553            }
554
555            var label = "#" + nb + " " + filename + suffix + ' (' + data['__meta']['datetime'].split(' ')[1] + ')';
556            return label;
557        }
558
559    });
560
561    // ------------------------------------------------------------------
562
563
564    /**
565     * DebugBar
566     *
567     * Creates a bar that appends itself to the body of your page
568     * and sticks to the bottom.
569     *
570     * The bar can be customized by adding tabs and indicators.
571     * A data map is used to fill those controls with data provided
572     * from datasets.
573     */
574    var DebugBar = PhpDebugBar.DebugBar = Widget.extend({
575
576        className: "phpdebugbar " + csscls('minimized'),
577
578        options: {
579            bodyMarginBottom: true,
580            theme: 'auto',
581            openBtnPosition: 'bottomLeft',
582            hideEmptyTabs: false,
583        },
584
585        initialize: function(options = {}) {
586            this.options = $.extend(this.options, options);
587            this.defaultOptions = { ...this.options };
588            this.controls = {};
589            this.dataMap = {};
590            this.datasets = {};
591            this.firstTabName = null;
592            this.activePanelName = null;
593            this.activeDatasetId = null;
594            this.datesetTitleFormater = new DatasetTitleFormater(this);
595            this.bodyMarginBottomHeight = parseInt($('body').css('margin-bottom'));
596            try {
597                this.isIframe = window.self !== window.top && window.top.phpdebugbar;
598            } catch (error) {
599                this.isIframe = false;
600            }
601            this.registerResizeHandler();
602            this.registerMediaListener();
603
604            // Attach settings
605            this.settings = new PhpDebugBar.DebugBar.Tab({"icon":"sliders", "title":"Settings", "widget": new Settings({
606                    'debugbar': this,
607                })});
608        },
609
610        /**
611         * Register resize event, for resize debugbar with reponsive css.
612         *
613         * @this {DebugBar}
614         */
615        registerResizeHandler: function() {
616            if (typeof this.resize.bind == 'undefined' || this.isIframe) return;
617
618            var f = this.resize.bind(this);
619            this.respCSSSize = 0;
620            $(window).resize(f);
621            setTimeout(f, 20);
622        },
623
624        registerMediaListener: function() {
625            const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
626            mediaQueryList.addEventListener('change', event => {
627                if (this.options.theme === 'auto') {
628                    this.setTheme('auto');
629                }
630            })
631        },
632
633        setTheme: function(theme) {
634            this.options.theme = theme;
635
636            if (theme === 'auto') {
637                const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
638                theme = mediaQueryList.matches ? 'dark' : 'light';
639            }
640
641            this.$el.attr('data-theme', theme)
642            if (this.openHandler) {
643                this.openHandler.$el.attr('data-theme', theme)
644            }
645        },
646
647        /**
648         * Resizes the debugbar to fit the current browser window
649         */
650        resize: function() {
651            if (this.isIframe) return;
652
653            var contentSize = this.respCSSSize;
654            if (this.respCSSSize == 0) {
655                this.$header.find("> *:visible").each(function () {
656                    contentSize += $(this).outerWidth(true);
657                });
658            }
659
660            var currentSize = this.$header.width();
661            var cssClass = csscls("mini-design");
662            var bool = this.$header.hasClass(cssClass);
663
664            if (currentSize <= contentSize && !bool) {
665                this.respCSSSize = contentSize;
666                this.$header.addClass(cssClass);
667            } else if (contentSize < currentSize && bool) {
668                this.respCSSSize = 0;
669                this.$header.removeClass(cssClass);
670            }
671
672            // Reset height to ensure bar is still visible
673            this.setHeight(this.$body.height());
674        },
675
676        /**
677         * Initialiazes the UI
678         *
679         * @this {DebugBar}
680         */
681        render: function() {
682            if (this.isIframe) {
683                this.$el.hide();
684            }
685
686            var self = this;
687            this.$el.appendTo('body');
688            this.$dragCapture = $('<div />').addClass(csscls('drag-capture')).appendTo(this.$el);
689            this.$resizehdle = $('<div />').addClass(csscls('resize-handle')).appendTo(this.$el);
690            this.$header = $('<div />').addClass(csscls('header')).appendTo(this.$el);
691            this.$headerBtn = $('<a />').addClass(csscls('restore-btn')).appendTo(this.$header);
692            this.$headerBtn.click(function() {
693                self.close();
694            });
695            this.$headerLeft = $('<div />').addClass(csscls('header-left')).appendTo(this.$header);
696            this.$headerRight = $('<div />').addClass(csscls('header-right')).appendTo(this.$header);
697            var $body = this.$body = $('<div />').addClass(csscls('body')).appendTo(this.$el);
698            this.recomputeBottomOffset();
699
700            // dragging of resize handle
701            var pos_y, orig_h;
702            this.$resizehdle.on('mousedown', function(e) {
703                orig_h = $body.height(), pos_y = e.pageY;
704                $body.parents().on('mousemove', mousemove).on('mouseup', mouseup);
705                self.$dragCapture.show();
706                e.preventDefault();
707            });
708            var mousemove = function(e) {
709                var h = orig_h + (pos_y - e.pageY);
710                self.setHeight(h);
711            };
712            var mouseup = function() {
713                $body.parents().off('mousemove', mousemove).off('mouseup', mouseup);
714                self.$dragCapture.hide();
715            };
716
717            // close button
718            this.$closebtn = $('<a />').addClass(csscls('close-btn')).appendTo(this.$headerRight);
719            this.$closebtn.click(function() {
720                self.close();
721            });
722
723            // minimize button
724            this.$minimizebtn = $('<a />').addClass(csscls('minimize-btn') ).appendTo(this.$headerRight);
725            this.$minimizebtn.click(function() {
726                self.minimize();
727            });
728
729            // maximize button
730            this.$maximizebtn = $('<a />').addClass(csscls('maximize-btn') ).appendTo(this.$headerRight);
731            this.$maximizebtn.click(function() {
732                self.restore();
733            });
734
735            // restore button
736            this.$restorebtn = $('<a />').addClass(csscls('restore-btn')).hide().appendTo(this.$el);
737            this.$restorebtn.click(function() {
738                self.restore();
739            });
740
741            // open button
742            this.$openbtn = $('<a />').addClass(csscls('open-btn')).appendTo(this.$headerRight).hide();
743            this.$openbtn.click(function() {
744                self.openHandler.show(function(id, dataset) {
745                    self.addDataSet(dataset, id, "(opened)");
746                    self.showTab();
747                });
748            });
749
750            // select box for data sets
751            this.$datasets = $('<select />').addClass(csscls('datasets-switcher')).attr('name', 'datasets-switcher')
752                .appendTo(this.$headerRight);
753            this.$datasets.change(function() {
754                self.showDataSet(this.value);
755            });
756
757            this.controls['__settings'] = this.settings;
758            this.settings.$tab.addClass(csscls('tab-settings'));
759            this.settings.$tab.attr('data-collector', '__settings');
760            this.settings.$el.attr('data-collector', '__settings');
761            this.settings.$tab.insertAfter(this.$minimizebtn).show();
762            this.settings.$tab.click(() => {
763                if (!this.isMinimized() && this.activePanelName == '__settings') {
764                    this.minimize();
765                } else {
766                    this.showTab('__settings');
767                    this.settings.get('widget').render();
768                }
769            });
770            this.settings.$el.appendTo(this.$body);
771        },
772
773        /**
774         * Sets the height of the debugbar body section
775         * Forces the height to lie within a reasonable range
776         * Stores the height in local storage so it can be restored
777         * Resets the document body bottom offset
778         *
779         * @this {DebugBar}
780         */
781        setHeight: function(height) {
782            var min_h = 40;
783            var max_h = window.innerHeight - this.$header.height() - 10;
784            height = Math.min(height, max_h);
785            height = Math.max(height, min_h);
786            this.$body.css('height', height);
787            localStorage.setItem('phpdebugbar-height', height);
788            this.recomputeBottomOffset();
789        },
790
791        /**
792         * Restores the state of the DebugBar using localStorage
793         * This is not called by default in the constructor and
794         * needs to be called by subclasses in their init() method
795         *
796         * @this {DebugBar}
797         */
798        restoreState: function() {
799            if (this.isIframe) return;
800            // bar height
801            var height = localStorage.getItem('phpdebugbar-height');
802            this.setHeight(height || this.$body.height());
803
804            // bar visibility
805            var open = localStorage.getItem('phpdebugbar-open');
806            if (open && open == '0') {
807                this.close();
808            } else {
809                var visible = localStorage.getItem('phpdebugbar-visible');
810                if (visible && visible == '1') {
811                    var tab = localStorage.getItem('phpdebugbar-tab');
812                    if (this.isTab(tab)) {
813                        this.showTab(tab);
814                    } else {
815                        this.showTab();
816                    }
817                }
818            }
819        },
820
821        /**
822         * Creates and adds a new tab
823         *
824         * @this {DebugBar}
825         * @param {String} name Internal name
826         * @param {Object} widget A widget object with an element property
827         * @param {String} title The text in the tab, if not specified, name will be used
828         * @return {Tab}
829         */
830        createTab: function(name, widget, title) {
831            var tab = new Tab({
832                title: title || (name.replace(/[_\-]/g, ' ').charAt(0).toUpperCase() + name.slice(1)),
833                widget: widget
834            });
835            return this.addTab(name, tab);
836        },
837
838        /**
839         * Adds a new tab
840         *
841         * @this {DebugBar}
842         * @param {String} name Internal name
843         * @param {Tab} tab Tab object
844         * @return {Tab}
845         */
846        addTab: function(name, tab) {
847            if (this.isControl(name)) {
848                throw new Error(name + ' already exists');
849            }
850
851            var self = this;
852            tab.$tab.appendTo(this.$headerLeft).click(function() {
853                if (!self.isMinimized() && self.activePanelName == name) {
854                    self.minimize();
855                } else {
856                    self.showTab(name);
857                }
858            })
859            tab.$tab.attr('data-empty', true);
860            tab.$tab.attr('data-collector', name);
861            tab.$el.attr('data-collector', name);
862            tab.$el.appendTo(this.$body);
863
864            this.controls[name] = tab;
865            if (this.firstTabName == null) {
866                this.firstTabName = name;
867            }
868            return tab;
869        },
870
871        /**
872         * Creates and adds an indicator
873         *
874         * @this {DebugBar}
875         * @param {String} name Internal name
876         * @param {String} icon
877         * @param {String|Object} tooltip
878         * @param {String} position "right" or "left", default is "right"
879         * @return {Indicator}
880         */
881        createIndicator: function(name, icon, tooltip, position) {
882            var indicator = new Indicator({
883                icon: icon,
884                tooltip: tooltip
885            });
886            return this.addIndicator(name, indicator, position);
887        },
888
889        /**
890         * Adds an indicator
891         *
892         * @this {DebugBar}
893         * @param {String} name Internal name
894         * @param {Indicator} indicator Indicator object
895         * @return {Indicator}
896         */
897        addIndicator: function(name, indicator, position) {
898            if (this.isControl(name)) {
899                throw new Error(name + ' already exists');
900            }
901
902            indicator.set('debugbar', this);
903
904            if (position == 'left') {
905                indicator.$el.insertBefore(this.$headerLeft.children().first());
906            } else {
907                indicator.$el.appendTo(this.$headerRight);
908            }
909
910            this.controls[name] = indicator;
911            return indicator;
912        },
913
914        /**
915         * Returns a control
916         *
917         * @param {String} name
918         * @return {Object}
919         */
920        getControl: function(name) {
921            if (this.isControl(name)) {
922                return this.controls[name];
923            }
924        },
925
926        /**
927         * Checks if there's a control under the specified name
928         *
929         * @this {DebugBar}
930         * @param {String} name
931         * @return {Boolean}
932         */
933        isControl: function(name) {
934            return typeof(this.controls[name]) != 'undefined';
935        },
936
937        /**
938         * Checks if a tab with the specified name exists
939         *
940         * @this {DebugBar}
941         * @param {String} name
942         * @return {Boolean}
943         */
944        isTab: function(name) {
945            return this.isControl(name) && this.controls[name] instanceof Tab;
946        },
947
948        /**
949         * Checks if an indicator with the specified name exists
950         *
951         * @this {DebugBar}
952         * @param {String} name
953         * @return {Boolean}
954         */
955        isIndicator: function(name) {
956            return this.isControl(name) && this.controls[name] instanceof Indicator;
957        },
958
959        /**
960         * Removes all tabs and indicators from the debug bar and hides it
961         *
962         * @this {DebugBar}
963         */
964        reset: function() {
965            this.minimize();
966            var self = this;
967            $.each(this.controls, function(name, control) {
968                if (self.isTab(name)) {
969                    control.$tab.remove();
970                }
971                control.$el.remove();
972            });
973            this.controls = {};
974        },
975
976        /**
977         * Open the debug bar and display the specified tab
978         *
979         * @this {DebugBar}
980         * @param {String} name If not specified, display the first tab
981         */
982        showTab: function(name) {
983            if (!name) {
984                if (this.activePanelName) {
985                    name = this.activePanelName;
986                } else {
987                    name = this.firstTabName;
988                }
989            }
990
991            if (!this.isTab(name)) {
992                throw new Error("Unknown tab '" + name + "'");
993            }
994
995            this.$resizehdle.show();
996            this.$body.show();
997            this.recomputeBottomOffset();
998
999            $(this.$header).find('> div > .' + csscls('active')).removeClass(csscls('active'));
1000            $(this.$body).find('> .' + csscls('active')).removeClass(csscls('active'));
1001
1002            this.controls[name].$tab.addClass(csscls('active'));
1003            this.controls[name].$el.addClass(csscls('active'));
1004            this.activePanelName = name;
1005
1006            this.$el.removeClass(csscls('minimized'));
1007            localStorage.setItem('phpdebugbar-visible', '1');
1008            localStorage.setItem('phpdebugbar-tab', name);
1009
1010            this.resize();
1011        },
1012
1013        /**
1014         * Hide panels and minimize the debug bar
1015         *
1016         * @this {DebugBar}
1017         */
1018        minimize: function() {
1019            this.$header.find('> div > .' + csscls('active')).removeClass(csscls('active'));
1020            this.$body.hide();
1021            this.$resizehdle.hide();
1022            this.recomputeBottomOffset();
1023            localStorage.setItem('phpdebugbar-visible', '0');
1024            this.$el.addClass(csscls('minimized'));
1025            this.resize();
1026        },
1027
1028        /**
1029         * Checks if the panel is minimized
1030         *
1031         * @return {Boolean}
1032         */
1033        isMinimized: function() {
1034            return this.$el.hasClass(csscls('minimized'));
1035        },
1036
1037        /**
1038         * Close the debug bar
1039         *
1040         * @this {DebugBar}
1041         */
1042        close: function() {
1043            this.$resizehdle.hide();
1044            this.$header.hide();
1045            this.$body.hide();
1046            this.$restorebtn.show();
1047            localStorage.setItem('phpdebugbar-open', '0');
1048            this.$el.addClass(csscls('closed'));
1049            this.recomputeBottomOffset();
1050        },
1051
1052        /**
1053         * Checks if the panel is closed
1054         *
1055         * @return {Boolean}
1056         */
1057        isClosed: function() {
1058            return this.$el.hasClass(csscls('closed'));
1059        },
1060
1061        /**
1062         * Restore the debug bar
1063         *
1064         * @this {DebugBar}
1065         */
1066        restore: function() {
1067            this.$resizehdle.show();
1068            this.$header.show();
1069            this.$restorebtn.hide();
1070            localStorage.setItem('phpdebugbar-open', '1');
1071            var tab = localStorage.getItem('phpdebugbar-tab');
1072            if (this.isTab(tab)) {
1073                this.showTab(tab);
1074            } else {
1075                this.showTab();
1076            }
1077            this.$el.removeClass(csscls('closed'));
1078            this.resize();
1079        },
1080
1081        /**
1082         * Recomputes the margin-bottom css property of the body so
1083         * that the debug bar never hides any content
1084         */
1085        recomputeBottomOffset: function() {
1086            if (this.options.bodyMarginBottom) {
1087                if (this.isClosed()) {
1088                    return $('body').css('margin-bottom', this.bodyMarginBottomHeight || '');
1089                }
1090
1091                var offset = parseInt(this.$el.height()) + (this.bodyMarginBottomHeight || 0);
1092                $('body').css('margin-bottom', offset);
1093            }
1094        },
1095
1096        /**
1097         * Sets the data map used by dataChangeHandler to populate
1098         * indicators and widgets
1099         *
1100         * A data map is an object where properties are control names.
1101         * The value of each property should be an array where the first
1102         * item is the name of a property from the data object (nested properties
1103         * can be specified) and the second item the default value.
1104         *
1105         * Example:
1106         *     {"memory": ["memory.peak_usage_str", "0B"]}
1107         *
1108         * @this {DebugBar}
1109         * @param {Object} map
1110         */
1111        setDataMap: function(map) {
1112            this.dataMap = map;
1113        },
1114
1115        /**
1116         * Same as setDataMap() but appends to the existing map
1117         * rather than replacing it
1118         *
1119         * @this {DebugBar}
1120         * @param {Object} map
1121         */
1122        addDataMap: function(map) {
1123            $.extend(this.dataMap, map);
1124        },
1125
1126        /**
1127         * Resets datasets and add one set of data
1128         *
1129         * For this method to be usefull, you need to specify
1130         * a dataMap using setDataMap()
1131         *
1132         * @this {DebugBar}
1133         * @param {Object} data
1134         * @return {String} Dataset's id
1135         */
1136        setData: function(data) {
1137            this.datasets = {};
1138            return this.addDataSet(data);
1139        },
1140
1141        /**
1142         * Adds a dataset
1143         *
1144         * If more than one dataset are added, the dataset selector
1145         * will be displayed.
1146         *
1147         * For this method to be usefull, you need to specify
1148         * a dataMap using setDataMap()
1149         *
1150         * @this {DebugBar}
1151         * @param {Object} data
1152         * @param {String} id The name of this set, optional
1153         * @param {String} suffix
1154         * @param {Bool} show Whether to show the new dataset, optional (default: true)
1155         * @return {String} Dataset's id
1156         */
1157        addDataSet: function(data, id, suffix, show) {
1158            if (!data || !data.__meta) return;
1159            if (this.isIframe) {
1160                window.top.phpdebugbar.addDataSet(data, id, '(iframe)' + (suffix || ''), show);
1161                return;
1162            }
1163
1164            var nb = getObjectSize(this.datasets) + 1;
1165            id = id || nb;
1166            data.__meta['nb'] = nb;
1167            data.__meta['suffix'] = suffix;
1168            this.datasets[id] = data;
1169
1170            var label = this.datesetTitleFormater.format(id, this.datasets[id], suffix, nb);
1171
1172            if (this.datasetTab) {
1173                this.datasetTab.set('data', this.datasets);
1174                var datasetSize = getObjectSize(this.datasets);
1175                this.datasetTab.set('badge', datasetSize > 1 ? datasetSize : null);
1176                this.datasetTab.$tab.show();
1177            }
1178
1179            this.$datasets.append($('<option value="' + id + '">' + label + '</option>'));
1180            if (this.$datasets.children().length > 1) {
1181                this.$datasets.show();
1182            }
1183
1184            if (typeof(show) == 'undefined' || show) {
1185                this.showDataSet(id);
1186            }
1187
1188            this.resize();
1189
1190            return id;
1191        },
1192
1193        /**
1194         * Loads a dataset using the open handler
1195         *
1196         * @param {String} id
1197         * @param {Bool} show Whether to show the new dataset, optional (default: true)
1198         */
1199        loadDataSet: function(id, suffix, callback, show) {
1200            if (!this.openHandler) {
1201                throw new Error('loadDataSet() needs an open handler');
1202            }
1203            var self = this;
1204            this.openHandler.load(id, function(data) {
1205                self.addDataSet(data, id, suffix, show);
1206                self.resize();
1207                callback && callback(data);
1208            });
1209        },
1210
1211        /**
1212         * Returns the data from a dataset
1213         *
1214         * @this {DebugBar}
1215         * @param {String} id
1216         * @return {Object}
1217         */
1218        getDataSet: function(id) {
1219            return this.datasets[id];
1220        },
1221
1222        /**
1223         * Switch the currently displayed dataset
1224         *
1225         * @this {DebugBar}
1226         * @param {String} id
1227         */
1228        showDataSet: function(id) {
1229            this.activeDatasetId = id;
1230            this.dataChangeHandler(this.datasets[id]);
1231
1232            if (this.$datasets.val() !== id) {
1233                this.$datasets.val(id);
1234            }
1235
1236            if (this.datasetTab) {
1237                this.datasetTab.get('widget').set('id', id);
1238            }
1239        },
1240
1241        /**
1242         * Called when the current dataset is modified.
1243         *
1244         * @this {DebugBar}
1245         * @param {Object} data
1246         */
1247        dataChangeHandler: function(data) {
1248            var self = this;
1249            $.each(this.dataMap, function(key, def) {
1250                var d = getDictValue(data, def[0], def[1]);
1251                if (key.indexOf(':') != -1) {
1252                    key = key.split(':');
1253                    self.getControl(key[0]).set(key[1], d);
1254                } else {
1255                    self.getControl(key).set('data', d);
1256                }
1257            });
1258            self.resize();
1259        },
1260
1261        /**
1262         * Sets the handler to open past dataset
1263         *
1264         * @this {DebugBar}
1265         * @param {object} handler
1266         */
1267        setOpenHandler: function(handler) {
1268            this.openHandler = handler;
1269            this.openHandler.$el.attr('data-theme', this.$el.attr('data-theme'));
1270            if (handler !== null) {
1271                this.$openbtn.show();
1272            } else {
1273                this.$openbtn.hide();
1274            }
1275        },
1276
1277        /**
1278         * Returns the handler to open past dataset
1279         *
1280         * @this {DebugBar}
1281         * @return {object}
1282         */
1283        getOpenHandler: function() {
1284            return this.openHandler;
1285        },
1286
1287        enableAjaxHandlerTab: function() {
1288            this.datasetTab = new PhpDebugBar.DebugBar.Tab({"icon":"history", "title":"Request history", "widget": new PhpDebugBar.Widgets.DatasetWidget({
1289                    'debugbar': this
1290                })});
1291            this.datasetTab.$tab.addClass(csscls('tab-history'));
1292            this.datasetTab.$tab.attr('data-collector', '__datasets');
1293            this.datasetTab.$el.attr('data-collector', '__datasets');
1294            this.datasetTab.$tab.insertAfter(this.$openbtn).hide();
1295            this.datasetTab.$tab.click(() => {
1296                if (!this.isMinimized() && this.activePanelName == '__datasets') {
1297                    this.minimize();
1298                } else {
1299                    this.showTab('__datasets');
1300                }
1301            });
1302            this.datasetTab.$el.appendTo(this.$body);
1303            this.controls['__datasets'] = this.datasetTab;
1304        },
1305    });
1306
1307    DebugBar.Tab = Tab;
1308    DebugBar.Indicator = Indicator;
1309
1310    // ------------------------------------------------------------------
1311
1312    /**
1313     * AjaxHandler
1314     *
1315     * Extract data from headers of an XMLHttpRequest and adds a new dataset
1316     *
1317     * @param {Bool} autoShow Whether to immediately show new datasets, optional (default: true)
1318     */
1319    var AjaxHandler = PhpDebugBar.AjaxHandler = function(debugbar, headerName, autoShow) {
1320        this.debugbar = debugbar;
1321        this.headerName = headerName || 'phpdebugbar';
1322        this.autoShow = typeof(autoShow) == 'undefined' ? true : autoShow;
1323        this.defaultAutoShow = this.autoShow;
1324        if (localStorage.getItem('phpdebugbar-ajaxhandler-autoshow') !== null) {
1325            this.autoShow = localStorage.getItem('phpdebugbar-ajaxhandler-autoshow') == '1';
1326        }
1327        if (debugbar.controls['__settings']) {
1328            debugbar.controls['__settings'].get('widget').set('autoshow', this.autoShow);
1329        }
1330    };
1331
1332    $.extend(AjaxHandler.prototype, {
1333
1334        /**
1335         * Handles a Fetch API Response or an XMLHttpRequest
1336         *
1337         * @this {AjaxHandler}
1338         * @param {Response|XMLHttpRequest} response
1339         * @return {Bool}
1340         */
1341        handle: function(response) {
1342            // Check if the debugbar header is available
1343            if (this.isFetch(response) && !response.headers.has(this.headerName + '-id')) {
1344                return true;
1345            } else if (this.isXHR(response) && response.getAllResponseHeaders().indexOf(this.headerName) === -1) {
1346                return true;
1347            }
1348            if (!this.loadFromId(response)) {
1349                return this.loadFromData(response);
1350            }
1351            return true;
1352        },
1353
1354        getHeader: function(response, header) {
1355            if (this.isFetch(response)) {
1356                return response.headers.get(header)
1357            }
1358
1359            return response.getResponseHeader(header)
1360        },
1361
1362        isFetch: function(response) {
1363            return Object.prototype.toString.call(response) == '[object Response]'
1364        },
1365
1366        isXHR: function(response) {
1367            return Object.prototype.toString.call(response) == '[object XMLHttpRequest]'
1368        },
1369
1370        setAutoShow: function(autoshow) {
1371            this.autoShow = autoshow;
1372            localStorage.setItem('phpdebugbar-ajaxhandler-autoshow', autoshow ? '1' : '0');
1373        },
1374
1375        /**
1376         * Checks if the HEADER-id exists and loads the dataset using the open handler
1377         *
1378         * @param {Response|XMLHttpRequest} response
1379         * @return {Bool}
1380         */
1381        loadFromId: function(response) {
1382            var id = this.extractIdFromHeaders(response);
1383            if (id && this.debugbar.openHandler) {
1384                this.debugbar.loadDataSet(id, "(ajax)", undefined, this.autoShow);
1385                return true;
1386            }
1387            return false;
1388        },
1389
1390        /**
1391         * Extracts the id from the HEADER-id
1392         *
1393         * @param {Response|XMLHttpRequest} response
1394         * @return {String}
1395         */
1396        extractIdFromHeaders: function(response) {
1397            return this.getHeader(response, this.headerName + '-id');
1398        },
1399
1400        /**
1401         * Checks if the HEADER exists and loads the dataset
1402         *
1403         * @param {Response|XMLHttpRequest} response
1404         * @return {Bool}
1405         */
1406        loadFromData: function(response) {
1407            var raw = this.extractDataFromHeaders(response);
1408            if (!raw) {
1409                return false;
1410            }
1411
1412            var data = this.parseHeaders(raw);
1413            if (data.error) {
1414                throw new Error('Error loading debugbar data: ' + data.error);
1415            } else if(data.data) {
1416                this.debugbar.addDataSet(data.data, data.id, "(ajax)", this.autoShow);
1417            }
1418            return true;
1419        },
1420
1421        /**
1422         * Extract the data as a string from headers of an XMLHttpRequest
1423         *
1424         * @this {AjaxHandler}
1425         * @param {Response|XMLHttpRequest} response
1426         * @return {string}
1427         */
1428        extractDataFromHeaders: function(response) {
1429            var data = this.getHeader(response, this.headerName);
1430            if (!data) {
1431                return;
1432            }
1433            for (var i = 1;; i++) {
1434                var header = this.getHeader(response, this.headerName + '-' + i);
1435                if (!header) {
1436                    break;
1437                }
1438                data += header;
1439            }
1440            return decodeURIComponent(data);
1441        },
1442
1443        /**
1444         * Parses the string data into an object
1445         *
1446         * @this {AjaxHandler}
1447         * @param {string} data
1448         * @return {string}
1449         */
1450        parseHeaders: function(data) {
1451            return JSON.parse(data);
1452        },
1453
1454        /**
1455         * Attaches an event listener to fetch
1456         *
1457         * @this {AjaxHandler}
1458         */
1459        bindToFetch: function() {
1460            var self = this;
1461            var proxied = window.fetch;
1462
1463            if (proxied !== undefined && proxied.polyfill !== undefined) {
1464                return;
1465            }
1466
1467            window.fetch = function () {
1468                var promise = proxied.apply(this, arguments);
1469
1470                promise.then(function (response) {
1471                    self.handle(response);
1472                }).catch(function(reason) {
1473                    // Fetch request failed or aborted via AbortController.abort().
1474                    // Catch is required to not trigger React's error handler.
1475                });
1476
1477                return promise;
1478            };
1479        },
1480
1481        /**
1482         * @deprecated use bindToXHR instead
1483         */
1484        bindToJquery: function(jq) {
1485            var self = this;
1486            jq(document).ajaxComplete(function(e, xhr, settings) {
1487                if (!settings.ignoreDebugBarAjaxHandler) {
1488                    self.handle(xhr);
1489                }
1490            });
1491        },
1492
1493        /**
1494         * Attaches an event listener to XMLHttpRequest
1495         *
1496         * @this {AjaxHandler}
1497         */
1498        bindToXHR: function() {
1499            var self = this;
1500            var proxied = XMLHttpRequest.prototype.open;
1501            XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
1502                var xhr = this;
1503                this.addEventListener("readystatechange", function() {
1504                    var skipUrl = self.debugbar.openHandler ? self.debugbar.openHandler.get('url') : null;
1505                    var href = (typeof url === 'string') ? url : url.href;
1506
1507                    if (xhr.readyState == 4 && href.indexOf(skipUrl) !== 0) {
1508                        self.handle(xhr);
1509                    }
1510                }, false);
1511                proxied.apply(this, Array.prototype.slice.call(arguments));
1512            };
1513        }
1514
1515    });
1516
1517})(PhpDebugBar.$);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.