Файловый менеджер - Редактировать - /home/avadvi5/public_html/wp-content/plugins/advanced-database-cleaner-pro/includes/models/class-adbc-options.php
Ðазад
<?php // Exit if accessed directly if ( ! defined( 'ABSPATH' ) ) exit; /** * ADBC options class. * * This class provides the options functions. */ class ADBC_Options { private const BIG_OPTION_THRESHOLD_WARNING = 150 * 1024; // 150 KB. (If you change this value, change it as well in js filter message and slice) private const TRUNCATE_LENGTH = 20; // Length to truncate the option value for display /** * Get the options list for the endpoint. * * @param array $filters Output of sanitize_filters(). * * @return WP_REST_Response The list of options. */ public static function get_options_list( $filters ) { // Prepare variables $options_list = []; $total_options = 0; $scan_counter = new ADBC_Scan_Counter(); $startRecord = ( $filters['current_page'] - 1 ) * $filters['items_per_page']; $endRecord = $startRecord + $filters['items_per_page']; $currentRecord = 0; $limit = ADBC_Settings::instance()->get_setting( 'database_rows_batch' ); $offset = 0; do { // Loop through all options in batches of $limit to avoid memory issues $options = self::get_options_list_batch( $filters, $limit, $offset ); $fetched_count = count( $options ); if ( ADBC_VERSION_TYPE === 'PREMIUM' ) ADBC_Scan_Results::instance()->load_scan_results_to_items_rows( $options, 'options' ); // Load scan results to the options rows else ADBC_Common_Model::load_scan_results_to_items_for_free_version( $options ); // Load scan results to the options rows for free version ADBC_Hardcoded_Items::instance()->load_hardcoded_scan_results_to_items_rows( $options, 'options' ); // Load hardcoded items to the options rows foreach ( $options as $index => $option ) { $scan_counter->refresh_categorization_count( $option->belongs_to ); if ( ! ADBC_Common_Model::is_item_satisfies_belongs_to( $filters, $option->belongs_to ) ) continue; $total_options++; // Count options that satisfy all filters and belongs_to // Only process the current batch if it's within the desired page range if ( $currentRecord >= $startRecord && $currentRecord < $endRecord ) { $options_list[] = [ // This id is used to identify the option in the frontend and take actions on it 'composite_id' => [ 'items_type' => 'options', 'site_id' => (int) $option->site_id, 'id' => (int) $option->option_id, 'name' => $option->name, ], 'id' => $option->option_id, 'name' => $option->name, // Used in the known addons modal & "show value modal". To be generic and work for all items types. 'option_name' => $option->name, 'value' => $option->value, 'size' => $option->size, 'autoload' => $option->autoload, 'site_id' => $option->site_id, 'belongs_to' => $option->belongs_to, 'known_plugins' => $option->known_plugins, 'known_themes' => $option->known_themes, ]; } $currentRecord++; } $offset += $limit; } while ( $fetched_count == $limit ); // Continue if the last batch was full // Loop over the $options_list and $scan_counter add the plugins/themes names from the dictionary if they are empty // This is because load_scan_results_to_rows() only loads the names of the plugins/themes that are currently installed if ( ADBC_VERSION_TYPE === 'PREMIUM' ) ADBC_Dictionary::add_missing_addons_names_from_dictionary( $options_list, $scan_counter, 'options' ); // Calculate total number of pages to verify that the current page sent by the user is within the range $total_real_pages = max( 1, ceil( $total_options / $filters['items_per_page'] ) ); return ADBC_Rest::success( "", [ 'items' => $options_list, 'total_items' => $total_options, 'real_current_page' => min( $filters['current_page'], $total_real_pages ), 'categorization_count' => $scan_counter->get_categorization_count(), 'plugins_count' => $scan_counter->get_plugins_count(), 'themes_count' => $scan_counter->get_themes_count(), ] ); } /** * Get the options list that satisfy the UI filters. * * @param array $filters Output of sanitize_filters(). * @param int $limit Limit for the number of rows to return. * @param int $offset Offset for the number of rows to return. * * @return array List of options that satisfy the filters. */ private static function get_options_list_batch( $filters, $limit, $offset ) { global $wpdb; $sites_list = ADBC_Sites::instance()->get_sites_list( $filters['site_id'] ); $is_single_site_query = ( count( $sites_list ) === 1 ); /* ────────────────────────────────────────────────────────────── * Build a safe ORDER BY clause * ─────────────────────────────────────────────────────────────*/ $allowed_columns = [ 'option_name' => '`name`', 'size' => '`size`', 'autoload' => '`autoload`', 'site_id' => '`site_id`', ]; $sort_col = $filters['sort_by'] ?? ''; $sort_dir = strtoupper( $filters['sort_order'] ?? 'ASC' ); $sort_dir = ( $sort_dir === 'DESC' ) ? 'DESC' : 'ASC'; // Add 'order by' clause if the column is allowed. $order_by_sql = isset( $allowed_columns[ $sort_col ] ) ? "ORDER BY {$allowed_columns[ $sort_col ]} {$sort_dir}" : ''; /* ────────────────────────────────────────────────────────────── * Single-site path (no UNION, no derived table) * ─────────────────────────────────────────────────────────────*/ if ( $is_single_site_query ) { $site = reset( $sites_list ); $table_name = $site['prefix'] . 'options'; $site_id = $site['id']; $sql = self::prepare_options_list_sql_for_single_site( $site_id, $table_name, $filters, $order_by_sql, $limit, $offset ); return $wpdb->get_results( $sql, OBJECT ); } /* ────────────────────────────────────────────────────────────── * Multisite path (UNION across all sites) * ─────────────────────────────────────────────────────────────*/ $union_queries = []; // Offset starts from 0, so we need to add the limit to it to increment the number of rows to fetch in each iteration. $total_rows_to_fetch = $offset + $limit; foreach ( $sites_list as $site ) { $table_name = $site['prefix'] . 'options'; // Get the options table name for the current site $site_id = $site['id']; // Get the site ID for the current site $union_queries[] = self::prepare_options_list_sql_for_union( $site_id, $table_name, $filters, $order_by_sql, $total_rows_to_fetch ); } $union_sql = implode( "\nUNION ALL\n", $union_queries ); $sql = $wpdb->prepare( "SELECT * FROM ( {$union_sql} ) AS rows_merged {$order_by_sql} LIMIT %d OFFSET %d ", $limit, $offset ); return $wpdb->get_results( $sql, OBJECT ); } /** * Prepare a SQL query string to get the options list for a single site * (no UNION, no derived table). * * @param int $site_id Site ID to query. * @param string $table_name Options table name to query. * @param array $filters Output of sanitize_filters(). * @param string $order_by_sql SQL query clause to order the results. * @param int $limit Limit for the number of rows to return. * @param int $offset Offset for the number of rows to return. * * @return string SQL query to get the options list for a single site. */ private static function prepare_options_list_sql_for_single_site( $site_id, $table_name, $filters, $order_by_sql, $limit, $offset ) { global $wpdb; $truncate_length = self::TRUNCATE_LENGTH; $autoloaded_values = self::get_values_to_autoload(); $params = [ absint( $site_id ) ]; // %d for site_id in SELECT /* ────────────────────────────────────────────────────────────── * Assemble the dynamic WHERE parts * ─────────────────────────────────────────────────────────────*/ $where = [ '`option_name` NOT LIKE %s', // skip transients '`option_name` NOT LIKE %s', // skip site transients ]; $params[] = '\_transient\_%'; $params[] = '\_site\_transient\_%'; /* — Autoload filter — */ if ( isset( $filters['autoload'] ) && $filters['autoload'] !== 'all' && ! empty( $autoloaded_values ) ) { $placeholders = implode( ',', array_fill( 0, count( $autoloaded_values ), '%s' ) ); if ( $filters['autoload'] === 'yes' ) { $where[] = "`autoload` IN ($placeholders)"; } else { $where[] = "`autoload` NOT IN ($placeholders)"; } $params = array_merge( $params, $autoloaded_values ); } /* — Size ≥ threshold — */ if ( ! empty( $filters['size'] ) && (int) $filters['size'] > 0 ) { $bytes = ADBC_Common_Utils::convert_size_to_bytes( $filters['size'], $filters['size_unit'] ); $where[] = 'OCTET_LENGTH(`option_value`) >= %d'; $params[] = $bytes; } /* — Search filter — */ if ( ! empty( $filters['search_for'] ) && ! empty( $filters['search_in'] ) ) { $needle = '%' . $wpdb->esc_like( $filters['search_for'] ) . '%'; switch ( $filters['search_in'] ) { case 'name': $where[] = '`option_name` LIKE %s'; $params[] = $needle; break; case 'value': $where[] = '`option_value` LIKE %s'; $params[] = $needle; break; case 'all': $where[] = '(`option_name` LIKE %s OR `option_value` LIKE %s)'; $params[] = $needle; // for option_name $params[] = $needle; // for option_value break; } } $where_sql = 'WHERE ' . implode( ' AND ', $where ); // Add limit & offset at the end of the params array $params[] = absint( $limit ); $params[] = absint( $offset ); /* ────────────────────────────────────────────────────────────── * Final SQL * ─────────────────────────────────────────────────────────────*/ $sql = $wpdb->prepare( "SELECT `option_name` AS name, `option_id` AS option_id, SUBSTRING(`option_value`, 1, {$truncate_length}) AS value, `autoload` AS autoload, OCTET_LENGTH(`option_value`) AS size, %d AS site_id FROM {$table_name} {$where_sql} {$order_by_sql} LIMIT %d OFFSET %d ", ...$params ); return $sql; } /** * Prepare a SQL query string to get the options list that satisfy the UI filters. It will be used in a UNION query to get all options in all sites. * * @param int $site_id Site ID to query. * @param string $table_name Options table name to query. * @param array $filters Output of sanitize_filters(). * @param string $order_by_sql SQL query clause to order the results. * @param int $total_rows_to_fetch Limit for the number of rows to return. * * @return string SQL query to get the options list. */ private static function prepare_options_list_sql_for_union( $site_id, $table_name, $filters, $order_by_sql, $total_rows_to_fetch ) { global $wpdb; $truncate_length = self::TRUNCATE_LENGTH; $autoloaded_values = self::get_values_to_autoload(); $params = [ absint( $site_id ) ]; // Place the site_id at the beginning of the params array $needs_fix = ! ADBC_Database::is_collaction_unified( 'options', false, $filters['site_id'] ); $target_collation = ! empty( $wpdb->collate ) ? $wpdb->collate : 'utf8mb4_unicode_ci'; if ( $needs_fix ) { $name_expr = "CONVERT(`option_name` USING utf8mb4) COLLATE {$target_collation}"; $value_expr = "CONVERT(SUBSTRING(`option_value`, 1, {$truncate_length}) USING utf8mb4) COLLATE {$target_collation}"; $autoload_expr = "CONVERT(`autoload` USING utf8mb4) COLLATE {$target_collation}"; } else { $name_expr = "`option_name`"; $value_expr = "SUBSTRING(`option_value`, 1, {$truncate_length})"; $autoload_expr = "`autoload`"; } /* ────────────────────────────────────────────────────────────── * Assemble the dynamic WHERE parts * ─────────────────────────────────────────────────────────────*/ $where = [ '`option_name` NOT LIKE %s', // skip site transients '`option_name` NOT LIKE %s', ]; $params[] = '\_transient\_%'; $params[] = '\_site\_transient\_%'; /* — Autoload filter — */ if ( isset( $filters['autoload'] ) && $filters['autoload'] !== 'all' ) { $placeholders = implode( ',', array_fill( 0, count( $autoloaded_values ), '%s' ) ); if ( $filters['autoload'] === 'yes' ) { $where[] = "`autoload` IN ($placeholders)"; } else { $where[] = "`autoload` NOT IN ($placeholders)"; } $params = array_merge( $params, $autoloaded_values ); } /* — Size ≥ threshold — */ if ( ! empty( $filters['size'] ) && (int) $filters['size'] > 0 ) { $bytes = ADBC_Common_Utils::convert_size_to_bytes( $filters['size'], $filters['size_unit'] ); $where[] = 'OCTET_LENGTH(`option_value`) >= %d'; $params[] = $bytes; } /* — Search filter — */ if ( ! empty( $filters['search_for'] ) && ! empty( $filters['search_in'] ) ) { $needle = '%' . $wpdb->esc_like( $filters['search_for'] ) . '%'; switch ( $filters['search_in'] ) { case 'name': $where[] = '`option_name` LIKE %s'; $params[] = $needle; break; case 'value': $where[] = '`option_value` LIKE %s'; $params[] = $needle; break; case 'all': // Search in both columns $where[] = '(`option_name` LIKE %s OR `option_value` LIKE %s)'; $params[] = $needle; // for option_name $params[] = $needle; // for option_value break; } } $where_sql = 'WHERE ' . implode( ' AND ', $where ); $params[] = absint( $total_rows_to_fetch ); // Add the limit to the params array /* ────────────────────────────────────────────────────────────── * 3. Final SQL and fetch * ─────────────────────────────────────────────────────────────*/ $sql = $wpdb->prepare( "SELECT {$name_expr} AS name, `option_id` AS option_id, {$value_expr} AS value, {$autoload_expr} AS autoload, OCTET_LENGTH(`option_value`) AS size, %d AS site_id FROM {$table_name} {$where_sql} {$order_by_sql} LIMIT %d ", ...$params ); return '(' . $sql . ')'; } /** * Count the size of all autoloaded options in the wp_options table. * * @return array Array with autoloaded size and health status. */ public static function count_autoload_size_using_sql() { global $wpdb; $autoload_values = self::get_values_to_autoload(); // Build "%s,%s,%s" for $wpdb->prepare() $in_placeholders = implode( ',', array_fill( 0, count( $autoload_values ), '%s' ) ); $sql = $wpdb->prepare( "SELECT COALESCE( SUM( CASE WHEN autoload IN ($in_placeholders) THEN LENGTH(option_value) END ), 0 ) FROM {$wpdb->options} ", ...$autoload_values ); $autoloaded_size = (int) $wpdb->get_var( $sql ); $autoload_limit_in_bytes = self::get_autoload_warning_limit(); $autoload_health = $autoloaded_size > $autoload_limit_in_bytes ? 'bad' : 'good'; return [ 'autoloaded_size' => ADBC_Common_Utils::format_bytes( $autoloaded_size ), 'autoload_health' => $autoload_health, 'autoload_limit' => ADBC_Common_Utils::format_bytes( $autoload_limit_in_bytes ), ]; } /** * Get the autoload warning limit from WordPress filter or default value. * This warning limit is used to determine if the autoloaded options size is healthy or not. * * @return int Autoload warning limit in bytes. */ public static function get_autoload_warning_limit() { // Get the autoload limit from WordPress filter (allows customization via site_status_autoloaded_options_size_limit) // This filter was introduced in WordPress 6.6.0, so we check version for backward compatibility $default_limit = 800000; // 800 KB - WordPress default recommendation global $wp_version; if ( version_compare( $wp_version, '6.6.0', '>=' ) ) { // WordPress 6.6.0+ - use the filter (allows customization) $autoload_limit_bytes = apply_filters( 'site_status_autoloaded_options_size_limit', $default_limit ); } else { // WordPress < 6.6.0 - use default limit (filter doesn't exist yet) $autoload_limit_bytes = $default_limit; } return $autoload_limit_bytes; } /** * Get the count of big options in all sites. * * @return int Total count of big options. */ public static function count_big_options() { global $wpdb; $total_big_options = 0; $sites_prefixes = array_keys( ADBC_Sites::instance()->get_all_prefixes() ); foreach ( $sites_prefixes as $site_prefix ) { $table = $site_prefix . "options"; $count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM `$table` WHERE option_name NOT LIKE %s AND option_name NOT LIKE %s AND OCTET_LENGTH(option_value) > %d", '\_transient\_%', '\_site\_transient\_%', self::BIG_OPTION_THRESHOLD_WARNING ) ); $total_big_options += $count; } return $total_big_options; } /** * Count the total number of options that are not scanned. * * @return int Total not scanned options. */ public static function count_total_not_scanned_options() { $sites_prefixes = array_keys( ADBC_Sites::instance()->get_all_prefixes() ); $total_not_scanned = 0; $limit = ADBC_Settings::instance()->get_setting( 'database_rows_batch' ); foreach ( $sites_prefixes as $site_prefix ) { $offset = 0; do { // Loop through all options in batches of $limit to avoid memory issues $options = self::get_options_names( $site_prefix, $limit, $offset, false ); $fetched_count = count( $options ); $not_scanned_count = 0; if ( ADBC_VERSION_TYPE === 'PREMIUM' ) $not_scanned_count = ADBC_Scan_Utils::count_not_scanned_items_in_list( "options", $options ); else $not_scanned_count = ADBC_Common_Model::count_not_scanned_items_in_list_for_free( "options", $options ); $total_not_scanned += $not_scanned_count; $offset += $limit; } while ( $fetched_count == $limit ); // Continue if the last batch was full } return $total_not_scanned; } /** * Return an array with values of autoload that should be autoloaded * * @return array Autoload values. E.g. [ "yes", "on"... ] */ public static function get_values_to_autoload() { $autoload_values = [ "yes" ]; // Default value is "yes" prior to WP 6.6.0 if ( function_exists( 'wp_autoload_values_to_autoload' ) ) $autoload_values = wp_autoload_values_to_autoload(); // WP 6.6.0 and above have this function to get values that should be autoloaded return $autoload_values; } /** * Get total of all options in wp_options table, excluding transients. * * @return int Options count. */ public static function get_total_options_count() { global $wpdb; $total_options = 0; $sites_list = ADBC_Sites::instance()->get_sites_list(); foreach ( $sites_list as $site ) { $table_name = $site['prefix'] . 'options'; $count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table_name} WHERE option_name NOT LIKE %s AND option_name NOT LIKE %s", '\_transient\_%', '\_site\_transient\_%' ) ); $total_options += $count; } return $total_options; } /** * Get options names for a specific site excluding transients. * * @param string $site_prefix Site prefix. * @param int $limit Limit. * @param int $offset Offset. * @param boolean $keyed wether or not to key the array by names * * @return array Associative options names. */ public static function get_options_names( $site_prefix, $limit, $offset, $keyed = true ) { global $wpdb; $table = $site_prefix . 'options'; // $site_prefix is safe to use here as it is validated in the calling function. $query = $wpdb->prepare( "SELECT option_name FROM `$table` WHERE option_name NOT LIKE %s AND option_name NOT LIKE %s LIMIT %d OFFSET %d", '\_transient\_%', '\_site\_transient\_%', absint( $limit ), absint( $offset ) ); $options_names = $wpdb->get_col( $query ); if ( $keyed ) return array_fill_keys( $options_names, true ); else return $options_names; } /** * Get options names from their ids in a specific site prefix excluding transients. * * @param string $site_prefix Site prefix of the options. * @param array $options_ids Options ids to get their names. * * @return array Associative options names. */ public static function get_options_names_from_ids( $site_prefix, $options_ids ) { global $wpdb; $table = $site_prefix . 'options'; // $site_prefix is safe to use here as it is validated in the calling function. if ( empty( $options_ids ) ) return []; $in_placeholders = implode( ',', array_fill( 0, count( $options_ids ), '%d' ) ); // Prepare args to pass to the query. $args = array_merge( $options_ids, // the %d placeholders [ '\_transient\_%', '\_site\_transient\_%' ] // the %s placeholders ); $query = $wpdb->prepare( "SELECT option_name FROM `$table` WHERE option_id IN ($in_placeholders) AND option_name NOT LIKE %s AND option_name NOT LIKE %s", ...$args ); $options_names = $wpdb->get_col( $query ); // transform the options names array to associative array with the option name as key and true as value $options_names = array_fill_keys( $options_names, true ); return $options_names; } /** * Set autoload to "no" for grouped options. Options are grouped by site ID as key. * * @param array $grouped_selected Grouped selected options to set autoload to "no". * * @return array An array of options names that were not processed. */ public static function set_autoload_to_no( $grouped_selected ) { $autoload_value = function_exists( 'wp_autoload_values_to_autoload' ) ? 'off' : 'no'; $not_processed = self::set_autoload( $grouped_selected, $autoload_value ); return $not_processed; } /** * Set autoload to "yes" for grouped options. Options are grouped by site ID as key. * * @param array $grouped_selected Grouped selected options to set autoload to "yes". * * @return array An array of options names that were not processed. */ public static function set_autoload_to_yes( $grouped_selected ) { $autoload_value = function_exists( 'wp_autoload_values_to_autoload' ) ? 'on' : 'yes'; $not_processed = self::set_autoload( $grouped_selected, $autoload_value ); return $not_processed; } /** * Change autoload value for options. Options are grouped by site ID as key. * * @param array $grouped_selected Grouped selected options to set autoload for. * @param string $autoload_value The value to set for autoload, typically "yes"/"no" (or "on"/"off" for WP 6.6.0 and above). * * @return array Normally should return an array of options names that were not processed, but in this case it returns an empty array. */ private static function set_autoload( $grouped_selected, $autoload_value ) { global $wpdb; foreach ( $grouped_selected as $site_id => $group ) { if ( empty( $group ) ) continue; $ids = array_column( $group, 'id' ); $ids_placeholder = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); ADBC_Sites::instance()->switch_to_blog_id( $site_id ); $sql = $wpdb->prepare( "UPDATE {$wpdb->options} SET autoload = %s WHERE option_id IN ( $ids_placeholder )", ...array_merge( [ $autoload_value ], $ids ) ); $wpdb->query( $sql ); // Clear the object cache so WordPress picks up the new autoload flags wp_cache_delete( 'alloptions', 'options' ); ADBC_Sites::instance()->restore_blog(); } return []; } /** * Delete grouped options. Options are grouped by site ID as key. * * Expected input shape: * [ * 1 => [ [ 'id' => 123, 'name' => 'foo' ], ... ], * 2 => [ [ 'id' => 456, 'name' => 'bar' ], ... ], * ] * * @param array $grouped_selected Grouped selected options to delete. * @return array An array of option names that were not processed (not deleted). */ public static function delete_options( $grouped_selected ) { $cleanup_method = ADBC_Settings::instance()->get_setting( 'sql_or_native_cleanup_method' ); if ( $cleanup_method === 'native' ) { return self::delete_options_native( $grouped_selected ); } return self::delete_options_sql( $grouped_selected ); } /** * Deletes options using WordPress native delete_option() where safe. * * Important edge case: * delete_option() effectively operates on a sanitized option name; if the stored option_name has * leading/trailing spaces, delete_option( $name ) can be misleading (it will not target the exact row). * For such cases, we fall back to SQL-by-ID even in "native" mode to avoid leaving undeletable rows. * * @param array $grouped_selected * @return array Not processed option names. */ protected static function delete_options_native( $grouped_selected ) { global $wpdb; $not_processed = []; foreach ( $grouped_selected as $site_id => $group ) { ADBC_Sites::instance()->switch_to_blog_id( $site_id ); foreach ( $group as $selected ) { // check if the option have a leading or ending space which will be trimmed by delete_option() $can_be_trimmed = $selected['name'] !== trim( $selected['name'] ); $success = false; // try deleting using standard wordpress function if the trimming will not mislead the name if ( ! $can_be_trimmed ) $success = delete_option( $selected['name'] ); // try deleting using direct sql by option id to be sure there's no problem in the name if ( ! $success ) $success = $wpdb->delete( $wpdb->options, array( 'option_id' => $selected['id'] ) ); // if the deletion failed, add the option name to the not processed list if ( ! $success ) $not_processed[] = $selected['name']; } ADBC_Sites::instance()->restore_blog(); } return $not_processed; } /** * Deletes options using direct SQL (bulk delete by option_id) for each site. * * @param array $grouped_selected * @return array Not processed option names. */ protected static function delete_options_sql( $grouped_selected ) { global $wpdb; $not_processed = []; foreach ( $grouped_selected as $site_id => $group ) { $ids = []; foreach ( $group as $selected ) $ids[] = $selected['id']; ADBC_Sites::instance()->switch_to_blog_id( $site_id ); $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); // Bulk delete. $sql = "DELETE FROM {$wpdb->options} WHERE option_id IN ($placeholders)"; $sql = $wpdb->prepare( $sql, ...$ids ); $wpdb->query( $sql ); // Identify any remaining rows (not processed) deterministically. $check_sql = "SELECT option_name FROM {$wpdb->options} WHERE option_id IN ($placeholders)"; $check_sql = $wpdb->prepare( $check_sql, ...$ids ); $remaining_names = $wpdb->get_col( $check_sql ); if ( ! empty( $remaining_names ) ) foreach ( $remaining_names as $opt_name ) $not_processed[] = $opt_name; ADBC_Sites::instance()->restore_blog(); } return $not_processed; } /** * Get options names that exist from a list of options names. * * @param array $options_names The list of options names to check. * * @return array The list of options names that exist. */ public static function get_options_names_that_exists_from_list( $options_names ) { global $wpdb; // Normalize and validate input if ( empty( $options_names ) || ! is_array( $options_names ) ) return []; // Build UNION branches over all sites' options tables $branches = []; $sites = ADBC_Sites::instance()->get_sites_list(); // Prepare the IN() placeholders once (same for all branches) $in_placeholders = implode( ',', array_fill( 0, count( $options_names ), '%s' ) ); foreach ( $sites as $site ) { $table = $site['prefix'] . 'options'; // Each branch is a fully prepared subquery to safely embed into the UNION $sql = $wpdb->prepare( "SELECT DISTINCT option_name AS name FROM `{$table}` WHERE option_name IN ( {$in_placeholders} ) AND option_name NOT LIKE %s AND option_name NOT LIKE %s", ...array_merge( $options_names, [ '\_transient\_%', '\_site\_transient\_%' ] ) ); $branches[] = '(' . $sql . ')'; } if ( empty( $branches ) ) return []; $union_sql = implode( "\nUNION ALL\n", $branches ); $query = "SELECT DISTINCT name FROM ( {$union_sql} ) AS existing_names"; $existing_names = $wpdb->get_col( $query ); return array_values( array_unique( array_filter( (array) $existing_names ) ) ); } }
| ver. 1.1 | |
.
| PHP 8.3.30 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка