<?php

// Utilities that depend on WordPress code.

namespace WP_CLI\Utils;

use ReflectionClass;
use ReflectionParameter;
use WP_CLI;
use WP_CLI\UpgraderSkin;

/**
 * @return void
 */
function wp_not_installed() {
	global $wpdb, $table_prefix;
	if ( ! is_blog_installed() && ! defined( 'WP_INSTALLING' ) ) {
		$tables         = $wpdb->get_col( "SHOW TABLES LIKE '%_options'" );
		$found_prefixes = [];
		if ( count( $tables ) ) {
			foreach ( $tables as $table ) {
				$maybe_prefix = substr( $table, 0, - strlen( 'options' ) );
				if ( $maybe_prefix !== $table_prefix ) {
					$found_prefixes[] = $maybe_prefix;
				}
			}
		}
		if ( count( $found_prefixes ) ) {
			sort( $found_prefixes );
			$prefix_list   = implode( ', ', $found_prefixes );
			$install_label = count( $found_prefixes ) > 1 ? 'installations' : 'installation';
			WP_CLI::error(
				"The site you have requested is not installed.\n" .
				"Your table prefix is '{$table_prefix}'. Found {$install_label} with table prefix: {$prefix_list}.\n" .
				'Or, run `wp core install` to create database tables.'
			);
		} else {
			WP_CLI::error(
				"The site you have requested is not installed.\n" .
				'Run `wp core install` to create database tables.'
			);
		}
	}
}

// phpcs:disable WordPress.PHP.IniSet -- Intentional & correct usage.

/**
 * @return void
 */
function wp_debug_mode() {
	if ( WP_CLI::get_config( 'debug' ) ) {
		if ( ! defined( 'WP_DEBUG' ) ) {
			define( 'WP_DEBUG', true );
		}

		error_reporting( E_ALL & ~E_DEPRECATED );
	} else {
		if ( WP_DEBUG ) {
			error_reporting( E_ALL );

			if ( WP_DEBUG_DISPLAY ) {
				ini_set( 'display_errors', 1 );
			} elseif ( null !== WP_DEBUG_DISPLAY ) {
				ini_set( 'display_errors', 0 );
			}

			if ( in_array( strtolower( (string) WP_DEBUG_LOG ), [ 'true', '1' ], true ) ) {
				$log_path = WP_CONTENT_DIR . '/debug.log';
			} elseif ( is_string( WP_DEBUG_LOG ) ) {
				$log_path = WP_DEBUG_LOG;
			} else {
				$log_path = false;
			}

			if ( false !== $log_path ) {
				ini_set( 'log_errors', 1 );
				ini_set( 'error_log', $log_path );
			}
		} else {
			error_reporting( E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_ERROR | E_WARNING | E_PARSE | E_USER_ERROR | E_USER_WARNING | E_RECOVERABLE_ERROR );
		}

		if ( defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
			ini_set( 'display_errors', 0 );
		}
	}

	// XDebug already sends errors to STDERR.
	ini_set( 'display_errors', function_exists( 'xdebug_debug_zval' ) ? false : 'stderr' );
}
// phpcs:enable

/**
 * @return void
 */
function replace_wp_die_handler() {
	\remove_filter( 'wp_die_handler', '_default_wp_die_handler' );
	\add_filter(
		'wp_die_handler',
		function () {
			return __NAMESPACE__ . '\\wp_die_handler';
		}
	);
}

/**
 * @return never
 */
function wp_die_handler( $message ) {

	if ( $message instanceof \WP_Error ) {
		$text_message = $message->get_error_message();
		$error_data   = $message->get_error_data( 'internal_server_error' );
		if ( ! empty( $error_data['error']['file'] )
			&& false !== stripos( $error_data['error']['file'], 'themes/functions.php' ) ) {
			$text_message = 'An unexpected functions.php file in the themes directory may have caused this internal server error.';
		}
	} else {
		$text_message = $message;
	}

	$text_message = wp_clean_error_message( $text_message );

	WP_CLI::error( $text_message );
}

/**
 * Clean HTML error message so suitable for text display.
 *
 * @param string $message
 * @return string
 */
function wp_clean_error_message( $message ) {
	$original_message = trim( $message );
	$message          = $original_message;
	if ( preg_match( '|^\<h1>(.+?)</h1>|', $original_message, $matches ) ) {
		$message = $matches[1] . '.';
	}
	if ( preg_match( '|\<p>(.+?)</p>|', $original_message, $matches ) ) {
		$message .= ' ' . $matches[1];
	}

	$search_replace = [
		'<code>'  => '`',
		'</code>' => '`',
	];
	$message        = str_replace( array_keys( $search_replace ), array_values( $search_replace ), $message );
	$message        = namespace\strip_tags( $message );
	$message        = html_entity_decode( $message, ENT_COMPAT, 'UTF-8' );

	return $message;
}

/**
 * @param string $url
 * @return string
 */
function wp_redirect_handler( $url ) {
	WP_CLI::warning( 'Some code is trying to do a URL redirect. Backtrace:' );

	ob_start();
	debug_print_backtrace();
	fwrite( STDERR, ob_get_clean() );

	return $url;
}

/**
 * @param string $since Version number.
 * @param string $path File to include.
 * @return void
 */
function maybe_require( $since, $path ) {
	if ( wp_version_compare( $since, '>=' ) ) {
		require $path;
	}
}

/**
 *
 * @param class-string $class_name
 * @param bool         $insecure
 *
 * @return \WP_Upgrader Upgrader instance.
 * @throws \ReflectionException
 */
function get_upgrader( $class_name, $insecure = false ) {
	if ( ! class_exists( '\WP_Upgrader' ) ) {
		if ( file_exists( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ) ) {
			include ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
		}
	}

	if ( ! class_exists( '\WP_Upgrader_Skin' ) ) {
		if ( file_exists( ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php' ) ) {
			include ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php';
		}
	}

	$uses_insecure_flag = false;

	$reflection  = new ReflectionClass( $class_name );
	$constructor = $reflection->getConstructor();
	if ( $constructor ) {
		$arguments = $constructor->getParameters();
		/** @var ReflectionParameter $argument */
		foreach ( $arguments as $argument ) {
			if ( 'insecure' === $argument->name ) {
				$uses_insecure_flag = true;
				break;
			}
		}
	}

	if ( $uses_insecure_flag ) {
		return new $class_name( new UpgraderSkin(), $insecure );
	} else {
		return new $class_name( new UpgraderSkin() );
	}
}

/**
 * Converts a plugin basename back into a friendly slug.
 *
 * @param string $basename
 * @return string
 */
function get_plugin_name( $basename ) {
	if ( false === strpos( $basename, '/' ) ) {
		$name = basename( $basename, '.php' );
	} else {
		$name = dirname( $basename );
	}

	return $name;
}

/**
 * Determine whether a plugin is skipped.
 *
 * @param string $file
 * @return bool
 */
function is_plugin_skipped( $file ) {
	$name = get_plugin_name( str_replace( WP_PLUGIN_DIR . '/', '', $file ) );

	$skipped_plugins = WP_CLI::get_runner()->config['skip-plugins'];
	if ( true === $skipped_plugins ) {
		return true;
	}

	if ( ! is_array( $skipped_plugins ) ) {
		$skipped_plugins = explode( ',', $skipped_plugins );
	}

	return in_array( $name, array_filter( $skipped_plugins ), true );
}

/**
 * Get theme name from path.
 *
 * @param string $path
 * @return string
 */
function get_theme_name( $path ) {
	return basename( $path );
}

/**
 * Determine whether a theme is skipped.
 *
 * @param string $path
 * @return bool
 */
function is_theme_skipped( $path ) {
	$name = get_theme_name( $path );

	$skipped_themes = WP_CLI::get_runner()->config['skip-themes'];
	if ( true === $skipped_themes ) {
		return true;
	}

	if ( ! is_array( $skipped_themes ) ) {
		$skipped_themes = explode( ',', $skipped_themes );
	}

	return in_array( $name, array_filter( $skipped_themes ), true );
}

/**
 * Register the sidebar for unused widgets.
 * Core does this in /wp-admin/widgets.php, which isn't helpful.
 *
 * @return void
 */
function wp_register_unused_sidebar() {

	register_sidebar(
		[
			'name'          => __( 'Inactive Widgets' ),
			'id'            => 'wp_inactive_widgets',
			'class'         => 'inactive-sidebar',
			'description'   => __( 'Drag widgets here to remove them from the sidebar but keep their settings.' ),
			'before_widget' => '',
			'after_widget'  => '',
			'before_title'  => '',
			'after_title'   => '',
		]
	);
}

/**
 * Attempts to determine which object cache is being used.
 *
 * Note that the guesses made by this function are based on the WP_Object_Cache classes
 * that define the 3rd party object cache extension. Changes to those classes could render
 * problems with this function's ability to determine which object cache is being used.
 *
 * @return string
 */
function wp_get_cache_type() {
	global $_wp_using_ext_object_cache, $wp_object_cache;

	$message = 'Unknown';

	if ( ! empty( $_wp_using_ext_object_cache ) ) {
		// Test for Memcached PECL extension memcached object cache (https://github.com/tollmanz/wordpress-memcached-backend)
		if ( isset( $wp_object_cache->m ) && $wp_object_cache->m instanceof \Memcached ) {
			$message = 'Memcached';

			// Test for Memcache PECL extension memcached object cache (https://wordpress.org/extend/plugins/memcached/)
		} elseif ( isset( $wp_object_cache->mc ) ) {
			$is_memcache = true;
			foreach ( $wp_object_cache->mc as $bucket ) {
				if ( ! $bucket instanceof \Memcache && ! $bucket instanceof \Memcached ) {
					$is_memcache = false;
				}
			}

			if ( $is_memcache ) {
				$message = 'Memcache';
			}

			// Test for Xcache object cache (https://plugins.svn.wordpress.org/xcache/trunk/object-cache.php)
		} elseif ( $wp_object_cache instanceof \XCache_Object_Cache ) {
			$message = 'Xcache';

			// Test for WinCache object cache (https://wordpress.org/extend/plugins/wincache-object-cache-backend/)
		} elseif ( class_exists( 'WinCache_Object_Cache' ) ) {
			$message = 'WinCache';

			// Test for APC object cache (https://wordpress.org/extend/plugins/apc/)
		} elseif ( class_exists( 'APC_Object_Cache' ) ) {
			$message = 'APC';

			// Test for WP Redis (https://wordpress.org/plugins/wp-redis/)
		} elseif ( isset( $wp_object_cache->redis ) && $wp_object_cache->redis instanceof \Redis ) {
			$message = 'Redis';

			// Test for Redis Object Cache (https://wordpress.org/plugins/redis-cache/)
		} elseif ( method_exists( $wp_object_cache, 'redis_instance' ) && method_exists( $wp_object_cache, 'redis_status' ) ) {
			$message = 'Redis';

			// Test for Object Cache Pro (https://objectcache.pro/)
		} elseif ( method_exists( $wp_object_cache, 'config' ) && method_exists( $wp_object_cache, 'connection' ) ) {
			$message = 'Redis';

			// Test for WP LCache Object cache (https://github.com/lcache/wp-lcache)
		} elseif ( isset( $wp_object_cache->lcache ) && $wp_object_cache->lcache instanceof \LCache\Integrated ) {
			$message = 'WP LCache';

		} elseif ( function_exists( 'w3_instance' ) ) {
			$config = w3_instance( 'W3_Config' );

			if ( $config->get_boolean( 'objectcache.enabled' ) ) {
				$message = 'W3TC ' . $config->get_string( 'objectcache.engine' );
			}
		}
	} else {
		$message = 'Default';
	}

	return $message;
}

/**
 * Clear WordPress internal object caches.
 *
 * In long-running scripts, the internal caches on `$wp_object_cache` and `$wpdb`
 * can grow to consume gigabytes of memory. Periodically calling this utility
 * can help with memory management.
 *
 * @access public
 * @category System
 * @deprecated 1.5.0
 *
 * @return void
 */
function wp_clear_object_cache() {
	global $wpdb, $wp_object_cache;

	$wpdb->queries = [];

	if ( function_exists( 'wp_cache_flush_runtime' ) && function_exists( 'wp_cache_supports' ) ) {
		if ( wp_cache_supports( 'flush_runtime' ) ) {
			wp_cache_flush_runtime();
			return;
		}
	}

	if ( ! is_object( $wp_object_cache ) ) {
		return;
	}

	// The following are Memcached (Redux) plugin specific (see https://core.trac.wordpress.org/ticket/31463).
	if ( isset( $wp_object_cache->group_ops ) ) {
		$wp_object_cache->group_ops = [];
	}
	if ( isset( $wp_object_cache->stats ) ) {
		$wp_object_cache->stats = [];
	}
	if ( isset( $wp_object_cache->memcache_debug ) ) {
		$wp_object_cache->memcache_debug = [];
	}
	// Used by `WP_Object_Cache` also.
	if ( isset( $wp_object_cache->cache ) ) {
		$wp_object_cache->cache = [];
	}
}

/**
 * Get a set of tables in the database.
 *
 * Interprets common command-line options into a resolved set of table names.
 *
 * @param array<string>         $args Provided table names, or tables with wildcards.
 * @param array<string, string> $assoc_args Optional flags for groups of tables (e.g. --network)
 * @return array<string>
 */
function wp_get_table_names( $args, $assoc_args = [] ) {
	global $wpdb;

	// Abort if incompatible args supplied.
	if ( get_flag_value( $assoc_args, 'base-tables-only' ) && get_flag_value( $assoc_args, 'views-only' ) ) {
		WP_CLI::error( 'You cannot supply --base-tables-only and --views-only at the same time.' );
	}

	// Pre-load tables SQL query with Views restriction if needed.
	if ( get_flag_value( $assoc_args, 'base-tables-only' ) ) {
		$tables_sql = 'SHOW FULL TABLES WHERE Table_Type = "BASE TABLE"';

	} elseif ( get_flag_value( $assoc_args, 'views-only' ) ) {
		$tables_sql = 'SHOW FULL TABLES WHERE Table_Type = "VIEW"';

	}

	if ( get_flag_value( $assoc_args, 'all-tables' ) ) {
		if ( empty( $tables_sql ) ) {
			$tables_sql = 'SHOW TABLES';
		}

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is safe, see above.
		$tables = $wpdb->get_col( $tables_sql, 0 );

	} elseif ( get_flag_value( $assoc_args, 'all-tables-with-prefix' ) ) {
		if ( empty( $tables_sql ) ) {
			$tables_sql = $wpdb->prepare( 'SHOW TABLES LIKE %s', esc_like( $wpdb->get_blog_prefix() ) . '%' );
		} else {
			$tables_sql .= sprintf( " AND %s LIKE '%s'", esc_sql_ident( 'Tables_in_' . $wpdb->dbname ), esc_like( $wpdb->get_blog_prefix() ) . '%' );
		}

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above.
		$tables = $wpdb->get_col( $tables_sql, 0 );

	} else {
		$scope = get_flag_value( $assoc_args, 'scope', 'all' );

		// Note: BC change 1.5.0, taking scope into consideration for network also.
		if ( get_flag_value( $assoc_args, 'network' ) && is_multisite() ) {
			$network_global_scope = in_array( $scope, [ 'all', 'global', 'ms_global' ], true ) ? ( 'all' === $scope ? 'global' : $scope ) : '';
			$wp_tables            = array_values( $wpdb->tables( $network_global_scope ) );
			if ( in_array( $scope, [ 'all', 'blog' ], true ) ) {
				// Do directly for compat with old WP versions. Note: private, deleted, archived sites are not excluded.
				$blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs WHERE site_id = $wpdb->siteid" );
				foreach ( $blog_ids as $blog_id ) {
					$wp_tables = array_merge( $wp_tables, array_values( $wpdb->tables( 'blog', true /*prefix*/, $blog_id ) ) );
				}
			}
		} else {
			$wp_tables = array_values( $wpdb->tables( $scope ) );
		}

		// The global_terms_enabled() function has been deprecated with WP 6.1+.
		if ( wp_version_compare( '6.1', '>=' ) || ! global_terms_enabled() ) { // phpcs:ignore WordPress.WP.DeprecatedFunctions.global_terms_enabledFound
			// Only include sitecategories when it's actually enabled.
			$wp_tables = array_values( array_diff( $wp_tables, [ $wpdb->sitecategories ] ) );
		}

		// Note: BC change 1.5.0, tables are sorted (via TABLES view).
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- uses esc_sql_ident() and $wpdb->_escape().
		$tables = $wpdb->get_col( sprintf( "SHOW TABLES WHERE %s IN ('%s')", esc_sql_ident( 'Tables_in_' . $wpdb->dbname ), implode( "', '", $wpdb->_escape( $wp_tables ) ) ) );

		if ( get_flag_value( $assoc_args, 'base-tables-only' ) || get_flag_value( $assoc_args, 'views-only' ) ) {
			// Apply Views restriction args if needed.
			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above.
			$views_query_tables = $wpdb->get_col( $tables_sql, 0 );
			$tables             = array_intersect( $tables, $views_query_tables );
		}
	}

	// Filter by `$args`.
	if ( $args ) {
		$args_tables = [];
		foreach ( $args as $arg ) {
			if ( false !== strpos( $arg, '*' ) || false !== strpos( $arg, '?' ) ) {
				$args_tables = array_merge(
					$args_tables,
					array_filter(
						$tables,
						function ( $v ) use ( $arg ) {
							return fnmatch( $arg, $v );
						}
					)
				);
			} else {
				$args_tables[] = $arg;
			}
		}
		$args_tables = array_values( array_unique( $args_tables ) );
		$tables      = array_values( array_intersect( $tables, $args_tables ) );
		if ( empty( $tables ) ) {
			WP_CLI::error( sprintf( "Couldn't find any tables matching: %s", implode( ' ', $args ) ) );
		}
	}

	return $tables;
}

/**
 * Failsafe use of the WordPress wp_strip_all_tags() function.
 *
 * Automatically falls back to strip_tags() function if the WP function is not
 * available.
 *
 * @param string $string String to strip the tags from.
 * @return string String devoid of tags.
 */
function strip_tags( $string ) {
	if ( function_exists( 'wp_strip_all_tags' ) ) {
		return \wp_strip_all_tags( $string );
	}

	$string = preg_replace(
		'@<(script|style)[^>]*?>.*?</\\1>@si',
		'',
		$string
	);

	// phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- Fallback.
	$string = \strip_tags( $string );

	return trim( $string );
}
