Plugin Directory

Changeset 3717902


Ignore:
Timestamp:
09/28/2026 08:49:50 PM (3 weeks ago)
Author:
likoton
Message:

A date range selection filter has been added

Location:
likoton-debug-logs/trunk
Files:
26 edited

Legend:

Unmodified
Added
Removed
  • likoton-debug-logs/trunk/assets/admin-dark.css

    r3600839 r3717902  
    1 /* -------------------------------------------------------------
    2  * Dark mode background
    3  * ------------------------------------------------------------- */
     1/* Dark mode background */
    42
    53body.toplevel_page_likoton-debug-logs-logs,
    … …  
    97}
    108
    11 /* -------------------------------------------------------------
    12  * Table styling
    13  * ------------------------------------------------------------- */
     9/* Table styling */
    1410
    1511.ldl-logs.wp-list-table {
    … …  
    3632}
    3733
    38 /* -------------------------------------------------------------
    39  * Column widths
    40  * ------------------------------------------------------------- */
     34/* Column widths */
    4135.ldl-logs .column-id {
    4236    width: 60px;
    … …  
    6256}
    6357
    64 /* -------------------------------------------------------------
    65  * Selects and inputs
    66  * ------------------------------------------------------------- */
     58/* Selects and inputs */
    6759
    6860select,
    … …  
    8577input[type="search"]:focus,
    8678input[type="text"]:focus,
    87 input[type="number"]:focus {
     79input[type="number"]:focus  {
    8880    border-color: #2271b1 !important;
    8981    box-shadow: 0 0 0 1px #2271b1 !important;
     82}
     83
     84input.ldl-date-input:focus {
     85    border-color: #2271b1 !important;
     86    box-shadow: none !important;
     87    outline: none !important;
     88}
     89
     90input.ldl-date-input.is-active {
     91    border-color: #2271b1 !important;
     92    box-shadow: 0 0 0 1px #2271b1 !important;
     93}
     94
     95.ldl-date-trigger {
     96    color: #aaa !important;
     97}
     98
     99.ldl-datepicker {
     100    background-color: #1d2327 !important;
     101    border-color: #2c3338 !important;
     102    color: #f0f0f1 !important;
     103    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5) !important;
     104}
     105
     106.ldl-datepicker-footer button {
     107    color: #2271b1;
     108}
     109
     110.ldl-datepicker-day.is-selected {
     111    background: #2271b1;
     112}
     113
     114.ldl-datepicker-day.is-today {
     115    box-shadow: inset 0 0 0 1px #2271b1;
     116}
     117
     118.ldl-datepicker-nav {
     119    color: #f0f0f1 !important;
     120}
     121
     122.ldl-datepicker-nav:hover,
     123.ldl-datepicker-day:hover:not(:disabled) {
     124    background-color: #2c3338 !important;
     125}
     126
     127.ldl-datepicker-weekdays span {
     128    color: #999 !important;
     129}
     130
     131.ldl-datepicker-day:disabled {
     132    color: #555 !important;
     133}
     134
     135.ldl-datepicker-footer {
     136    border-top-color: #2c3338 !important;
    90137}
    91138
    … …  
    107154}
    108155
    109 /* -------------------------------------------------------------
    110  * Buttons
    111  * ------------------------------------------------------------- */
     156/* Buttons */
    112157
    113158.button,
    … …  
    118163}
    119164
    120 /* -------------------------------------------------------------
    121  * Footer
    122  * ------------------------------------------------------------- */
     165/* Footer */
    123166
    124167.ldl-footer img {
    … …  
    136179}
    137180
    138 /* -------------------------------------------------------------
    139  * Dark mode sorting indicators
    140  * ------------------------------------------------------------- */
     181/* Dark mode sorting indicators */
    141182
    142183#ldl-logs-table th.sorted-column {
    … …  
    149190}
    150191
    151 /* -------------------------------------------------------------
    152  * Dark mode text
    153  * ------------------------------------------------------------- */
     192/* Dark mode text */
    154193.form-table th, h1, h2, h3 {
    155194        color: #f0f0f1;
    156195}
    157196
    158 /* -------------------------------------------------------------
    159  * WP7 NAV — Dark Mode
    160  * ------------------------------------------------------------- */
     197/* WP7 NAV — Dark Mode */
    161198 
    162199 .health-check-tabs-wrapper {
    … …  
    183220}
    184221
    185 /* -------------------------------------------------------------
    186  * Auto save toast
    187  * ------------------------------------------------------------- */
     222/* Auto save toast */
    188223
    189224.ldl-toast {
    … …  
    191226}
    192227
    193 /* -------------------------------------------------------------
    194  * Donation page — Dark Mode
    195  * ------------------------------------------------------------- */
     228/* Donation page — Dark Mode */
    196229
    197230body.likoton-debug-logs-logs_page_likoton-debug-logs-donation,
    … …  
    215248.ldl-donation-box img {
    216249        height: 32px;
    217     filter: brightness(9); /* delikatne przyciemnienie dla lepszego kontrastu */
     250    filter: brightness(9);
    218251    transition: filter 0.2s ease;
    219252}
    … …  
    228261}
    229262
    230 /* -------------------------------------------------------------
    231  * Segmented Control
    232  * ------------------------------------------------------------- */
     263/* Segmented Control */
    233264
    234265 .ldl-segment {
  • likoton-debug-logs/trunk/assets/admin.css

    r3647996 r3717902  
    7272    padding: 0 8px;
    7373    box-sizing: border-box;
     74}
     75
     76/* Date range filter */
     77
     78.ldl-date-range {
     79    display: none;
     80    align-items: center;
     81    gap: 8px;
     82}
     83
     84.ldl-date-range.is-visible {
     85    display: flex;
     86}
     87
     88.ldl-date-range label {
     89    font-size: 12px;
     90    white-space: nowrap;
     91}
     92
     93.ldl-date-field {
     94    position: relative;
     95    display: inline-flex;
     96    align-items: center;
     97}
     98
     99.ldl-date-input {
     100    height: 32px;
     101    padding: 0 30px 0 8px;
     102    border: 1px solid #ccc;
     103    border-radius: 2px;
     104    box-sizing: border-box;
     105    background-color: #fff !important;
     106    cursor: pointer;
     107    width: 120px;
     108    caret-color: transparent;
     109    user-select: none;
     110}
     111
     112input.ldl-date-input:focus {
     113    border-color: #ccc !important;
     114    box-shadow: none !important;
     115    outline: none !important;
     116}
     117
     118input.ldl-date-input.is-active {
     119    border-color: var(--wp-admin-theme-color) !important;
     120    box-shadow: 0 0 0 var(--wp-admin-border-width-focus, 1.5px) var(--wp-admin-theme-color) !important;
     121}
     122
     123.ldl-date-trigger {
     124    position: absolute;
     125    right: 2px;
     126    top: 50%;
     127    transform: translateY(-50%);
     128    background: none;
     129    border: none;
     130    cursor: pointer;
     131    padding: 4px;
     132    display: flex;
     133    align-items: center;
     134    color: #555;
     135}
     136
     137.ldl-date-trigger .dashicons {
     138    font-size: 18px;
     139    width: 18px;
     140    height: 18px;
     141}
     142
     143/* Custom date picker popup */
     144
     145.ldl-datepicker {
     146    display: none;
     147    position: absolute;
     148    z-index: 100000;
     149    top: 100%;
     150    left: 0;
     151    margin-top: 4px;
     152    background: #fff;
     153    border: 1px solid #ccc;
     154    border-radius: 4px;
     155    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
     156    padding: 10px;
     157    width: 240px;
     158}
     159
     160.ldl-datepicker.is-open {
     161    display: block;
     162}
     163
     164.ldl-datepicker-header {
     165    display: flex;
     166    align-items: center;
     167    justify-content: space-between;
     168    margin-bottom: 8px;
     169}
     170
     171.ldl-datepicker-title {
     172    font-weight: 600;
     173    text-transform: capitalize;
     174    font-size: 13px;
     175}
     176
     177.ldl-datepicker-nav {
     178    background: none;
     179    border: none;
     180    cursor: pointer;
     181    font-size: 16px;
     182    line-height: 1;
     183    padding: 2px 8px;
     184    color: #333;
     185    border-radius: 2px;
     186}
     187
     188.ldl-datepicker-nav:hover {
     189    background: #f0f0f1;
     190}
     191
     192.ldl-datepicker-weekdays,
     193.ldl-datepicker-days {
     194    display: grid;
     195    grid-template-columns: repeat(7, 1fr);
     196    gap: 2px;
     197    text-align: center;
     198}
     199
     200.ldl-datepicker-weekdays span {
     201    font-size: 11px;
     202    color: #777;
     203    text-transform: capitalize;
     204    padding: 4px 0;
     205}
     206
     207.ldl-datepicker-day {
     208    background: none;
     209    border: none;
     210    padding: 6px 0;
     211    font-size: 12px;
     212    border-radius: 3px;
     213    cursor: pointer;
     214    color: inherit;
     215}
     216
     217.ldl-datepicker-day:hover:not(:disabled) {
     218    background: #f0f0f1;
     219}
     220
     221.ldl-datepicker-day.is-empty {
     222    cursor: default;
     223}
     224
     225.ldl-datepicker-day.is-today {
     226    box-shadow: inset 0 0 0 1px var(--wp-admin-theme-color);
     227}
     228
     229.ldl-datepicker-day.is-selected {
     230    background:var(--wp-admin-theme-color-darker-10);
     231    color: #fff;
     232}
     233
     234.ldl-datepicker-day:disabled {
     235    color: #ccc;
     236    cursor: not-allowed;
     237}
     238
     239.ldl-datepicker-footer {
     240    display: flex;
     241    justify-content: space-between;
     242    margin-top: 8px;
     243    padding-top: 8px;
     244    border-top: 1px solid #eee;
     245}
     246
     247.ldl-datepicker-footer button {
     248    background: none;
     249    border: none;
     250    color: var(--wp-admin-theme-color);
     251    cursor: pointer;
     252    font-size: 12px;
     253    font-weight:600;
     254    padding: 0;
     255}
     256
     257.ldl-datepicker-footer button:hover {
     258    text-decoration: underline;
    74259}
    75260
    … …  
    120305
    121306#ldl-logs-table {
    122         border-radius: 2px;
     307        border:none;
    123308        min-width: 600px;
    124309    border-collapse: collapse;
    … …  
    222407.ldl-actions {
    223408    display: flex;
     409    flex-direction: row;
     410    align-items: stretch;
    224411    gap: 10px;
    225412    margin-top: 15px;
    … …  
    431618        width: 100%;
    432619    }
    433        
    434         .ldl-actions {
     620
     621    .ldl-date-range.is-visible {
     622        flex-direction: column;
     623        align-items: stretch;
     624        width: 100%;
     625    }
     626
     627    .ldl-date-field,
     628    .ldl-date-input {
     629        width: 100%;
     630    }
     631
     632    .ldl-actions {
    435633        flex-direction: column;
    436634        align-items: stretch;
    437635    }
    438636
     637    .ldl-datepicker {
     638        left: auto;
     639        right: 0;
     640    }
     641
    439642    .ldl-actions button {
    440643        width: 100%;
  • likoton-debug-logs/trunk/assets/admin.js

    r3647996 r3717902  
    5353        const $filters = $('#ldl-filters');
    5454
     55        let scrollPage = 1;
     56        let scrollLoading = false;
     57        let scrollDone = false;
     58
    5559        function reloadTable() {
    5660            const url = $filters.attr('action');
    … …  
    6367                    applyDefaultDateSort();
    6468                }
     69                scrollPage = 1;
     70                scrollDone = false;
    6571            });
    6672        }
    … …  
    7480            $filters.on('change', 'select', function () {
    7581                reloadTable();
     82            });
     83
     84            const $dateRange = $('#ldl-date-range');
     85            const $dateFrom = $('#ldl-date-from');
     86            const $dateTo = $('#ldl-date-to');
     87
     88            function toggleDateRange() {
     89                $dateRange.toggleClass('is-visible', $filters.find('select[name="last"]').val() === 'range');
     90            }
     91
     92            function syncDateConstraints() {
     93                if ($dateFrom.val()) {
     94                    $dateTo.attr('data-min', $dateFrom.val());
     95                }
     96                if ($dateTo.val()) {
     97                    $dateFrom.attr('data-max', $dateTo.val());
     98                }
     99            }
     100
     101            toggleDateRange();
     102            syncDateConstraints();
     103
     104            $filters.on('change', 'select[name="last"]', toggleDateRange);
     105
     106            $filters.on('change', '.ldl-date-input', function () {
     107                syncDateConstraints();
     108                reloadTable();
     109            });
     110
     111            initDatePicker();
     112        }
     113
     114        function initDatePicker() {
     115            const locale = (window.likotonDebugLogsData && likotonDebugLogsData.locale) || 'en-US';
     116            const i18n = (window.likotonDebugLogsData && likotonDebugLogsData.i18n) || {};
     117            const labelToday = i18n.today || 'Today';
     118            const labelClear = i18n.clear || 'Clear';
     119
     120            const $panel = $(
     121                '<div class="ldl-datepicker">' +
     122                    '<div class="ldl-datepicker-header">' +
     123                        '<button type="button" class="ldl-datepicker-nav ldl-datepicker-prev">&lsaquo;</button>' +
     124                        '<span class="ldl-datepicker-title"></span>' +
     125                        '<button type="button" class="ldl-datepicker-nav ldl-datepicker-next">&rsaquo;</button>' +
     126                    '</div>' +
     127                    '<div class="ldl-datepicker-weekdays"></div>' +
     128                    '<div class="ldl-datepicker-days"></div>' +
     129                    '<div class="ldl-datepicker-footer">' +
     130                        '<button type="button" class="ldl-datepicker-clear"></button>' +
     131                        '<button type="button" class="ldl-datepicker-today"></button>' +
     132                    '</div>' +
     133                '</div>'
     134            );
     135
     136            $panel.find('.ldl-datepicker-clear').text(labelClear);
     137            $panel.find('.ldl-datepicker-today').text(labelToday);
     138
     139            let $activeInput = null;
     140            let viewYear;
     141            let viewMonth;
     142
     143            function pad(n) {
     144                return (n < 10 ? '0' : '') + n;
     145            }
     146
     147            function toISO(y, m, d) {
     148                return y + '-' + pad(m + 1) + '-' + pad(d);
     149            }
     150
     151            function parseISO(str) {
     152                const parts = /^(\d{4})-(\d{2})-(\d{2})$/.exec(str || '');
     153                if (!parts) {
     154                    return null;
     155                }
     156                return { y: parseInt(parts[1], 10), m: parseInt(parts[2], 10) - 1, d: parseInt(parts[3], 10) };
     157            }
     158
     159            function weekdayNames() {
     160                const fmt = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' });
     161                const names = [];
     162                const base = Date.UTC(2023, 0, 2); // A Monday.
     163                for (let i = 0; i < 7; i++) {
     164                    names.push(fmt.format(new Date(base + i * 86400000)));
     165                }
     166                return names;
     167            }
     168
     169            function monthTitle(y, m) {
     170                const fmt = new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' });
     171                return fmt.format(new Date(y, m, 1));
     172            }
     173
     174            function render() {
     175                $panel.find('.ldl-datepicker-title').text(monthTitle(viewYear, viewMonth));
     176
     177                const $wd = $panel.find('.ldl-datepicker-weekdays').empty();
     178                weekdayNames().forEach(function (n) {
     179                    $('<span></span>').text(n).appendTo($wd);
     180                });
     181
     182                const $days = $panel.find('.ldl-datepicker-days').empty();
     183
     184                const firstOfMonth = new Date(viewYear, viewMonth, 1);
     185                const startOffset = (firstOfMonth.getDay() + 6) % 7; // Monday = 0.
     186                const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
     187
     188                const minAttr = $activeInput.attr('data-min') || '';
     189                const maxAttr = $activeInput.attr('data-max') || '';
     190                const selectedISO = $activeInput.val();
     191                const now = new Date();
     192                const todayISO = toISO(now.getFullYear(), now.getMonth(), now.getDate());
     193
     194                for (let i = 0; i < startOffset; i++) {
     195                    $('<span class="ldl-datepicker-day is-empty"></span>').appendTo($days);
     196                }
     197
     198                for (let d = 1; d <= daysInMonth; d++) {
     199                    const iso = toISO(viewYear, viewMonth, d);
     200                    const disabled = (minAttr && iso < minAttr) || (maxAttr && iso > maxAttr);
     201                    const $btn = $('<button type="button" class="ldl-datepicker-day"></button>')
     202                        .text(d)
     203                        .attr('data-date', iso)
     204                        .prop('disabled', disabled);
     205
     206                    if (iso === todayISO) {
     207                        $btn.addClass('is-today');
     208                    }
     209                    if (iso === selectedISO) {
     210                        $btn.addClass('is-selected');
     211                    }
     212
     213                    $btn.appendTo($days);
     214                }
     215            }
     216
     217            function open($input) {
     218                if ($activeInput) {
     219                    $activeInput.removeClass('is-active');
     220                }
     221                $activeInput = $input;
     222                const parsed = parseISO($input.val()) || (function () {
     223                    const now = new Date();
     224                    return { y: now.getFullYear(), m: now.getMonth(), d: now.getDate() };
     225                }());
     226                viewYear = parsed.y;
     227                viewMonth = parsed.m;
     228                render();
     229
     230                const $field = $input.closest('.ldl-date-field');
     231                $field.append($panel.detach());
     232                $panel.addClass('is-open');
     233                $input.addClass('is-active');
     234            }
     235
     236            function close() {
     237                $panel.removeClass('is-open');
     238                if ($activeInput) {
     239                    $activeInput.removeClass('is-active');
     240                }
     241                $activeInput = null;
     242            }
     243
     244            $(document).on('mousedown', '.ldl-date-input', function (e) {
     245                e.preventDefault();
     246            });
     247
     248            $(document).on('click', '.ldl-date-trigger, .ldl-date-input', function (e) {
     249                e.preventDefault();
     250                if (document.activeElement && document.activeElement !== document.body) {
     251                    document.activeElement.blur();
     252                }
     253                const $field = $(this).closest('.ldl-date-field');
     254                const $input = $field.find('.ldl-date-input');
     255                if ($activeInput && $activeInput[0] === $input[0] && $panel.hasClass('is-open')) {
     256                    close();
     257                } else {
     258                    open($input);
     259                }
     260            });
     261
     262            $(document).on('click', function (e) {
     263                if ($panel.hasClass('is-open') && !$(e.target).closest('.ldl-datepicker, .ldl-date-field').length) {
     264                    close();
     265                }
     266            });
     267
     268            $(document).on('keydown', function (e) {
     269                if (e.key === 'Escape' && $panel.hasClass('is-open')) {
     270                    close();
     271                }
     272            });
     273
     274            $panel.on('click', '.ldl-datepicker-prev', function () {
     275                viewMonth--;
     276                if (viewMonth < 0) {
     277                    viewMonth = 11;
     278                    viewYear--;
     279                }
     280                render();
     281            });
     282
     283            $panel.on('click', '.ldl-datepicker-next', function () {
     284                viewMonth++;
     285                if (viewMonth > 11) {
     286                    viewMonth = 0;
     287                    viewYear++;
     288                }
     289                render();
     290            });
     291
     292            $panel.on('click', '.ldl-datepicker-day:not(:disabled):not(.is-empty)', function () {
     293                const iso = $(this).attr('data-date');
     294                $activeInput.val(iso).trigger('change');
     295                close();
     296            });
     297
     298            $panel.on('click', '.ldl-datepicker-today', function () {
     299                const now = new Date();
     300                const iso = toISO(now.getFullYear(), now.getMonth(), now.getDate());
     301                const minAttr = $activeInput.attr('data-min') || '';
     302                const maxAttr = $activeInput.attr('data-max') || '';
     303                if ((minAttr && iso < minAttr) || (maxAttr && iso > maxAttr)) {
     304                    return;
     305                }
     306                $activeInput.val(iso).trigger('change');
     307                close();
     308            });
     309
     310            $panel.on('click', '.ldl-datepicker-clear', function () {
     311                const isFrom = $activeInput.attr('id') === 'ldl-date-from';
     312                const $from = $('#ldl-date-from');
     313                const $to = $('#ldl-date-to');
     314
     315                const now = new Date();
     316                const today = toISO(now.getFullYear(), now.getMonth(), now.getDate());
     317                const yestDate = new Date(now);
     318                yestDate.setDate(now.getDate() - 1);
     319                const yesterday = toISO(yestDate.getFullYear(), yestDate.getMonth(), yestDate.getDate());
     320
     321                if (isFrom) {
     322                    $from.val(yesterday);
     323                    if ($to.val() && $to.val() < yesterday) {
     324                        $to.val(today);
     325                    }
     326                } else {
     327                    $to.val(today);
     328                    if ($from.val() && $from.val() > today) {
     329                        $from.val(yesterday);
     330                    }
     331                }
     332
     333                $activeInput.trigger('change');
     334                close();
    76335            });
    77336        }
    … …  
    180439            });
    181440
    182             let page = 1;
    183             let loading = false;
    184             let done = false;
    185 
    186441            const $loader = $('<div class="ldl-scroll-loader">Loading…</div>').css({
    187442                padding: '15px',
    … …  
    194449            $wrapper.append($loader);
    195450
     451            function filterValue(name) {
     452                const found = $filters.serializeArray().find(function (item) {
     453                    return item.name === name;
     454                });
     455                return found ? found.value : '';
     456            }
     457
    196458            function loadMore() {
    197                 if (loading || done) return;
     459                if (scrollLoading || scrollDone) return;
    198460                const $tableBody = $('#ldl-logs-table tbody');
    199461                if (!$tableBody.length) return;
    200                 loading = true;
     462                scrollLoading = true;
    201463                $loader.css('opacity', 1);
    202                 const params = new URLSearchParams(window.location.search);
    203464                $.get(ajaxUrl, {
    204465                    action: 'likoton_debug_logs_load_more_logs',
    205                     page_num: page + 1,
    206                     s: params.get('s') || '',
    207                     level: params.get('level') || '',
    208                     source: params.get('source') || '',
    209                     last: params.get('last') || 50,
     466                    page_num: scrollPage + 1,
     467                    s: filterValue('s'),
     468                    level: filterValue('level'),
     469                    source: filterValue('source'),
     470                    last: filterValue('last') || 50,
    210471                    likoton_debug_logs_nonce: likotonDebugLogsData.nonce
    211472                }, function (response) {
    212473                    if (response.success) {
    213474                        if (response.data.done) {
    214                             done = true;
     475                            scrollDone = true;
    215476                            $loader.text('✔ All logs loaded');
    216477                            return;
    217478                        }
    218                         page++;
     479                        scrollPage++;
    219480                        $tableBody.append(response.data.html);
    220481                    }
    221482                }).always(function () {
    222                     loading = false;
     483                    scrollLoading = false;
    223484                    $loader.css('opacity', 0);
    224485                });
    … …  
    243504                data.push({ name: 'last', value: 50 });
    244505            }
     506
     507            data.push({ name: 'auto_refresh', value: '1' });
    245508
    246509            const url = $filters.attr('action');
  • likoton-debug-logs/trunk/includes/class-likoton-debug-logs-admin.php

    r3647996 r3717902  
    66}
    77
    8 /**
    9  * Admin interface for LiKoToN Debug Logs
    10  */
     8/* Admin interface for LiKoToN Debug Logs */
    119class Likoton_Debug_Logs_Admin {
    1210
    … …  
    2624    }
    2725
    28     /**
    29      * Levels
    30      */
     26    /* Levels */
    3127    private static function get_all_levels() {
    3228        return [
    … …  
    3935    }
    4036
    41     /**
    42      * Schedules
    43      */
     37    /* Schedules */
    4438    public static function add_schedules( $schedules ) {
    4539        $schedules['likoton_debug_logs_every_30_minutes'] = [
    … …  
    5650    }
    5751
    58     /**
    59      * Auto-save
    60      */
     52    /* Auto-save */
    6153    public static function ajax_save_settings() {
    6254        check_ajax_referer( 'likoton_debug_logs_save_settings', 'likoton_debug_logs_nonce' );
    … …  
    117109        $last_raw = isset( $_GET['last'] ) ? sanitize_text_field( wp_unslash( $_GET['last'] ) ) : 50;
    118110
    119         if ( $last_raw === 'all' ) {
     111        if ( $last_raw === 'all' || $last_raw === 'range' ) {
    120112            wp_send_json_success( [ 'html' => '', 'done' => true ] );
    121113        }
    122114
    123         $per_page = (int) $last_raw;
    124         if ( $per_page <= 0 ) {
    125             $per_page = 50;
     115        $time_windowed = in_array( $last_raw, [ 'hour', 'today', 'week' ], true );
     116
     117        if ( $time_windowed ) {
     118            $per_page = 100;
     119        } else {
     120            $per_page = (int) $last_raw;
     121            if ( $per_page <= 0 ) {
     122                $per_page = 50;
     123            }
    126124        }
    127125
    … …  
    132130            'page'     => $page,
    133131            'per_page' => $per_page,
     132            'since'    => $time_windowed ? self::compute_since( $last_raw ) : '',
    134133        ] );
    135134
    … …  
    156155    }
    157156
    158     /**
    159      * Automatic log cleanup
    160      */
     157    /* Automatic log cleanup */
    161158
    162159    public static function cleanup_logs() {
    … …  
    191188    }
    192189
    193     /**
    194      * Register admin menu
    195      */
     190    /* Register admin menu */
    196191    public static function menu() {
    197192        $capability = get_option( 'likoton_debug_logs_capability', 'manage_options' );
    … …  
    239234    }
    240235
    241     /**
    242      * Header
    243      */
     236    /* Header */
    244237    public static function render_header() {
    245238        ?>
    … …  
    248241    }
    249242
    250     /**
    251      * Render Logs page
    252      */
     243    /* Render Logs page */
    253244    public static function render_logs_page() {
    254245        ?>
    … …  
    270261    }
    271262
    272     /**
    273      * Render Settings page
    274      */
     263    /* Render Settings page */
    275264    public static function render_settings_page() {
    276265
    … …  
    441430    }
    442431
    443     /**
    444      * Render tabs navigation
    445      */
     432    /* Render tabs navigation */
    446433    private static function render_tabs( $active = 'logs' ) {
    447434        ?>
    … …  
    471458    }
    472459
    473     /**
    474      * Format log level
    475      */
     460    /* Format log level */
    476461    private static function format_level( $level ) {
    477462        return ucwords( str_replace( '_', ' ', $level ) );
    478463    }
    479464
    480     /**
    481      * Format UTC date to local WordPress time
    482      */
     465    /* Compute the UTC datetime lower bound for the "hour" / "today" / "week" quick filters. */
     466    private static function compute_since( $last_raw ) {
     467        $now = current_datetime();
     468
     469        switch ( $last_raw ) {
     470            case 'hour':
     471                $since = $now->modify( '-1 hour' );
     472                break;
     473
     474            case 'today':
     475                $since = $now->setTime( 0, 0, 0 );
     476                break;
     477
     478            case 'week':
     479                $since = $now->modify( '-7 days' );
     480                break;
     481
     482            default:
     483                return '';
     484        }
     485
     486        return $since->setTimezone( new \DateTimeZone( 'UTC' ) )->format( 'Y-m-d H:i:s' );
     487    }
     488
     489    /* Format UTC date to local WordPress time */
    483490    public static function format_local_date( $utc_string ) {
    484491
    … …  
    501508    }
    502509
    503     /**
    504      * Delete logs
    505      */
     510    /* Delete logs */
    506511    public static function handle_delete_logs() {
    507512
    … …  
    522527    }
    523528
    524     /**
    525      * Export logs
    526      */
     529    /* Export logs */
    527530    public static function handle_export_logs() {
    528531
    … …  
    578581    }
    579582
    580     /**
    581      * Render logs table and filters
    582      */
     583    /* Render logs table and filters */
    583584    public static function render_logs_content() {
    584585
    585         // phpcs:disable WordPress.Security.NonceVerification.Recommended
    586         $search   = isset( $_GET['s'] )      ? sanitize_text_field( wp_unslash( $_GET['s'] ) )      : '';
    587         $last_raw = isset( $_GET['last'] )   ? sanitize_text_field( wp_unslash( $_GET['last'] ) )   : '50';
    588         $level    = isset( $_GET['level'] )  ? sanitize_text_field( wp_unslash( $_GET['level'] ) )  : '';
    589         $source   = isset( $_GET['source'] ) ? sanitize_text_field( wp_unslash( $_GET['source'] ) ) : '';
    590         // phpcs:enable WordPress.Security.NonceVerification.Recommended
     586        $filter_keys = [ 's', 'level', 'source', 'last', 'date_from', 'date_to' ];
     587        $user_id     = get_current_user_id();
     588
     589        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only presence check, no state change
     590        $has_explicit_filters = (bool) array_filter( $filter_keys, function( $key ) {
     591            return isset( $_GET[ $key ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     592        } );
     593
     594        if ( $has_explicit_filters ) {
     595            // phpcs:disable WordPress.Security.NonceVerification.Recommended
     596            $search    = isset( $_GET['s'] )         ? sanitize_text_field( wp_unslash( $_GET['s'] ) )         : '';
     597            $last_raw  = isset( $_GET['last'] )      ? sanitize_text_field( wp_unslash( $_GET['last'] ) )      : 'today';
     598            $level     = isset( $_GET['level'] )     ? sanitize_text_field( wp_unslash( $_GET['level'] ) )     : '';
     599            $source    = isset( $_GET['source'] )    ? sanitize_text_field( wp_unslash( $_GET['source'] ) )    : '';
     600            $date_from = isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : '';
     601            $date_to   = isset( $_GET['date_to'] )   ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) )   : '';
     602            // phpcs:enable WordPress.Security.NonceVerification.Recommended
     603
     604            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only flag, no state change itself
     605            $is_auto_refresh = isset( $_GET['auto_refresh'] );
     606
     607            if ( $user_id && ! $is_auto_refresh ) {
     608                update_user_meta( $user_id, 'likoton_debug_logs_filters', [
     609                    's'         => $search,
     610                    'level'     => $level,
     611                    'source'    => $source,
     612                    'last'      => $last_raw,
     613                    'date_from' => $date_from,
     614                    'date_to'   => $date_to,
     615                ] );
     616            }
     617        } else {
     618            $remembered = $user_id ? get_user_meta( $user_id, 'likoton_debug_logs_filters', true ) : '';
     619            $remembered = is_array( $remembered ) ? $remembered : [];
     620
     621            $search    = isset( $remembered['s'] )         ? $remembered['s']         : '';
     622            $last_raw  = isset( $remembered['last'] )      ? $remembered['last']      : 'today';
     623            $level     = isset( $remembered['level'] )     ? $remembered['level']     : '';
     624            $source    = isset( $remembered['source'] )    ? $remembered['source']    : '';
     625            $date_from = isset( $remembered['date_from'] ) ? $remembered['date_from'] : '';
     626            $date_to   = isset( $remembered['date_to'] )   ? $remembered['date_to']   : '';
     627        }
    591628
    592629        $page = 1;
    593630
    594         if ( $last_raw === 'all' ) {
     631        $time_windowed = in_array( $last_raw, [ 'hour', 'today', 'week' ], true );
     632
     633        if ( $last_raw === 'all' || $last_raw === 'range' ) {
    595634            $per_page = 999999;
     635        } elseif ( $time_windowed ) {
     636            $per_page = 100;
    596637        } else {
    597638            $per_page = (int) $last_raw;
    … …  
    603644        $last = $last_raw;
    604645
     646        $today_local = current_datetime()->format( 'Y-m-d' );
     647
     648        if ( '' === $date_from ) {
     649            $date_from = current_datetime()->modify( '-1 day' )->format( 'Y-m-d' );
     650        }
     651
     652        if ( '' === $date_to ) {
     653            $date_to = $today_local;
     654        }
     655
    605656        $logs = Likoton_Debug_Logs_Installer::get_logs( [
    606             'search'   => $search,
    607             'level'    => $level,
    608             'source'   => $source,
    609             'page'     => $page,
    610             'per_page' => $per_page,
     657            'search'    => $search,
     658            'level'     => $level,
     659            'source'    => $source,
     660            'page'      => $page,
     661            'per_page'  => $per_page,
     662            'date_from' => $last_raw === 'range' ? $date_from : '',
     663            'date_to'   => $last_raw === 'range' ? $date_to : '',
     664            'since'     => $time_windowed ? self::compute_since( $last_raw ) : '',
    611665        ] );
    612666        ?>
    … …  
    647701
    648702                <select name="last">
    649                     <option value="20" <?php selected( $last, 20 ); ?>><?php esc_html_e( 'Last 20', 'likoton-debug-logs' ); ?></option>
    650                     <option value="50" <?php selected( $last, 50 ); ?>><?php esc_html_e( 'Last 50', 'likoton-debug-logs' ); ?></option>
    651                     <option value="100" <?php selected( $last, 100 ); ?>><?php esc_html_e( 'Last 100', 'likoton-debug-logs' ); ?></option>
    652                     <option value="200" <?php selected( $last, 200 ); ?>><?php esc_html_e( 'Last 200', 'likoton-debug-logs' ); ?></option>
    653                     <option value="500" <?php selected( $last, 500 ); ?>><?php esc_html_e( 'Last 500', 'likoton-debug-logs' ); ?></option>
    654                     <option value="1000" <?php selected( $last, 1000 ); ?>><?php esc_html_e( 'Last 1000', 'likoton-debug-logs' ); ?></option>
     703                    <option value="hour" <?php selected( $last, 'hour' ); ?>><?php esc_html_e( 'Last hour', 'likoton-debug-logs' ); ?></option>
     704                    <option value="today" <?php selected( $last, 'today' ); ?>><?php esc_html_e( 'Today', 'likoton-debug-logs' ); ?></option>
     705                    <option value="week" <?php selected( $last, 'week' ); ?>><?php esc_html_e( 'Last week', 'likoton-debug-logs' ); ?></option>
    655706                    <option value="all" <?php selected( $last, 'all' ); ?>><?php esc_html_e( 'All logs', 'likoton-debug-logs' ); ?></option>
     707                    <option value="range" <?php selected( $last, 'range' ); ?>><?php esc_html_e( 'Date range', 'likoton-debug-logs' ); ?></option>
    656708                </select>
     709
     710                <div class="ldl-date-range<?php echo $last_raw === 'range' ? ' is-visible' : ''; ?>" id="ldl-date-range">
     711                    <label for="ldl-date-from"><?php esc_html_e( 'From date', 'likoton-debug-logs' ); ?></label>
     712                    <div class="ldl-date-field">
     713                        <input type="text" name="date_from" id="ldl-date-from" class="ldl-date-input"
     714                               value="<?php echo esc_attr( $date_from ); ?>"
     715                               data-max="<?php echo esc_attr( $date_to ); ?>"
     716                               readonly="readonly" autocomplete="off" />
     717                        <button type="button" class="ldl-date-trigger" aria-label="<?php esc_attr_e( 'Choose date', 'likoton-debug-logs' ); ?>">
     718                            <span class="dashicons dashicons-calendar-alt"></span>
     719                        </button>
     720                    </div>
     721
     722                    <label for="ldl-date-to"><?php esc_html_e( 'To date', 'likoton-debug-logs' ); ?></label>
     723                    <div class="ldl-date-field">
     724                        <input type="text" name="date_to" id="ldl-date-to" class="ldl-date-input"
     725                               value="<?php echo esc_attr( $date_to ); ?>"
     726                               data-min="<?php echo esc_attr( $date_from ); ?>"
     727                               data-max="<?php echo esc_attr( $today_local ); ?>"
     728                               readonly="readonly" autocomplete="off" />
     729                        <button type="button" class="ldl-date-trigger" aria-label="<?php esc_attr_e( 'Choose date', 'likoton-debug-logs' ); ?>">
     730                            <span class="dashicons dashicons-calendar-alt"></span>
     731                        </button>
     732                    </div>
     733                </div>
    657734
    658735            </div>
  • likoton-debug-logs/trunk/includes/class-likoton-debug-logs-assets.php

    r3600839 r3717902  
    4646
    4747                wp_localize_script( 'likoton-debug-logs-admin', 'likotonDebugLogsData', [
    48                         'nonce' => wp_create_nonce( 'likoton_debug_logs_load_more_logs' ),
     48                        'nonce'  => wp_create_nonce( 'likoton_debug_logs_load_more_logs' ),
     49                        'locale' => str_replace( '_', '-', get_user_locale() ),
     50                        'i18n'   => [
     51                                'today' => __( 'Today', 'likoton-debug-logs' ),
     52                                'clear' => __( 'Clear', 'likoton-debug-logs' ),
     53                        ],
    4954                ] );
    5055    }
  • likoton-debug-logs/trunk/includes/class-likoton-debug-logs-installer.php

    r3647996 r3717902  
    1111    const TABLE_NAME = 'likoton_debug_logs';
    1212
    13     /** Default colors for known sources */
     13    /* Default colors for known sources */
    1414    public static function get_default_source_colors() {
    1515        return [
    … …  
    101101
    102102        $defaults = [
    103             'search'   => '',
    104             'level'    => '',
    105             'source'   => '',
    106             'page'     => 1,
    107             'per_page' => 50,
     103            'search'    => '',
     104            'level'     => '',
     105            'source'    => '',
     106            'page'      => 1,
     107            'per_page'  => 50,
     108            'date_from' => '',
     109            'date_to'   => '',
     110            'since'     => '',
    108111        ];
    109112        $args = wp_parse_args( $args, $defaults );
    110113
    111         $search   = $args['search'];
    112         $level    = sanitize_key( $args['level'] );
    113         $source   = sanitize_key( $args['source'] );
    114         $page     = max( 1, (int) $args['page'] );
    115         $per_page = max( 1, (int) $args['per_page'] );
    116         $offset   = ( $page - 1 ) * $per_page;
     114        $search    = $args['search'];
     115        $level     = sanitize_key( $args['level'] );
     116        $source    = sanitize_key( $args['source'] );
     117        $page      = max( 1, (int) $args['page'] );
     118        $per_page  = max( 1, (int) $args['per_page'] );
     119        $offset    = ( $page - 1 ) * $per_page;
     120        $date_from = sanitize_text_field( $args['date_from'] );
     121        $date_to   = sanitize_text_field( $args['date_to'] );
     122        $since     = sanitize_text_field( $args['since'] );
    117123
    118124        // Table name is built from $wpdb->prefix + a hardcoded constant — safe to use with esc_sql.
    … …  
    157163        }
    158164
     165        $valid_date = '/^\d{4}-\d{2}-\d{2}$/';
     166
     167        if ( $date_from !== '' && $date_to !== '' && preg_match( $valid_date, $date_from ) && preg_match( $valid_date, $date_to ) ) {
     168            if ( $date_from > $date_to ) {
     169                [ $date_from, $date_to ] = [ $date_to, $date_from ];
     170            }
     171
     172            $site_tz = wp_timezone();
     173            $utc_tz  = new \DateTimeZone( 'UTC' );
     174
     175            $range_start = \DateTime::createFromFormat( 'Y-m-d H:i:s', $date_from . ' 00:00:00', $site_tz );
     176            $range_end   = \DateTime::createFromFormat( 'Y-m-d H:i:s', $date_to . ' 23:59:59', $site_tz );
     177
     178            if ( $range_start && $range_end ) {
     179                $range_start->setTimezone( $utc_tz );
     180                $range_end->setTimezone( $utc_tz );
     181
     182                $where_extra .= ' AND created_at BETWEEN %s AND %s';
     183                $params[]     = $range_start->format( 'Y-m-d H:i:s' );
     184                $params[]     = $range_end->format( 'Y-m-d H:i:s' );
     185            }
     186        }
     187
     188        if ( $since !== '' && preg_match( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $since ) ) {
     189            $where_extra .= ' AND created_at >= %s';
     190            $params[]     = $since;
     191        }
     192
    159193        $params[] = $per_page;
    160194        $params[] = $offset;
  • likoton-debug-logs/trunk/includes/class-likoton-debug-logs-source-colors.php

    r3650170 r3717902  
    3535    }
    3636
    37     // ---------------------------------------------------------------
    3837    // Helpers
    39     // ---------------------------------------------------------------
    4038
    4139    /** Returns merged array: defaults overridden by saved custom colors */
    … …  
    5957    }
    6058
    61     // ---------------------------------------------------------------
    62     // Inline CSS injection
    63     // ---------------------------------------------------------------
    64 
     59    /** Inline CSS injection */
    6560    public static function inject_inline_styles() {
    6661        $screen = get_current_screen();
  • likoton-debug-logs/trunk/languages/likoton-debug-logs-cs_CZ.po

    r3647996 r3717902  
    4747msgid "All logs"
    4848msgstr "Všechny záznamy"
     49
     50msgid "Date range"
     51msgstr "Rozsah dat"
     52
     53msgid "From date"
     54msgstr "Od data"
     55
     56msgid "To date"
     57msgstr "Do data"
     58
     59msgid "Choose date"
     60msgstr "Vybrat datum"
     61
     62msgid "Today"
     63msgstr "Dnes"
     64
     65msgid "Last hour"
     66msgstr "Poslední hodina"
     67
     68msgid "Last week"
     69msgstr "Poslední týden"
     70
     71msgid "Clear"
     72msgstr "Vymazat"
    4973
    5074msgid "Level"
  • likoton-debug-logs/trunk/languages/likoton-debug-logs-de_DE.po

    r3647996 r3717902  
    4747msgid "All logs"
    4848msgstr "Alle Protokolle"
     49
     50msgid "Date range"
     51msgstr "Datumsbereich"
     52
     53msgid "From date"
     54msgstr "Von Datum"
     55
     56msgid "To date"
     57msgstr "Bis Datum"
     58
     59msgid "Choose date"
     60msgstr "Datum auswählen"
     61
     62msgid "Today"
     63msgstr "Heute"
     64
     65msgid "Last hour"
     66msgstr "Letzte Stunde"
     67
     68msgid "Last week"
     69msgstr "Letzte Woche"
     70
     71msgid "Clear"
     72msgstr "Löschen"
    4973
    5074msgid "Level"
  • likoton-debug-logs/trunk/languages/likoton-debug-logs-en_US.po

    r3647996 r3717902  
    4747msgid "All logs"
    4848msgstr "All logs"
     49
     50msgid "Date range"
     51msgstr "Date range"
     52
     53msgid "From date"
     54msgstr "From date"
     55
     56msgid "To date"
     57msgstr "To date"
     58
     59msgid "Choose date"
     60msgstr "Choose date"
     61
     62msgid "Today"
     63msgstr "Today"
     64
     65msgid "Last hour"
     66msgstr "Last hour"
     67
     68msgid "Last week"
     69msgstr "Last week"
     70
     71msgid "Clear"
     72msgstr "Clear"
    4973
    5074msgid "Level"
  • likoton-debug-logs/trunk/languages/likoton-debug-logs-es_ES.po

    r3647996 r3717902  
    4747msgid "All logs"
    4848msgstr "Todos los registros"
     49
     50msgid "Date range"
     51msgstr "Rango de fechas"
     52
     53msgid "From date"
     54msgstr "Desde fecha"
     55
     56msgid "To date"
     57msgstr "Hasta fecha"
     58
     59msgid "Choose date"
     60msgstr "Elegir fecha"
     61
     62msgid "Today"
     63msgstr "Hoy"
     64
     65msgid "Last hour"
     66msgstr "Última hora"
     67
     68msgid "Last week"
     69msgstr "Última semana"
     70
     71msgid "Clear"
     72msgstr "Borrar"
    4973
    5074msgid "Level"
  • likoton-debug-logs/trunk/languages/likoton-debug-logs-fr_FR.po

    r3647996 r3717902  
    4747msgid "All logs"
    4848msgstr "Tous les journaux"
     49
     50msgid "Date range"
     51msgstr "Plage de dates"
     52
     53msgid "From date"
     54msgstr "Date de début"
     55
     56msgid "To date"
     57msgstr "Date de fin"
     58
     59msgid "Choose date"
     60msgstr "Choisir une date"
     61
     62msgid "Today"
     63msgstr "Aujourd'hui"
     64
     65msgid "Last hour"
     66msgstr "Dernière heure"
     67
     68msgid "Last week"
     69msgstr "Dernière semaine"
     70
     71msgid "Clear"
     72msgstr "Effacer"
    4973
    5074msgid "Level"
  • likoton-debug-logs/trunk/languages/likoton-debug-logs-pl_PL.po

    r3647996 r3717902  
    4747msgid "All logs"
    4848msgstr "Wszystkie logi"
     49
     50msgid "Date range"
     51msgstr "Zakres dat"
     52
     53msgid "From date"
     54msgstr "Od daty"
     55
     56msgid "To date"
     57msgstr "Do daty"
     58
     59msgid "Choose date"
     60msgstr "Wybierz datę"
     61
     62msgid "Today"
     63msgstr "Dzisiaj"
     64
     65msgid "Last hour"
     66msgstr "Ostatnia godzina"
     67
     68msgid "Last week"
     69msgstr "Ostatni tydzień"
     70
     71msgid "Clear"
     72msgstr "Wyczyść"
    4973
    5074msgid "Level"
  • likoton-debug-logs/trunk/languages/likoton-debug-logs-pt_PT.po

    r3647996 r3717902  
    4747msgid "All logs"
    4848msgstr "Todos os registos"
     49
     50msgid "Date range"
     51msgstr "Intervalo de datas"
     52
     53msgid "From date"
     54msgstr "De data"
     55
     56msgid "To date"
     57msgstr "Até data"
     58
     59msgid "Choose date"
     60msgstr "Escolher data"
     61
     62msgid "Today"
     63msgstr "Hoje"
     64
     65msgid "Last hour"
     66msgstr "Última hora"
     67
     68msgid "Last week"
     69msgstr "Última semana"
     70
     71msgid "Clear"
     72msgstr "Limpar"
    4973
    5074msgid "Level"
  • likoton-debug-logs/trunk/languages/likoton-debug-logs-sk_SK.po

    r3647996 r3717902  
    4747msgid "All logs"
    4848msgstr "Všetky záznamy"
     49
     50msgid "Date range"
     51msgstr "Rozsah dátumov"
     52
     53msgid "From date"
     54msgstr "Od dátumu"
     55
     56msgid "To date"
     57msgstr "Do dátumu"
     58
     59msgid "Choose date"
     60msgstr "Vybrať dátum"
     61
     62msgid "Today"
     63msgstr "Dnes"
     64
     65msgid "Last hour"
     66msgstr "Posledná hodina"
     67
     68msgid "Last week"
     69msgstr "Posledný týždeň"
     70
     71msgid "Clear"
     72msgstr "Vymazať"
    4973
    5074msgid "Level"
  • likoton-debug-logs/trunk/languages/likoton-debug-logs.pot

    r3647996 r3717902  
    5555
    5656#: includes/class-likoton-debug-logs-admin.php
     57msgid "Date range"
     58msgstr ""
     59
     60#: includes/class-likoton-debug-logs-admin.php
     61msgid "From date"
     62msgstr ""
     63
     64#: includes/class-likoton-debug-logs-admin.php
     65msgid "To date"
     66msgstr ""
     67
     68#: includes/class-likoton-debug-logs-admin.php
     69msgid "Choose date"
     70msgstr ""
     71
     72#: includes/class-likoton-debug-logs-assets.php
     73msgid "Today"
     74msgstr ""
     75
     76#: includes/class-likoton-debug-logs-admin.php
     77msgid "Last hour"
     78msgstr ""
     79
     80#: includes/class-likoton-debug-logs-admin.php
     81msgid "Last week"
     82msgstr ""
     83
     84#: includes/class-likoton-debug-logs-assets.php
     85msgid "Clear"
     86msgstr ""
     87
     88#: includes/class-likoton-debug-logs-admin.php
    5789msgid "Level"
    5890msgstr ""
  • likoton-debug-logs/trunk/likoton-debug-logs.php

    r3647996 r3717902  
    55 * Description: Collects and displays WordPress and PHP debug logs with filters, live view and dark mode
    66 * Author: LiKoToN
    7  * Version: 1.1.0
     7 * Version: 1.2.0
    88 * License: GPLv3
    99 * License URI: License URI: https://www.gnu.org/licenses/gpl-3.0-standalone.html
  • likoton-debug-logs/trunk/readme.txt

    r3647996 r3717902  
    44Requires at least: 6.0
    55Tested up to: 7.1
    6 Stable tag: 1.1.0
     6Stable tag: 1.2.0
    77License: GPLv3
    88License URI: https://www.gnu.org/licenses/gpl-3.0-standalone.html
    … …  
    2525- Infinite scroll 
    2626- Client-side sorting 
    27 - Search, level, source and "last X logs" filters 
     27- Search, level, source and time filters 
    2828- Color-coded badges 
    2929- Dark mode 
    … …  
    3232
    3333Perfect for developers, administrators and anyone who needs a clear view of what happens inside WordPress.
    34 
    35 == Description (pl_PL) ==
    36 
    37 LiKoToN Debug Logs to lekka i nowoczesna wtyczka debugująca dla WordPressa, która automatycznie zbiera:
    38 
    39 - błędy i ostrzeżenia PHP 
    40 - błędy WordPressa (`wp_error_added`) 
    41 - wywołania REST API (ścieżka, metoda, parametry) 
    42 - logowania użytkowników 
    43 - własne logi przez `Likoton_Debug_Logs_Logger::log()` 
    44 
    45 Wtyczka oferuje czytelny panel logów z:
    46 
    47 - filtrowaniem AJAX 
    48 - nieskończonym przewijaniem (infinite scroll) 
    49 - sortowaniem po kolumnach 
    50 - wyszukiwaniem, filtrem poziomu, źródła i ostatnich X logów 
    51 - kolorowymi znacznikami poziomów i źródeł 
    52 - trybem ciemnym 
    53 - eksportem CSV (czas UTC + lokalny) 
    54 - automatycznym czyszczeniem logów 
    55 
    56 Idealna dla deweloperów i administratorów, którzy chcą mieć pełną kontrolę nad tym, co dzieje się w WordPressie.
    5734
    5835---
    … …  
    7047- AJAX live filtering 
    7148- Infinite scroll 
    72 - Search, level, source, last X logs
     49- Search, level, source, time
    7350- Client-side sorting 
    7451- Color-coded badges 
    … …  
    9875---
    9976
    100 ## Developer API
     77## Installation
    10178
    102 ### Custom logs
    103 ```php
    104 Likoton_Debug_Logs_Logger::log( 'info', 'custom_source', 'Something happened', [ 'extra' => 'data' ] );
     79### From WordPress Dashboard
     801. Upload the plugin ZIP 
     812. Activate **LiKoToN Debug Logs** 
     823. Go to **LiKoToN Debug Logs → Logs**
     83
     84### Manual Installation
     851. Upload folder `likoton-debug-logs` to `/wp-content/plugins/` 
     862. Activate plugin 
     873. Logs appear in the main admin menu 
     88
     89---
    10590
    10691## Supported log levels (PSR-3 + extended PHP levels):
    … …  
    145130Yes, each site has its own logs table.
    146131
     132== Screenshots ==
     133
     1341. Logs tab
     1352. Logs tab in dark mode
     1363. Settings in dark mode
     137
    147138## Changelog ##
     139
     1401.2.0
     141- A date range selection filter has been added
    148142
    1491431.1.0
Note: See TracChangeset for help on using the changeset viewer.