diff --git a/js/admin-script.js b/js/admin-script.js index cbe412a..45cfd25 100644 --- a/js/admin-script.js +++ b/js/admin-script.js @@ -39,12 +39,33 @@ jQuery( document ).ready( function () { return 'option_' + optionName.replace( /\./g, '_' ); } + /** + * Store sources (plugin names) for each table from AJAX responses. + * + * @type {Object} + */ + const tableSources = {}; + + /** + * Creates a column filter setup function bound to a specific table selector. + * + * @param {string} tableSelector - The table selector. + * @return {Function} The filter setup function. + */ + function createColumnFilterSetup( tableSelector ) { + return function () { + setupColumnFilters.call( this, tableSelector ); + }; + } + /** * Initializes the DataTable for the given selector. * * @param {string} selector - The table selector. */ function initializeDataTable( selector ) { + const filterSetup = createColumnFilterSetup( selector ); + const options = { pageLength: 25, autoWidth: false, @@ -54,7 +75,7 @@ jQuery( document ).ready( function () { return generateRowId( data.name ); }, initComplete() { - this.api().columns( 'source:name' ).every( setupColumnFilters ); + this.api().columns( 'source:name' ).every( filterSetup ); }, language: aaaOptionOptimizer.i18n, }; @@ -66,7 +87,10 @@ jQuery( document ).ready( function () { 'aaa-option-optimizer/v1/unused-options', headers: { 'X-WP-Nonce': aaaOptionOptimizer.nonce }, type: 'GET', - dataSrc: 'data', + dataSrc( json ) { + tableSources[ selector ] = json.sources || []; + return json.data; + }, }; options.serverSide = true; options.processing = true; @@ -75,7 +99,7 @@ jQuery( document ).ready( function () { }; options.initComplete = function () { getBulkActionsForm( selector, [ 'autoload-off' ] ).call( this ); - this.api().columns( 'source:name' ).every( setupColumnFilters ); + this.api().columns( 'source:name' ).every( filterSetup ); }; options.order = [ [ 1, 'asc' ] ]; // Order by 2nd column, first column is checkbox. } @@ -87,7 +111,10 @@ jQuery( document ).ready( function () { 'aaa-option-optimizer/v1/used-not-autoloaded-options', headers: { 'X-WP-Nonce': aaaOptionOptimizer.nonce }, type: 'GET', - dataSrc: 'data', + dataSrc( json ) { + tableSources[ selector ] = json.sources || []; + return json.data; + }, }; options.serverSide = true; options.processing = true; @@ -96,7 +123,7 @@ jQuery( document ).ready( function () { }; options.initComplete = function () { getBulkActionsForm( selector, [ 'autoload-on' ] ).call( this ); - this.api().columns( 'source:name' ).every( setupColumnFilters ); + this.api().columns( 'source:name' ).every( filterSetup ); }; options.order = [ [ 1, 'asc' ] ]; // Order by 2nd column, first column is checkbox. } @@ -106,10 +133,16 @@ jQuery( document ).ready( function () { url: `${ aaaOptionOptimizer.root }aaa-option-optimizer/v1/options-that-do-not-exist`, headers: { 'X-WP-Nonce': aaaOptionOptimizer.nonce }, type: 'GET', - dataSrc: 'data', + dataSrc( json ) { + tableSources[ selector ] = json.sources || []; + return json.data; + }, }; options.serverSide = true; options.processing = true; + options.initComplete = function () { + this.api().columns( 'source:name' ).every( filterSetup ); + }; } if ( selector === '#all_options_table' ) { @@ -124,7 +157,7 @@ jQuery( document ).ready( function () { 'autoload-on', 'autoload-off', ] ).call( this ); - this.api().columns( 'source:name' ).every( setupColumnFilters ); + this.api().columns( 'source:name' ).every( filterSetup ); }; options.order = [ [ 1, 'asc' ] ]; // Order by 2nd column, first column is checkbox. } @@ -253,8 +286,10 @@ jQuery( document ).ready( function () { /** * Sets up the column filters for the DataTable. + * + * @param {string} tableSelector - The table selector to get sources from. */ - function setupColumnFilters() { + function setupColumnFilters( tableSelector ) { const column = this; const select = document.createElement( 'select' ); select.add( @@ -266,13 +301,22 @@ jQuery( document ).ready( function () { column.search( select.value, { exact: true } ).draw(); } ); - column - .data() - .unique() - .sort() - .each( function ( d ) { - select.add( new Option( d ) ); + // Use sources from AJAX response if available (for server-side processing), + // otherwise fall back to column data (for client-side processing). + const sources = tableSources[ tableSelector ]; + if ( sources && sources.length > 0 ) { + sources.forEach( function ( source ) { + select.add( new Option( source ) ); } ); + } else { + column + .data() + .unique() + .sort() + .each( function ( d ) { + select.add( new Option( d ) ); + } ); + } } /** diff --git a/src/class-rest.php b/src/class-rest.php index 9310487..5934a77 100644 --- a/src/class-rest.php +++ b/src/class-rest.php @@ -255,6 +255,15 @@ public function get_unused_options() { $autoload_option_keys = array_fill_keys( $autoloaded_option_names, true ); $unused_keys = array_diff_key( $autoload_option_keys, $used_options ); + // Collect all unique sources (plugin names) before any filtering. + $all_sources = []; + foreach ( array_keys( $unused_keys ) as $option_name ) { + $plugin_name = $this->get_plugin_name( $option_name ); + $all_sources[ $plugin_name ] = true; + } + $all_sources = array_keys( $all_sources ); + sort( $all_sources ); + // Apply source filter to unused keys if specified. $filter_by_source = isset( $_GET['columns'][2]['search']['value'] ) ? trim( \sanitize_text_field( \wp_unslash( $_GET['columns'][2]['search']['value'] ) ) ) : ''; if ( '' !== $filter_by_source ) { @@ -315,6 +324,7 @@ public function get_unused_options() { 'recordsTotal' => $total_unused, 'recordsFiltered' => $total_unused, 'data' => $response_data, + 'sources' => $all_sources, ], 200 ); @@ -366,6 +376,15 @@ public function get_used_not_autoloaded_options() { // Find used options that are not autoloaded. $non_autoloaded_used_keys = array_diff_key( $used_options, $autoload_option_keys ); + // Collect all unique sources (plugin names) before any filtering. + $all_sources = []; + foreach ( array_keys( $non_autoloaded_used_keys ) as $option_name ) { + $plugin_name = $this->get_plugin_name( $option_name ); + $all_sources[ $plugin_name ] = true; + } + $all_sources = array_keys( $all_sources ); + sort( $all_sources ); + // Filter by source (plugin). $filter_by_source = isset( $_GET['columns'][2]['search']['value'] ) ? trim( \sanitize_text_field( \wp_unslash( $_GET['columns'][2]['search']['value'] ) ) ) : ''; if ( '' !== $filter_by_source ) { @@ -391,6 +410,7 @@ function ( $option_name ) use ( $search ) { 'recordsTotal' => 0, 'recordsFiltered' => 0, 'data' => [], + 'sources' => $all_sources, ], 200 ); @@ -442,6 +462,7 @@ function ( $option_name ) use ( $search ) { 'recordsTotal' => $total_filtered, 'recordsFiltered' => $total_filtered, 'data' => $response_data, + 'sources' => $all_sources, ], 200 ); @@ -490,24 +511,6 @@ public function get_options_that_do_not_exist() { // Get used options that are not autoloaded. $non_autoloaded_keys = array_diff_key( $used_options, $autoload_option_keys ); - // Filter by source (plugin). - $filter_by_source = isset( $_GET['columns'][1]['search']['value'] ) ? trim( \sanitize_text_field( \wp_unslash( $_GET['columns'][1]['search']['value'] ) ) ) : ''; - if ( '' !== $filter_by_source ) { - $non_autoloaded_keys = $this->filter_by_source( $non_autoloaded_keys, $filter_by_source ); - } - - // Search. - $search = isset( $_GET['search']['value'] ) ? trim( \sanitize_text_field( \wp_unslash( $_GET['search']['value'] ) ) ) : ''; - if ( '' !== $search ) { - $non_autoloaded_keys = array_filter( - $non_autoloaded_keys, - function ( $option_name ) use ( $search ) { - return stripos( $option_name, $search ) !== false; - }, - ARRAY_FILTER_USE_KEY - ); - } - if ( empty( $non_autoloaded_keys ) ) { return new \WP_REST_Response( [ @@ -515,6 +518,7 @@ function ( $option_name ) use ( $search ) { 'recordsTotal' => 0, 'recordsFiltered' => 0, 'data' => [], + 'sources' => [], ], 200 ); @@ -532,11 +536,11 @@ function ( $option_name ) use ( $search ) { ); $existing_keys = array_fill_keys( $existing_option_names, true ); - // Filter only those that do NOT exist. - $response_data = []; + // Build array of non-existing options (before any filtering). + $non_existing_options = []; foreach ( $non_autoloaded_keys as $option => $count ) { if ( ! isset( $existing_keys[ $option ] ) ) { - $response_data[] = [ + $non_existing_options[ $option ] = [ 'name' => $option, 'plugin' => $this->get_plugin_name( $option ), 'count' => $count, @@ -545,6 +549,37 @@ function ( $option_name ) use ( $search ) { } } + // Collect all unique sources (plugin names) before any filtering. + $all_sources = []; + foreach ( $non_existing_options as $row ) { + $all_sources[ $row['plugin'] ] = true; + } + $all_sources = array_keys( $all_sources ); + sort( $all_sources ); + + // Filter by source (plugin). + $filter_by_source = isset( $_GET['columns'][1]['search']['value'] ) ? trim( \sanitize_text_field( \wp_unslash( $_GET['columns'][1]['search']['value'] ) ) ) : ''; + if ( '' !== $filter_by_source ) { + $non_existing_options = array_filter( + $non_existing_options, + function ( $row ) use ( $filter_by_source ) { + return false !== stripos( $row['plugin'], $filter_by_source ); + } + ); + } + + // Search. + $search = isset( $_GET['search']['value'] ) ? trim( \sanitize_text_field( \wp_unslash( $_GET['search']['value'] ) ) ) : ''; + if ( '' !== $search ) { + $non_existing_options = array_filter( + $non_existing_options, + function ( $row ) use ( $search ) { + return stripos( $row['name'], $search ) !== false; + } + ); + } + + $response_data = array_values( $non_existing_options ); $total_filtered = count( $response_data ); // Pagination. @@ -564,6 +599,7 @@ function ( $option_name ) use ( $search ) { 'recordsTotal' => $total_filtered, 'recordsFiltered' => $total_filtered, 'data' => $response_data, + 'sources' => $all_sources, ], 200 );