Plugin Directory

Changeset 3260159


Ignore:
Timestamp:
03/22/2025 11:54:13 PM (12 months ago)
Author:
nhrrob
Message:

Update to version 1.1.7-beta1 from GitHub

Location:
nhrrob-options-table-manager
Files:
18 edited
1 copied

Legend:

Unmodified
Added
Removed
  • nhrrob-options-table-manager/tags/1.1.7-beta1/assets/css/admin.css

    r3256055 r3260159  
    378378    background-color: #FF9800;
    379379}
     380
     381#nhrotm-data-table tfoot input {
     382    padding: 8px 10px;
     383    margin: 0;
     384    border: 1px solid #aaa;
     385    border-radius: 5px;
     386    font-weight: normal;
     387}
     388
     389/* Filter  */
     390
     391.nhrotm-filter-container {
     392    margin-bottom: 20px;
     393    padding: 15px;
     394    background: #fff;
     395    border-radius: 5px;
     396    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
     397}
     398
     399.nhrotm-filter-row {
     400    display: flex;
     401    /* justify-content: space-between; */
     402    justify-content: center;
     403    align-items: center;
     404    /* margin-bottom: 15px; */
     405    gap: 10px;
     406}
     407
     408.nhrotm-filter-group {
     409    display: flex;
     410    align-items: center;
     411    gap: 10px;
     412}
     413
     414#option-type-filter {
     415    min-width: 200px;
     416    padding: 4px 8px;
     417    border: 1px solid #ddd;
     418    border-radius: 4px;
     419}
     420
     421#delete-expired-transients {
     422    background-color: #e74c3c;
     423    padding: 4px 8px;
     424    color: white;
     425    border: none;
     426}
  • nhrrob-options-table-manager/tags/1.1.7-beta1/assets/js/admin.js

    r3256055 r3260159  
    3737                "type": "GET",
    3838                "url": nhrotmOptionsTableManager.ajaxUrl + "?action=nhrotm_option_table_data&nonce="+nhrotmOptionsTableManager.nonce,
     39                "data": function(d) {
     40                    // Add column search values to the request
     41                    for (let i = 0; i < d.columns.length; i++) {                       
     42                        d.columns[i].search.value = $('#nhrotm-data-table tfoot input').eq(i).val();
     43                    }
     44
     45                    // Option type filter
     46                    $('#delete-expired-transients').attr('disabled', true);
     47                    d.optionTypeFilter = $('#option-type-filter').val();
     48
     49                    if ( d.optionTypeFilter === 'all-transients' ) {
     50                        $('#delete-expired-transients').attr('disabled', false);
     51
     52                        let currentSearch = d.columns[1].search.value || '';
     53                        if (!currentSearch.includes('transient_')) {
     54                            d.columns[1].search.value = 'transient_' + currentSearch;
     55                        }
     56                    }
     57                }
    3958            },
    4059            "columns": [
     
    4968            // "scrollCollapse": true,
    5069            // "paging": true,
    51             // "order": [[0, 'asc']], // Default order on the first column in ascending
     70            // "order": [[0, 'asc']], // Default order on the first column in ascending,
     71            "initComplete": function () {
     72                this.api()
     73                    .columns()
     74                    .every(function () {
     75                        let column = this;
     76                        let title = column.footer().textContent;
     77         
     78                        // Create input element
     79                        let input = document.createElement('input');
     80                        input.placeholder = title;
     81                        column.footer().replaceChildren(input);
     82         
     83                        input.addEventListener('keyup', function() {
     84                            if (column.search() !== this.value) {
     85                              column.search(this.value).draw();
     86                            }
     87                        });
     88                    });
     89            }
    5290        });
    5391
     
    424462            });
    425463        }
     464
     465        // Filtering
     466        // Add filter dropdown handler
     467        $('#option-type-filter').on('change', function() {
     468            table.ajax.reload();
     469        });
     470
     471        // Add "Delete All Transients" button handler
     472        $('#delete-expired-transients').on('click', function() {
     473            if (confirm('Are you sure you want to delete expired transients? This action cannot be undone.')) {
     474                $.ajax({
     475                    url: nhrotmOptionsTableManager.ajaxUrl,
     476                    type: 'POST',
     477                    data: {
     478                        action: 'nhrotm_delete_expired_transients',
     479                        nonce: nhrotmOptionsTableManager.nonce
     480                    },
     481                    success: function(response) {
     482                        if (response.success) {
     483                            showToast("Expired transients deleted successfully!", "success");
     484                            table.ajax.reload();
     485                        } else {
     486                            showToast('Failed to delete transients: ' + response.data, "error");
     487                        }
     488                    },
     489                    error: function() {
     490                        showToast('An error occurred while deleting transients.', "error");
     491                    }
     492                });
     493            }
     494        });
    426495       
    427496    });
  • nhrrob-options-table-manager/tags/1.1.7-beta1/composer.json

    r3256055 r3260159  
    2626    },
    2727    "scripts": {
    28         "deploy": "composer install --no-dev && wp dist-archive . && composer install"
     28        "deploy": "composer install --no-dev && wp dist-archive . && composer install",
     29        "dev": "composer install",
     30        "build": "composer install --no-dev"
    2931    }
    3032}
  • nhrrob-options-table-manager/tags/1.1.7-beta1/includes/Ajax.php

    r3256055 r3260159  
    1717        add_action('wp_ajax_nhrotm_edit_option', [ $this, 'edit_option' ]);
    1818        add_action('wp_ajax_nhrotm_delete_option', [ $this, 'delete_option' ]);
     19        add_action('wp_ajax_nhrotm_delete_expired_transients', [ $this, 'delete_expired_transients' ]);
    1920        add_action('wp_ajax_nhrotm_option_usage_analytics', [ $this, 'option_usage_analytics' ]);
    2021
     
    4445        // Search parameter
    4546        $search = isset($_GET['search']['value']) ? sanitize_text_field(wp_unslash($_GET['search']['value'])) : '';
     47        $option_type_filter = isset($_GET['optionTypeFilter']) && in_array($_GET['optionTypeFilter'], ['all-options', 'all-transients', 'active-transients', 'expired-transients']) ? sanitize_text_field(wp_unslash($_GET['optionTypeFilter'])) : 'all-options';
    4648       
    4749        // Sorting parameters
    4850        $order_column_index = isset($_GET['order'][0]['column']) ? intval($_GET['order'][0]['column']) : 0;
    4951        $order_direction = isset($_GET['order'][0]['dir']) && in_array($_GET['order'][0]['dir'], ['asc', 'desc']) ? strtolower( sanitize_text_field( wp_unslash( $_GET['order'][0]['dir'] ) ) ) : 'asc';
    50 
     52   
    5153        // Define columns in the correct order for sorting
    5254        $columns = ['option_id', 'option_name', 'option_value', 'autoload'];
     
    6466        );
    6567       
    66         // Prepare queries for different order options
    67         // Using separate complete queries for each column and direction to avoid concatenation
     68        // Get column search values
     69        $column_search = [];
     70        if (isset($_GET['columns']) && is_array( $_GET['columns'] )) {
     71            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
     72            $columns = $this->sanitize_recursive( wp_unslash( $_GET['columns'] ) );
     73
     74            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
     75            foreach ($_GET['columns'] as $column) {
     76                if (isset($column['search']['value'])) {
     77                    $column_search[] = sanitize_text_field(wp_unslash($column['search']['value']));
     78                } else {
     79                    $column_search[] = '';
     80                }
     81            }
     82        }
     83       
     84        // Build WHERE clause for search conditions
     85        $where_clauses = [];
     86       
     87        // Global search
    6888        if (!empty($search)) {
    6989            $search_like = '%' . $wpdb->esc_like($search) . '%';
     90            $where_clauses[] = $wpdb->prepare(
     91                "(option_name LIKE %s OR option_value LIKE %s)",
     92                $search_like,
     93                $search_like
     94            );
     95        }
     96       
     97        // Individual column searches
     98        if (!empty($column_search)) {
     99            // option_id column (index 0)
     100            if (!empty($column_search[0])) {
     101                // For numeric column, use exact match or range
     102                if (is_numeric($column_search[0])) {
     103                    $where_clauses[] = $wpdb->prepare("option_id = %d", intval($column_search[0]));
     104                }
     105            }
    70106           
    71             // Get filtered count
    72             // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    73             $filtered_records = $wpdb->get_var(
    74                 $wpdb->prepare(
    75                     "SELECT COUNT(*) FROM {$wpdb->prefix}options WHERE option_name LIKE %s OR option_value LIKE %s",
    76                     $search_like,
    77                     $search_like
    78                 )
    79             );
    80            
    81             // Get data with search and properly hardcoded ORDER BY
    82             if ($order_column === 'option_id') {
    83                 if ($order_direction === 'desc') {
    84                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    85                     $data = $wpdb->get_results(
    86                         $wpdb->prepare(
    87                             "SELECT * FROM {$wpdb->prefix}options
    88                             WHERE option_name LIKE %s OR option_value LIKE %s
    89                             ORDER BY option_id DESC
    90                             LIMIT %d, %d",
    91                             $search_like, $search_like, $start, $length
    92                         ),
    93                         ARRAY_A
    94                     );
    95                 } else {
    96                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    97                     $data = $wpdb->get_results(
    98                         $wpdb->prepare(
    99                             "SELECT * FROM {$wpdb->prefix}options
    100                             WHERE option_name LIKE %s OR option_value LIKE %s
    101                             ORDER BY option_id ASC
    102                             LIMIT %d, %d",
    103                             $search_like, $search_like, $start, $length
    104                         ),
    105                         ARRAY_A
    106                     );
    107                 }
    108             } elseif ($order_column === 'option_name') {
    109                 if ($order_direction === 'desc') {
    110                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    111                     $data = $wpdb->get_results(
    112                         $wpdb->prepare(
    113                             "SELECT * FROM {$wpdb->prefix}options
    114                             WHERE option_name LIKE %s OR option_value LIKE %s
    115                             ORDER BY option_name DESC
    116                             LIMIT %d, %d",
    117                             $search_like, $search_like, $start, $length
    118                         ),
    119                         ARRAY_A
    120                     );
    121                 } else {
    122                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    123                     $data = $wpdb->get_results(
    124                         $wpdb->prepare(
    125                             "SELECT * FROM {$wpdb->prefix}options
    126                             WHERE option_name LIKE %s OR option_value LIKE %s
    127                             ORDER BY option_name ASC
    128                             LIMIT %d, %d",
    129                             $search_like, $search_like, $start, $length
    130                         ),
    131                         ARRAY_A
    132                     );
    133                 }
    134             } elseif ($order_column === 'option_value') {
    135                 if ($order_direction === 'desc') {
    136                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    137                     $data = $wpdb->get_results(
    138                         $wpdb->prepare(
    139                             "SELECT * FROM {$wpdb->prefix}options
    140                             WHERE option_name LIKE %s OR option_value LIKE %s
    141                             ORDER BY option_value DESC
    142                             LIMIT %d, %d",
    143                             $search_like, $search_like, $start, $length
    144                         ),
    145                         ARRAY_A
    146                     );
    147                 } else {
    148                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    149                     $data = $wpdb->get_results(
    150                         $wpdb->prepare(
    151                             "SELECT * FROM {$wpdb->prefix}options
    152                             WHERE option_name LIKE %s OR option_value LIKE %s
    153                             ORDER BY option_value ASC
    154                             LIMIT %d, %d",
    155                             $search_like, $search_like, $start, $length
    156                         ),
    157                         ARRAY_A
    158                     );
    159                 }
    160             } elseif ($order_column === 'autoload') {
    161                 if ($order_direction === 'desc') {
    162                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    163                     $data = $wpdb->get_results(
    164                         $wpdb->prepare(
    165                             "SELECT * FROM {$wpdb->prefix}options
    166                             WHERE option_name LIKE %s OR option_value LIKE %s
    167                             ORDER BY autoload DESC
    168                             LIMIT %d, %d",
    169                             $search_like, $search_like, $start, $length
    170                         ),
    171                         ARRAY_A
    172                     );
    173                 } else {
    174                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    175                     $data = $wpdb->get_results(
    176                         $wpdb->prepare(
    177                             "SELECT * FROM {$wpdb->prefix}options
    178                             WHERE option_name LIKE %s OR option_value LIKE %s
    179                             ORDER BY autoload ASC
    180                             LIMIT %d, %d",
    181                             $search_like, $search_like, $start, $length
    182                         ),
    183                         ARRAY_A
    184                     );
    185                 }
    186             } else {
    187                 // Default fallback
    188                 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    189                 $data = $wpdb->get_results(
    190                     $wpdb->prepare(
    191                         "SELECT * FROM {$wpdb->prefix}options
    192                         WHERE option_name LIKE %s OR option_value LIKE %s
    193                         ORDER BY option_id ASC
    194                         LIMIT %d, %d",
    195                         $search_like, $search_like, $start, $length
    196                     ),
    197                     ARRAY_A
     107            // option_name column (index 1)
     108            if (!empty($column_search[1])) {
     109                $where_clauses[] = $wpdb->prepare(
     110                    "option_name LIKE %s",
     111                    '%' . $wpdb->esc_like($column_search[1]) . '%'
    198112                );
    199113            }
    200         } else {
    201             // No search applied, use total as filtered count
    202             $filtered_records = $total_records;
    203114           
    204             // Get data without search and properly hardcoded ORDER BY
    205             if ($order_column === 'option_id') {
    206                 if ($order_direction === 'desc') {
    207                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    208                     $data = $wpdb->get_results(
    209                         $wpdb->prepare(
    210                             "SELECT * FROM {$wpdb->prefix}options
    211                             ORDER BY option_id DESC
    212                             LIMIT %d, %d",
    213                             $start, $length
    214                         ),
    215                         ARRAY_A
    216                     );
    217                 } else {
    218                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    219                     $data = $wpdb->get_results(
    220                         $wpdb->prepare(
    221                             "SELECT * FROM {$wpdb->prefix}options
    222                             ORDER BY option_id ASC
    223                             LIMIT %d, %d",
    224                             $start, $length
    225                         ),
    226                         ARRAY_A
    227                     );
    228                 }
    229             } elseif ($order_column === 'option_name') {
    230                 if ($order_direction === 'desc') {
    231                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    232                     $data = $wpdb->get_results(
    233                         $wpdb->prepare(
    234                             "SELECT * FROM {$wpdb->prefix}options
    235                             ORDER BY option_name DESC
    236                             LIMIT %d, %d",
    237                             $start, $length
    238                         ),
    239                         ARRAY_A
    240                     );
    241                 } else {
    242                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    243                     $data = $wpdb->get_results(
    244                         $wpdb->prepare(
    245                             "SELECT * FROM {$wpdb->prefix}options
    246                             ORDER BY option_name ASC
    247                             LIMIT %d, %d",
    248                             $start, $length
    249                         ),
    250                         ARRAY_A
    251                     );
    252                 }
    253             } elseif ($order_column === 'option_value') {
    254                 if ($order_direction === 'desc') {
    255                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    256                     $data = $wpdb->get_results(
    257                         $wpdb->prepare(
    258                             "SELECT * FROM {$wpdb->prefix}options
    259                             ORDER BY option_value DESC
    260                             LIMIT %d, %d",
    261                             $start, $length
    262                         ),
    263                         ARRAY_A
    264                     );
    265                 } else {
    266                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    267                     $data = $wpdb->get_results(
    268                         $wpdb->prepare(
    269                             "SELECT * FROM {$wpdb->prefix}options
    270                             ORDER BY option_value ASC
    271                             LIMIT %d, %d",
    272                             $start, $length
    273                         ),
    274                         ARRAY_A
    275                     );
    276                 }
    277             } elseif ($order_column === 'autoload') {
    278                 if ($order_direction === 'desc') {
    279                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    280                     $data = $wpdb->get_results(
    281                         $wpdb->prepare(
    282                             "SELECT * FROM {$wpdb->prefix}options
    283                             ORDER BY autoload DESC
    284                             LIMIT %d, %d",
    285                             $start, $length
    286                         ),
    287                         ARRAY_A
    288                     );
    289                 } else {
    290                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    291                     $data = $wpdb->get_results(
    292                         $wpdb->prepare(
    293                             "SELECT * FROM {$wpdb->prefix}options
    294                             ORDER BY autoload ASC
    295                             LIMIT %d, %d",
    296                             $start, $length
    297                         ),
    298                         ARRAY_A
    299                     );
    300                 }
    301             } else {
    302                 // Default fallback
    303                 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    304                 $data = $wpdb->get_results(
    305                     $wpdb->prepare(
    306                         "SELECT * FROM {$wpdb->prefix}options
    307                         ORDER BY option_id ASC
    308                         LIMIT %d, %d",
    309                         $start, $length
    310                     ),
    311                     ARRAY_A
     115            // option_value column (index 2)
     116            if (!empty($column_search[2])) {
     117                $where_clauses[] = $wpdb->prepare(
     118                    "option_value LIKE %s",
     119                    '%' . $wpdb->esc_like($column_search[2]) . '%'
    312120                );
    313121            }
    314         }
     122           
     123            // autoload column (index 3)
     124            if (!empty($column_search[3])) {
     125                $where_clauses[] = $wpdb->prepare(
     126                    "autoload LIKE %s",
     127                    '%' . $wpdb->esc_like($column_search[3]) . '%'
     128                );
     129            }
     130        }
     131       
     132        // Combine WHERE clauses
     133        $where_sql = '';
     134        if (!empty($where_clauses)) {
     135            $where_sql = 'WHERE ' . implode(' AND ', $where_clauses);
     136        }
     137       
     138        // Count filtered records
     139        $filtered_records_sql = "SELECT COUNT(*) FROM {$wpdb->prefix}options {$where_sql}";
     140        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
     141        $filtered_records = $wpdb->get_var($filtered_records_sql);
     142       
     143        // SQL for ordering
     144        $order_sql = "ORDER BY {$order_column} {$order_direction}";
     145       
     146        // Get data with search, order, and pagination
     147        $data_sql = "SELECT * FROM {$wpdb->prefix}options {$where_sql} {$order_sql} LIMIT %d, %d";
     148        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     149        $data = $wpdb->get_results(
     150            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     151            $wpdb->prepare($data_sql, $start, $length),
     152            ARRAY_A
     153        );
    315154       
    316155        // Wrap the option_value in the scrollable-cell div
     
    318157            $is_protected = in_array($row['option_name'], $this->get_protected_options());
    319158            $protected_attr = $is_protected ? sprintf('title="%s" disabled', esc_attr__('Protected', 'nhrrob-options-table-manager')) : '';
     159       
     160            if ( 'all-transients' === $option_type_filter ) {
     161                // all options are transients
     162                $transient_name = str_replace('_transient_', '', $row['option_name']);
     163                $transient_value = get_transient($transient_name);
     164
     165                $transient_status = $transient_value ? '[active]' : '[expired]';
     166                $row['option_name'] = esc_html($transient_status . $row['option_name']);
     167            }
    320168
    321169            $row['option_value'] = '<div class="scrollable-cell">' . esc_html($row['option_value']) . '</div>';
     
    329177                $protected_attr,
    330178            );
    331         }
     179        }       
    332180       
    333181        // Prepare response for DataTables
     
    628476   
    629477        wp_die();
     478    }
     479
     480    /**
     481     * Delete expired transients from the options table
     482     */
     483    public function delete_expired_transients() {
     484        // Verify nonce
     485        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'nhrotm-admin-nonce')) {
     486            wp_send_json_error('Invalid nonce');
     487            wp_die();
     488        }
     489       
     490        global $wpdb;
     491       
     492        // Get all transient options
     493        // phpcs:ignore:WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     494        $transients = $wpdb->get_results(
     495            "SELECT option_name, option_value
     496            FROM {$wpdb->options}
     497            WHERE option_name LIKE '%_transient_%'
     498            AND option_name NOT LIKE '%_transient_timeout_%'",
     499            ARRAY_A
     500        );
     501       
     502        try {
     503            $deleted_transients = [];
     504
     505            foreach ($transients as $transient) {
     506
     507                $transient_name = str_replace('_transient_', '', $transient['option_name']);
     508
     509                if (false === get_transient($transient_name)) {
     510                    // Transient has expired, delete it
     511                    $deleted_transients[] = $transient_name;
     512                    delete_transient(esc_sql( $transient_name ) );
     513                }
     514            }
     515
     516            wp_send_json_success([
     517                'message' => 'Expired transients deleted successfully',
     518                'count' => count($deleted_transients),
     519                'deleted_transients' => $deleted_transients,
     520            ]);
     521        } catch (\Exception $e) {
     522            wp_send_json_error('Database error');
     523            wp_die();
     524        }
    630525    }
    631526
  • nhrrob-options-table-manager/tags/1.1.7-beta1/includes/Traits/GlobalTrait.php

    r3256055 r3260159  
    282282
    283283    // Helper function to recursively sanitize arrays and objects
    284     public function sanitize_recursive(&$data) {
    285         // Handle arrays using array_walk_recursive for deep sanitization
     284    public function sanitize_recursive($data) {
    286285        if (is_array($data)) {
    287             array_walk_recursive($data, function (&$item, $key) {
    288                 if ($key !== 'content') {
    289                     $item = sanitize_text_field($item); // Only sanitize if the key is not 'content'
    290                 }
    291             });
    292         }
    293         // Handle objects by converting them to arrays and applying the function recursively
    294         elseif (is_object($data)) {
    295             foreach ($data as $key => &$value) {
    296                 if ($key === 'content') {
    297                     continue; // Skip sanitization for 'content' fields
    298                 }
    299                
    300                 if (is_scalar($value)) {
    301                     $value = sanitize_text_field($value);
    302                 } else {
    303                     $this->sanitize_recursive($value); // Recurse for arrays or nested objects
     286            $sanitized_array = [];
     287            foreach ($data as $key => $value) {
     288                $sanitized_key = sanitize_key($key);
     289                if ($sanitized_key !== '') {
     290                    $sanitized_array[$sanitized_key] = $this->sanitize_recursive($value);
    304291                }
    305292            }
     293            return $sanitized_array;
     294        } elseif (is_object($data)) {
     295            $sanitized_object = new \stdClass();
     296            $object_vars = get_object_vars($data);
     297           
     298            foreach ($object_vars as $key => $value) {
     299                $sanitized_key = $this->sanitize_key($key);
     300                if ($sanitized_key !== '') {
     301                    $sanitized_object->$sanitized_key = $this->sanitize_recursive($value);
     302                }
     303            }
     304            return $sanitized_object;
     305        } else {
     306            return $this->sanitize_item($data);
    306307        }
    307308    }
    308309
    309     public function castValues(&$array, $option_name = '') {
    310         // $exceptional_option_names = $this->exceptional_option_names();
    311 
    312         foreach ($array as $key => &$value) {
    313             if (is_array($value)) {
    314                 $this->castValues($value); // Recursive call for nested arrays
    315             } elseif (is_numeric($value)) {
    316                 $value = (int) $value; // Convert numeric strings to integers
    317             } elseif ($value === "true") {
    318                 $value = true; // Convert "true" string to boolean true
    319             } elseif ($value === "false") {
    320                 $value = false; // Convert "false" string to boolean false
    321             } elseif (empty($value) && $key === "recurrence") {
    322                 $value = false; // Ensure recurrence defaults to false if empty
    323             }
    324 
    325             // if ( ! empty( $option_name ) && in_array( $option_name, $exceptional_option_names ) ) {
    326             //     if ( empty($value) && $key === "recurrence" ) {
    327             //         $value = false;
    328             //     }
    329             // }
     310    public function sanitize_item( $item ){
     311        $item_formatted = '';
     312
     313        if ( is_numeric( $item )) {
     314            $item_formatted = intval( $item );
     315        } elseif ( is_email( $item )) {
     316            $item_formatted = sanitize_email( $item );
     317        } else {
     318            $item_formatted = sanitize_text_field( wp_unslash( $item ) );
    330319        }
     320
     321        return $item_formatted;
    331322    }
    332323
  • nhrrob-options-table-manager/tags/1.1.7-beta1/includes/views/admin/settings/index.php

    r3256055 r3260159  
    1919        </div>
    2020
     21        <!-- Filter starts -->
     22        <div class="nhrotm-filter-container">
     23            <div class="nhrotm-filter-row">
     24                <div class="nhrotm-filter-group">
     25                    <select id="option-type-filter">
     26                        <option value="all-options"><?php esc_html_e('All Options', 'nhrrob-options-table-manager'); ?></option>
     27                        <option value="all-transients"><?php esc_html_e('All Transients', 'nhrrob-options-table-manager'); ?></option>
     28                    </select>
     29                </div>
     30                <div class="nhrotm-filter-group">
     31                    <button id="delete-expired-transients" class="button button-danger" disabled><?php esc_html_e('Delete Expired Transients', 'nhrrob-options-table-manager'); ?></button>
     32                </div>
     33            </div>
     34        </div>
     35        <!-- Filter ends  -->
     36         
    2137        <table id="nhrotm-data-table" class="nhrotm-data-table wp-list-table widefat fixed striped">
    2238            <thead>
     
    3046            </thead>
    3147
     48            <tfoot>
     49                <tr>
     50                    <th><?php esc_html_e('Option ID', 'nhrrob-options-table-manager'); ?></th>
     51                    <th><?php esc_html_e('Option Name', 'nhrrob-options-table-manager'); ?></th>
     52                    <th><?php esc_html_e('Option Value', 'nhrrob-options-table-manager'); ?></th>
     53                    <th><?php esc_html_e('Autoload', 'nhrrob-options-table-manager'); ?></th>
     54                    <th><?php esc_html_e('Action', 'nhrrob-options-table-manager'); ?></th>
     55                </tr>
     56            </tfoot>
     57           
    3258            <tbody>
    3359
  • nhrrob-options-table-manager/tags/1.1.7-beta1/nhrrob-options-table-manager.php

    r3256164 r3260159  
    66 * Author: Nazmul Hasan Robin
    77 * Author URI: https://profiles.wordpress.org/nhrrob/
    8  * Version: 1.1.6
     8 * Version: 1.1.7-beta1
    99 * Requires at least: 6.0
    1010 * Requires PHP: 7.4
     
    2828     * @var string
    2929     */
    30     const nhrotm_version = '1.1.6';
     30    const nhrotm_version = '1.1.7-beta1';
    3131
    3232    /**
  • nhrrob-options-table-manager/tags/1.1.7-beta1/readme.txt

    r3256164 r3260159  
    55Tested up to: 6.7 
    66Requires PHP: 7.4 
    7 Stable tag: 1.1.6 
     7Stable tag: 1.1.6
    88License: GPLv2 or later 
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html 
     
    8585
    8686== Changelog ==
     87
     88= 1.1.7 - 23/03/2025 =
     89- Added: Column search feature
     90- Added: Filter by option type - option or transient
     91- Added: Delete all expired transients button and functionality
     92- Few minor bug fixing & improvements
    8793
    8894= 1.1.6 - 15/03/2025 =
  • nhrrob-options-table-manager/tags/1.1.7-beta1/vendor/composer/installed.php

    r3256164 r3260159  
    11<?php return array(
    22    'root' => array(
    3         'pretty_version' => 'dev-trunk',
    4         'version' => 'dev-trunk',
     3        'pretty_version' => 'dev-master',
     4        'version' => 'dev-master',
    55        'type' => 'wordpress-plugin',
    66        'install_path' => __DIR__ . '/../../',
    77        'aliases' => array(),
    8         'reference' => 'd2fb624425f2ff9b9547ceac02969a74b7af62a7',
     8        'reference' => '1313080a34a75fa363e2c99b07d90d28b2ca3308',
    99        'name' => 'nhrotm/options-table-manager',
    1010        'dev' => false,
     
    1212    'versions' => array(
    1313        'nhrotm/options-table-manager' => array(
    14             'pretty_version' => 'dev-trunk',
    15             'version' => 'dev-trunk',
     14            'pretty_version' => 'dev-master',
     15            'version' => 'dev-master',
    1616            'type' => 'wordpress-plugin',
    1717            'install_path' => __DIR__ . '/../../',
    1818            'aliases' => array(),
    19             'reference' => 'd2fb624425f2ff9b9547ceac02969a74b7af62a7',
     19            'reference' => '1313080a34a75fa363e2c99b07d90d28b2ca3308',
    2020            'dev_requirement' => false,
    2121        ),
  • nhrrob-options-table-manager/trunk/assets/css/admin.css

    r3256055 r3260159  
    378378    background-color: #FF9800;
    379379}
     380
     381#nhrotm-data-table tfoot input {
     382    padding: 8px 10px;
     383    margin: 0;
     384    border: 1px solid #aaa;
     385    border-radius: 5px;
     386    font-weight: normal;
     387}
     388
     389/* Filter  */
     390
     391.nhrotm-filter-container {
     392    margin-bottom: 20px;
     393    padding: 15px;
     394    background: #fff;
     395    border-radius: 5px;
     396    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
     397}
     398
     399.nhrotm-filter-row {
     400    display: flex;
     401    /* justify-content: space-between; */
     402    justify-content: center;
     403    align-items: center;
     404    /* margin-bottom: 15px; */
     405    gap: 10px;
     406}
     407
     408.nhrotm-filter-group {
     409    display: flex;
     410    align-items: center;
     411    gap: 10px;
     412}
     413
     414#option-type-filter {
     415    min-width: 200px;
     416    padding: 4px 8px;
     417    border: 1px solid #ddd;
     418    border-radius: 4px;
     419}
     420
     421#delete-expired-transients {
     422    background-color: #e74c3c;
     423    padding: 4px 8px;
     424    color: white;
     425    border: none;
     426}
  • nhrrob-options-table-manager/trunk/assets/js/admin.js

    r3256055 r3260159  
    3737                "type": "GET",
    3838                "url": nhrotmOptionsTableManager.ajaxUrl + "?action=nhrotm_option_table_data&nonce="+nhrotmOptionsTableManager.nonce,
     39                "data": function(d) {
     40                    // Add column search values to the request
     41                    for (let i = 0; i < d.columns.length; i++) {                       
     42                        d.columns[i].search.value = $('#nhrotm-data-table tfoot input').eq(i).val();
     43                    }
     44
     45                    // Option type filter
     46                    $('#delete-expired-transients').attr('disabled', true);
     47                    d.optionTypeFilter = $('#option-type-filter').val();
     48
     49                    if ( d.optionTypeFilter === 'all-transients' ) {
     50                        $('#delete-expired-transients').attr('disabled', false);
     51
     52                        let currentSearch = d.columns[1].search.value || '';
     53                        if (!currentSearch.includes('transient_')) {
     54                            d.columns[1].search.value = 'transient_' + currentSearch;
     55                        }
     56                    }
     57                }
    3958            },
    4059            "columns": [
     
    4968            // "scrollCollapse": true,
    5069            // "paging": true,
    51             // "order": [[0, 'asc']], // Default order on the first column in ascending
     70            // "order": [[0, 'asc']], // Default order on the first column in ascending,
     71            "initComplete": function () {
     72                this.api()
     73                    .columns()
     74                    .every(function () {
     75                        let column = this;
     76                        let title = column.footer().textContent;
     77         
     78                        // Create input element
     79                        let input = document.createElement('input');
     80                        input.placeholder = title;
     81                        column.footer().replaceChildren(input);
     82         
     83                        input.addEventListener('keyup', function() {
     84                            if (column.search() !== this.value) {
     85                              column.search(this.value).draw();
     86                            }
     87                        });
     88                    });
     89            }
    5290        });
    5391
     
    424462            });
    425463        }
     464
     465        // Filtering
     466        // Add filter dropdown handler
     467        $('#option-type-filter').on('change', function() {
     468            table.ajax.reload();
     469        });
     470
     471        // Add "Delete All Transients" button handler
     472        $('#delete-expired-transients').on('click', function() {
     473            if (confirm('Are you sure you want to delete expired transients? This action cannot be undone.')) {
     474                $.ajax({
     475                    url: nhrotmOptionsTableManager.ajaxUrl,
     476                    type: 'POST',
     477                    data: {
     478                        action: 'nhrotm_delete_expired_transients',
     479                        nonce: nhrotmOptionsTableManager.nonce
     480                    },
     481                    success: function(response) {
     482                        if (response.success) {
     483                            showToast("Expired transients deleted successfully!", "success");
     484                            table.ajax.reload();
     485                        } else {
     486                            showToast('Failed to delete transients: ' + response.data, "error");
     487                        }
     488                    },
     489                    error: function() {
     490                        showToast('An error occurred while deleting transients.', "error");
     491                    }
     492                });
     493            }
     494        });
    426495       
    427496    });
  • nhrrob-options-table-manager/trunk/composer.json

    r3256055 r3260159  
    2626    },
    2727    "scripts": {
    28         "deploy": "composer install --no-dev && wp dist-archive . && composer install"
     28        "deploy": "composer install --no-dev && wp dist-archive . && composer install",
     29        "dev": "composer install",
     30        "build": "composer install --no-dev"
    2931    }
    3032}
  • nhrrob-options-table-manager/trunk/includes/Ajax.php

    r3256055 r3260159  
    1717        add_action('wp_ajax_nhrotm_edit_option', [ $this, 'edit_option' ]);
    1818        add_action('wp_ajax_nhrotm_delete_option', [ $this, 'delete_option' ]);
     19        add_action('wp_ajax_nhrotm_delete_expired_transients', [ $this, 'delete_expired_transients' ]);
    1920        add_action('wp_ajax_nhrotm_option_usage_analytics', [ $this, 'option_usage_analytics' ]);
    2021
     
    4445        // Search parameter
    4546        $search = isset($_GET['search']['value']) ? sanitize_text_field(wp_unslash($_GET['search']['value'])) : '';
     47        $option_type_filter = isset($_GET['optionTypeFilter']) && in_array($_GET['optionTypeFilter'], ['all-options', 'all-transients', 'active-transients', 'expired-transients']) ? sanitize_text_field(wp_unslash($_GET['optionTypeFilter'])) : 'all-options';
    4648       
    4749        // Sorting parameters
    4850        $order_column_index = isset($_GET['order'][0]['column']) ? intval($_GET['order'][0]['column']) : 0;
    4951        $order_direction = isset($_GET['order'][0]['dir']) && in_array($_GET['order'][0]['dir'], ['asc', 'desc']) ? strtolower( sanitize_text_field( wp_unslash( $_GET['order'][0]['dir'] ) ) ) : 'asc';
    50 
     52   
    5153        // Define columns in the correct order for sorting
    5254        $columns = ['option_id', 'option_name', 'option_value', 'autoload'];
     
    6466        );
    6567       
    66         // Prepare queries for different order options
    67         // Using separate complete queries for each column and direction to avoid concatenation
     68        // Get column search values
     69        $column_search = [];
     70        if (isset($_GET['columns']) && is_array( $_GET['columns'] )) {
     71            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
     72            $columns = $this->sanitize_recursive( wp_unslash( $_GET['columns'] ) );
     73
     74            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
     75            foreach ($_GET['columns'] as $column) {
     76                if (isset($column['search']['value'])) {
     77                    $column_search[] = sanitize_text_field(wp_unslash($column['search']['value']));
     78                } else {
     79                    $column_search[] = '';
     80                }
     81            }
     82        }
     83       
     84        // Build WHERE clause for search conditions
     85        $where_clauses = [];
     86       
     87        // Global search
    6888        if (!empty($search)) {
    6989            $search_like = '%' . $wpdb->esc_like($search) . '%';
     90            $where_clauses[] = $wpdb->prepare(
     91                "(option_name LIKE %s OR option_value LIKE %s)",
     92                $search_like,
     93                $search_like
     94            );
     95        }
     96       
     97        // Individual column searches
     98        if (!empty($column_search)) {
     99            // option_id column (index 0)
     100            if (!empty($column_search[0])) {
     101                // For numeric column, use exact match or range
     102                if (is_numeric($column_search[0])) {
     103                    $where_clauses[] = $wpdb->prepare("option_id = %d", intval($column_search[0]));
     104                }
     105            }
    70106           
    71             // Get filtered count
    72             // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    73             $filtered_records = $wpdb->get_var(
    74                 $wpdb->prepare(
    75                     "SELECT COUNT(*) FROM {$wpdb->prefix}options WHERE option_name LIKE %s OR option_value LIKE %s",
    76                     $search_like,
    77                     $search_like
    78                 )
    79             );
    80            
    81             // Get data with search and properly hardcoded ORDER BY
    82             if ($order_column === 'option_id') {
    83                 if ($order_direction === 'desc') {
    84                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    85                     $data = $wpdb->get_results(
    86                         $wpdb->prepare(
    87                             "SELECT * FROM {$wpdb->prefix}options
    88                             WHERE option_name LIKE %s OR option_value LIKE %s
    89                             ORDER BY option_id DESC
    90                             LIMIT %d, %d",
    91                             $search_like, $search_like, $start, $length
    92                         ),
    93                         ARRAY_A
    94                     );
    95                 } else {
    96                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    97                     $data = $wpdb->get_results(
    98                         $wpdb->prepare(
    99                             "SELECT * FROM {$wpdb->prefix}options
    100                             WHERE option_name LIKE %s OR option_value LIKE %s
    101                             ORDER BY option_id ASC
    102                             LIMIT %d, %d",
    103                             $search_like, $search_like, $start, $length
    104                         ),
    105                         ARRAY_A
    106                     );
    107                 }
    108             } elseif ($order_column === 'option_name') {
    109                 if ($order_direction === 'desc') {
    110                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    111                     $data = $wpdb->get_results(
    112                         $wpdb->prepare(
    113                             "SELECT * FROM {$wpdb->prefix}options
    114                             WHERE option_name LIKE %s OR option_value LIKE %s
    115                             ORDER BY option_name DESC
    116                             LIMIT %d, %d",
    117                             $search_like, $search_like, $start, $length
    118                         ),
    119                         ARRAY_A
    120                     );
    121                 } else {
    122                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    123                     $data = $wpdb->get_results(
    124                         $wpdb->prepare(
    125                             "SELECT * FROM {$wpdb->prefix}options
    126                             WHERE option_name LIKE %s OR option_value LIKE %s
    127                             ORDER BY option_name ASC
    128                             LIMIT %d, %d",
    129                             $search_like, $search_like, $start, $length
    130                         ),
    131                         ARRAY_A
    132                     );
    133                 }
    134             } elseif ($order_column === 'option_value') {
    135                 if ($order_direction === 'desc') {
    136                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    137                     $data = $wpdb->get_results(
    138                         $wpdb->prepare(
    139                             "SELECT * FROM {$wpdb->prefix}options
    140                             WHERE option_name LIKE %s OR option_value LIKE %s
    141                             ORDER BY option_value DESC
    142                             LIMIT %d, %d",
    143                             $search_like, $search_like, $start, $length
    144                         ),
    145                         ARRAY_A
    146                     );
    147                 } else {
    148                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    149                     $data = $wpdb->get_results(
    150                         $wpdb->prepare(
    151                             "SELECT * FROM {$wpdb->prefix}options
    152                             WHERE option_name LIKE %s OR option_value LIKE %s
    153                             ORDER BY option_value ASC
    154                             LIMIT %d, %d",
    155                             $search_like, $search_like, $start, $length
    156                         ),
    157                         ARRAY_A
    158                     );
    159                 }
    160             } elseif ($order_column === 'autoload') {
    161                 if ($order_direction === 'desc') {
    162                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    163                     $data = $wpdb->get_results(
    164                         $wpdb->prepare(
    165                             "SELECT * FROM {$wpdb->prefix}options
    166                             WHERE option_name LIKE %s OR option_value LIKE %s
    167                             ORDER BY autoload DESC
    168                             LIMIT %d, %d",
    169                             $search_like, $search_like, $start, $length
    170                         ),
    171                         ARRAY_A
    172                     );
    173                 } else {
    174                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    175                     $data = $wpdb->get_results(
    176                         $wpdb->prepare(
    177                             "SELECT * FROM {$wpdb->prefix}options
    178                             WHERE option_name LIKE %s OR option_value LIKE %s
    179                             ORDER BY autoload ASC
    180                             LIMIT %d, %d",
    181                             $search_like, $search_like, $start, $length
    182                         ),
    183                         ARRAY_A
    184                     );
    185                 }
    186             } else {
    187                 // Default fallback
    188                 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    189                 $data = $wpdb->get_results(
    190                     $wpdb->prepare(
    191                         "SELECT * FROM {$wpdb->prefix}options
    192                         WHERE option_name LIKE %s OR option_value LIKE %s
    193                         ORDER BY option_id ASC
    194                         LIMIT %d, %d",
    195                         $search_like, $search_like, $start, $length
    196                     ),
    197                     ARRAY_A
     107            // option_name column (index 1)
     108            if (!empty($column_search[1])) {
     109                $where_clauses[] = $wpdb->prepare(
     110                    "option_name LIKE %s",
     111                    '%' . $wpdb->esc_like($column_search[1]) . '%'
    198112                );
    199113            }
    200         } else {
    201             // No search applied, use total as filtered count
    202             $filtered_records = $total_records;
    203114           
    204             // Get data without search and properly hardcoded ORDER BY
    205             if ($order_column === 'option_id') {
    206                 if ($order_direction === 'desc') {
    207                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    208                     $data = $wpdb->get_results(
    209                         $wpdb->prepare(
    210                             "SELECT * FROM {$wpdb->prefix}options
    211                             ORDER BY option_id DESC
    212                             LIMIT %d, %d",
    213                             $start, $length
    214                         ),
    215                         ARRAY_A
    216                     );
    217                 } else {
    218                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    219                     $data = $wpdb->get_results(
    220                         $wpdb->prepare(
    221                             "SELECT * FROM {$wpdb->prefix}options
    222                             ORDER BY option_id ASC
    223                             LIMIT %d, %d",
    224                             $start, $length
    225                         ),
    226                         ARRAY_A
    227                     );
    228                 }
    229             } elseif ($order_column === 'option_name') {
    230                 if ($order_direction === 'desc') {
    231                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    232                     $data = $wpdb->get_results(
    233                         $wpdb->prepare(
    234                             "SELECT * FROM {$wpdb->prefix}options
    235                             ORDER BY option_name DESC
    236                             LIMIT %d, %d",
    237                             $start, $length
    238                         ),
    239                         ARRAY_A
    240                     );
    241                 } else {
    242                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    243                     $data = $wpdb->get_results(
    244                         $wpdb->prepare(
    245                             "SELECT * FROM {$wpdb->prefix}options
    246                             ORDER BY option_name ASC
    247                             LIMIT %d, %d",
    248                             $start, $length
    249                         ),
    250                         ARRAY_A
    251                     );
    252                 }
    253             } elseif ($order_column === 'option_value') {
    254                 if ($order_direction === 'desc') {
    255                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    256                     $data = $wpdb->get_results(
    257                         $wpdb->prepare(
    258                             "SELECT * FROM {$wpdb->prefix}options
    259                             ORDER BY option_value DESC
    260                             LIMIT %d, %d",
    261                             $start, $length
    262                         ),
    263                         ARRAY_A
    264                     );
    265                 } else {
    266                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    267                     $data = $wpdb->get_results(
    268                         $wpdb->prepare(
    269                             "SELECT * FROM {$wpdb->prefix}options
    270                             ORDER BY option_value ASC
    271                             LIMIT %d, %d",
    272                             $start, $length
    273                         ),
    274                         ARRAY_A
    275                     );
    276                 }
    277             } elseif ($order_column === 'autoload') {
    278                 if ($order_direction === 'desc') {
    279                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    280                     $data = $wpdb->get_results(
    281                         $wpdb->prepare(
    282                             "SELECT * FROM {$wpdb->prefix}options
    283                             ORDER BY autoload DESC
    284                             LIMIT %d, %d",
    285                             $start, $length
    286                         ),
    287                         ARRAY_A
    288                     );
    289                 } else {
    290                     // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    291                     $data = $wpdb->get_results(
    292                         $wpdb->prepare(
    293                             "SELECT * FROM {$wpdb->prefix}options
    294                             ORDER BY autoload ASC
    295                             LIMIT %d, %d",
    296                             $start, $length
    297                         ),
    298                         ARRAY_A
    299                     );
    300                 }
    301             } else {
    302                 // Default fallback
    303                 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    304                 $data = $wpdb->get_results(
    305                     $wpdb->prepare(
    306                         "SELECT * FROM {$wpdb->prefix}options
    307                         ORDER BY option_id ASC
    308                         LIMIT %d, %d",
    309                         $start, $length
    310                     ),
    311                     ARRAY_A
     115            // option_value column (index 2)
     116            if (!empty($column_search[2])) {
     117                $where_clauses[] = $wpdb->prepare(
     118                    "option_value LIKE %s",
     119                    '%' . $wpdb->esc_like($column_search[2]) . '%'
    312120                );
    313121            }
    314         }
     122           
     123            // autoload column (index 3)
     124            if (!empty($column_search[3])) {
     125                $where_clauses[] = $wpdb->prepare(
     126                    "autoload LIKE %s",
     127                    '%' . $wpdb->esc_like($column_search[3]) . '%'
     128                );
     129            }
     130        }
     131       
     132        // Combine WHERE clauses
     133        $where_sql = '';
     134        if (!empty($where_clauses)) {
     135            $where_sql = 'WHERE ' . implode(' AND ', $where_clauses);
     136        }
     137       
     138        // Count filtered records
     139        $filtered_records_sql = "SELECT COUNT(*) FROM {$wpdb->prefix}options {$where_sql}";
     140        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
     141        $filtered_records = $wpdb->get_var($filtered_records_sql);
     142       
     143        // SQL for ordering
     144        $order_sql = "ORDER BY {$order_column} {$order_direction}";
     145       
     146        // Get data with search, order, and pagination
     147        $data_sql = "SELECT * FROM {$wpdb->prefix}options {$where_sql} {$order_sql} LIMIT %d, %d";
     148        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     149        $data = $wpdb->get_results(
     150            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     151            $wpdb->prepare($data_sql, $start, $length),
     152            ARRAY_A
     153        );
    315154       
    316155        // Wrap the option_value in the scrollable-cell div
     
    318157            $is_protected = in_array($row['option_name'], $this->get_protected_options());
    319158            $protected_attr = $is_protected ? sprintf('title="%s" disabled', esc_attr__('Protected', 'nhrrob-options-table-manager')) : '';
     159       
     160            if ( 'all-transients' === $option_type_filter ) {
     161                // all options are transients
     162                $transient_name = str_replace('_transient_', '', $row['option_name']);
     163                $transient_value = get_transient($transient_name);
     164
     165                $transient_status = $transient_value ? '[active]' : '[expired]';
     166                $row['option_name'] = esc_html($transient_status . $row['option_name']);
     167            }
    320168
    321169            $row['option_value'] = '<div class="scrollable-cell">' . esc_html($row['option_value']) . '</div>';
     
    329177                $protected_attr,
    330178            );
    331         }
     179        }       
    332180       
    333181        // Prepare response for DataTables
     
    628476   
    629477        wp_die();
     478    }
     479
     480    /**
     481     * Delete expired transients from the options table
     482     */
     483    public function delete_expired_transients() {
     484        // Verify nonce
     485        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'nhrotm-admin-nonce')) {
     486            wp_send_json_error('Invalid nonce');
     487            wp_die();
     488        }
     489       
     490        global $wpdb;
     491       
     492        // Get all transient options
     493        // phpcs:ignore:WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     494        $transients = $wpdb->get_results(
     495            "SELECT option_name, option_value
     496            FROM {$wpdb->options}
     497            WHERE option_name LIKE '%_transient_%'
     498            AND option_name NOT LIKE '%_transient_timeout_%'",
     499            ARRAY_A
     500        );
     501       
     502        try {
     503            $deleted_transients = [];
     504
     505            foreach ($transients as $transient) {
     506
     507                $transient_name = str_replace('_transient_', '', $transient['option_name']);
     508
     509                if (false === get_transient($transient_name)) {
     510                    // Transient has expired, delete it
     511                    $deleted_transients[] = $transient_name;
     512                    delete_transient(esc_sql( $transient_name ) );
     513                }
     514            }
     515
     516            wp_send_json_success([
     517                'message' => 'Expired transients deleted successfully',
     518                'count' => count($deleted_transients),
     519                'deleted_transients' => $deleted_transients,
     520            ]);
     521        } catch (\Exception $e) {
     522            wp_send_json_error('Database error');
     523            wp_die();
     524        }
    630525    }
    631526
  • nhrrob-options-table-manager/trunk/includes/Traits/GlobalTrait.php

    r3256055 r3260159  
    282282
    283283    // Helper function to recursively sanitize arrays and objects
    284     public function sanitize_recursive(&$data) {
    285         // Handle arrays using array_walk_recursive for deep sanitization
     284    public function sanitize_recursive($data) {
    286285        if (is_array($data)) {
    287             array_walk_recursive($data, function (&$item, $key) {
    288                 if ($key !== 'content') {
    289                     $item = sanitize_text_field($item); // Only sanitize if the key is not 'content'
    290                 }
    291             });
    292         }
    293         // Handle objects by converting them to arrays and applying the function recursively
    294         elseif (is_object($data)) {
    295             foreach ($data as $key => &$value) {
    296                 if ($key === 'content') {
    297                     continue; // Skip sanitization for 'content' fields
    298                 }
    299                
    300                 if (is_scalar($value)) {
    301                     $value = sanitize_text_field($value);
    302                 } else {
    303                     $this->sanitize_recursive($value); // Recurse for arrays or nested objects
     286            $sanitized_array = [];
     287            foreach ($data as $key => $value) {
     288                $sanitized_key = sanitize_key($key);
     289                if ($sanitized_key !== '') {
     290                    $sanitized_array[$sanitized_key] = $this->sanitize_recursive($value);
    304291                }
    305292            }
     293            return $sanitized_array;
     294        } elseif (is_object($data)) {
     295            $sanitized_object = new \stdClass();
     296            $object_vars = get_object_vars($data);
     297           
     298            foreach ($object_vars as $key => $value) {
     299                $sanitized_key = $this->sanitize_key($key);
     300                if ($sanitized_key !== '') {
     301                    $sanitized_object->$sanitized_key = $this->sanitize_recursive($value);
     302                }
     303            }
     304            return $sanitized_object;
     305        } else {
     306            return $this->sanitize_item($data);
    306307        }
    307308    }
    308309
    309     public function castValues(&$array, $option_name = '') {
    310         // $exceptional_option_names = $this->exceptional_option_names();
    311 
    312         foreach ($array as $key => &$value) {
    313             if (is_array($value)) {
    314                 $this->castValues($value); // Recursive call for nested arrays
    315             } elseif (is_numeric($value)) {
    316                 $value = (int) $value; // Convert numeric strings to integers
    317             } elseif ($value === "true") {
    318                 $value = true; // Convert "true" string to boolean true
    319             } elseif ($value === "false") {
    320                 $value = false; // Convert "false" string to boolean false
    321             } elseif (empty($value) && $key === "recurrence") {
    322                 $value = false; // Ensure recurrence defaults to false if empty
    323             }
    324 
    325             // if ( ! empty( $option_name ) && in_array( $option_name, $exceptional_option_names ) ) {
    326             //     if ( empty($value) && $key === "recurrence" ) {
    327             //         $value = false;
    328             //     }
    329             // }
     310    public function sanitize_item( $item ){
     311        $item_formatted = '';
     312
     313        if ( is_numeric( $item )) {
     314            $item_formatted = intval( $item );
     315        } elseif ( is_email( $item )) {
     316            $item_formatted = sanitize_email( $item );
     317        } else {
     318            $item_formatted = sanitize_text_field( wp_unslash( $item ) );
    330319        }
     320
     321        return $item_formatted;
    331322    }
    332323
  • nhrrob-options-table-manager/trunk/includes/views/admin/settings/index.php

    r3256055 r3260159  
    1919        </div>
    2020
     21        <!-- Filter starts -->
     22        <div class="nhrotm-filter-container">
     23            <div class="nhrotm-filter-row">
     24                <div class="nhrotm-filter-group">
     25                    <select id="option-type-filter">
     26                        <option value="all-options"><?php esc_html_e('All Options', 'nhrrob-options-table-manager'); ?></option>
     27                        <option value="all-transients"><?php esc_html_e('All Transients', 'nhrrob-options-table-manager'); ?></option>
     28                    </select>
     29                </div>
     30                <div class="nhrotm-filter-group">
     31                    <button id="delete-expired-transients" class="button button-danger" disabled><?php esc_html_e('Delete Expired Transients', 'nhrrob-options-table-manager'); ?></button>
     32                </div>
     33            </div>
     34        </div>
     35        <!-- Filter ends  -->
     36         
    2137        <table id="nhrotm-data-table" class="nhrotm-data-table wp-list-table widefat fixed striped">
    2238            <thead>
     
    3046            </thead>
    3147
     48            <tfoot>
     49                <tr>
     50                    <th><?php esc_html_e('Option ID', 'nhrrob-options-table-manager'); ?></th>
     51                    <th><?php esc_html_e('Option Name', 'nhrrob-options-table-manager'); ?></th>
     52                    <th><?php esc_html_e('Option Value', 'nhrrob-options-table-manager'); ?></th>
     53                    <th><?php esc_html_e('Autoload', 'nhrrob-options-table-manager'); ?></th>
     54                    <th><?php esc_html_e('Action', 'nhrrob-options-table-manager'); ?></th>
     55                </tr>
     56            </tfoot>
     57           
    3258            <tbody>
    3359
  • nhrrob-options-table-manager/trunk/nhrrob-options-table-manager.php

    r3256164 r3260159  
    66 * Author: Nazmul Hasan Robin
    77 * Author URI: https://profiles.wordpress.org/nhrrob/
    8  * Version: 1.1.6
     8 * Version: 1.1.7-beta1
    99 * Requires at least: 6.0
    1010 * Requires PHP: 7.4
     
    2828     * @var string
    2929     */
    30     const nhrotm_version = '1.1.6';
     30    const nhrotm_version = '1.1.7-beta1';
    3131
    3232    /**
  • nhrrob-options-table-manager/trunk/readme.txt

    r3256164 r3260159  
    55Tested up to: 6.7 
    66Requires PHP: 7.4 
    7 Stable tag: 1.1.6 
     7Stable tag: 1.1.6
    88License: GPLv2 or later 
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html 
     
    8585
    8686== Changelog ==
     87
     88= 1.1.7 - 23/03/2025 =
     89- Added: Column search feature
     90- Added: Filter by option type - option or transient
     91- Added: Delete all expired transients button and functionality
     92- Few minor bug fixing & improvements
    8793
    8894= 1.1.6 - 15/03/2025 =
  • nhrrob-options-table-manager/trunk/vendor/composer/installed.php

    r3256164 r3260159  
    11<?php return array(
    22    'root' => array(
    3         'pretty_version' => 'dev-trunk',
    4         'version' => 'dev-trunk',
     3        'pretty_version' => 'dev-master',
     4        'version' => 'dev-master',
    55        'type' => 'wordpress-plugin',
    66        'install_path' => __DIR__ . '/../../',
    77        'aliases' => array(),
    8         'reference' => 'd2fb624425f2ff9b9547ceac02969a74b7af62a7',
     8        'reference' => '1313080a34a75fa363e2c99b07d90d28b2ca3308',
    99        'name' => 'nhrotm/options-table-manager',
    1010        'dev' => false,
     
    1212    'versions' => array(
    1313        'nhrotm/options-table-manager' => array(
    14             'pretty_version' => 'dev-trunk',
    15             'version' => 'dev-trunk',
     14            'pretty_version' => 'dev-master',
     15            'version' => 'dev-master',
    1616            'type' => 'wordpress-plugin',
    1717            'install_path' => __DIR__ . '/../../',
    1818            'aliases' => array(),
    19             'reference' => 'd2fb624425f2ff9b9547ceac02969a74b7af62a7',
     19            'reference' => '1313080a34a75fa363e2c99b07d90d28b2ca3308',
    2020            'dev_requirement' => false,
    2121        ),
Note: See TracChangeset for help on using the changeset viewer.