to find and * report WordPress installations. A WordPress installation is a wp-includes * directory with a version.php file. * * Avoids recursing some known paths (e.g. /node_modules/, hidden sys dirs) * to significantly improve performance. * * Indicates depth at which the WordPress installations was found, and its * alias, if it has one. * * ## OPTIONS * * * : Path to search the subdirectories of. * * [--skip-ignored-paths] * : Skip the paths that are ignored by default. * * [--include_ignored_paths=] * : Include additional ignored paths as CSV (e.g. '/sys-backup/,/temp/'). * * [--max_depth=] * : Only recurse to a specified depth, inclusive. * * [--fields=] * : Limit the output to specific row fields. * * [--field=] * : Output a specific field for each row. * * [--format=] * : Render output in a specific format. * --- * default: table * options: * - table * - json * - csv * - yaml * - count * --- * * [--verbose] * : Log useful information to STDOUT. * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each installation: * * * version_path - Path to the version.php file. * * version - WordPress version. * * depth - Directory depth at which the installation was found. * * alias - WP-CLI alias, if one is registered. * * These fields are optionally available: * * * wp_path - Path that can be passed to `--path=` global parameter. * * db_host - Host name for the database. * * db_user - User name for the database. * * db_name - Database name for the database. * * ## EXAMPLES * * # Find WordPress installations. * $ wp find ./ * +--------------------------------------+---------------------+-------+--------+ * | version_path | version | depth | alias | * +--------------------------------------+---------------------+-------+--------+ * | /Users/wpcli/wp-includes/version.php | 4.8-alpha-39357-src | 2 | @wpcli | * +--------------------------------------+---------------------+-------+--------+ * * @when before_wp_load */ public function __invoke( $args, $assoc_args ) { list( $path ) = $args; $this->base_path = realpath( $path ); if ( ! $this->base_path ) { WP_CLI::error( 'Invalid path specified.' ); } $this->skip_ignored_paths = Utils\get_flag_value( $assoc_args, 'skip-ignored-paths' ); if ( ! empty( $assoc_args['include_ignored_paths'] ) ) { $this->ignored_paths = array_merge( $this->ignored_paths, explode( ',', $assoc_args['include_ignored_paths'] ) ); } $this->max_depth = Utils\get_flag_value( $assoc_args, 'max_depth', false ); $this->verbose = Utils\get_flag_value( $assoc_args, 'verbose' ); $aliases = WP_CLI::get_runner()->aliases; foreach ( $aliases as $alias => $target ) { if ( empty( $target['path'] ) ) { continue; } $this->resolved_aliases[ rtrim( $target['path'], '/' ) ] = $alias; } $fields = [ 'version_path', 'version', 'depth', 'alias' ]; if ( ! empty( $assoc_args['fields'] ) ) { $fields = explode( ',', $assoc_args['fields'] ); } $this->start_time = microtime( true ); $this->log( "Searching for WordPress installations in '{$path}'" ); $this->recurse_directory( $this->base_path ); $this->log( "Finished search for WordPress installations in '{$path}'" ); $formatter = new Formatter( $assoc_args, $fields ); $formatter->display_items( $this->found_wp ); } private function recurse_directory( $path ) { // Assume this symlink will be traversed from its true direction if ( is_link( $path ) ) { return; } // Provide consistent trailing slashes to all paths $path = rtrim( $path, '/' ) . '/'; // Don't recurse directories that probably don't have a WordPress installation. if ( ! $this->skip_ignored_paths ) { // Assume base path doesn't need comparison $compared_path = preg_replace( '#^' . preg_quote( $this->base_path, '#' ) . '#', '', $path ); // Ignore all hidden system directories $bits = explode( '/', trim( $compared_path, '/' ) ); $current_dir = array_pop( $bits ); if ( $current_dir && '.' === $current_dir[0] ) { $this->log( "Matched ignored path. Skipping recursion into '{$path}'" ); return; } foreach ( $this->ignored_paths as $ignored_path ) { if ( false !== stripos( $compared_path, $ignored_path ) ) { $this->log( "Matched ignored path. Skipping recursion into '{$path}'" ); return; } } } // This looks like a wp-includes directory, so check if it has a // version.php file. if ( DIRECTORY_SEPARATOR . 'wp-includes/' === substr( $path, -13 ) && file_exists( $path . 'version.php' ) ) { $version_path = $path . 'version.php'; $wp_path = substr( $path, 0, -13 ); $alias = isset( $this->resolved_aliases[ $wp_path ] ) ? $this->resolved_aliases[ $wp_path ] : ''; $wp_path = rtrim( $wp_path, '/' ) . '/'; $this->found_wp[ $version_path ] = [ 'version_path' => $version_path, 'version' => self::get_wp_version( $version_path ), 'wp_path' => rtrim( $wp_path, '/' ) . '/', 'depth' => $this->current_depth - 1, 'alias' => $alias, 'db_host' => '', 'db_name' => '', 'db_user' => '', ]; $config_path = self::get_wp_config_path( $wp_path ); if ( $config_path ) { try { $transformer = new WPConfigTransformer( $config_path ); foreach ( [ 'db_host', 'db_name', 'db_user' ] as $constant ) { $value = $transformer->get_value( 'constant', strtoupper( $constant ) ); // Clean up strings. $first = substr( $value, 0, 1 ); $last = substr( $value, -1 ); $both = array_unique( [ $first, $last ] ); if ( in_array( $both, [ [ '"' ], [ "'" ] ], true ) ) { $value = substr( $value, 1, -1 ); } $this->found_wp[ $version_path ][ $constant ] = $value; } } catch ( Exception $exception ) { $this->log( "Could not process the 'wp-config.php' transformation: {$exception->getMessage()}" ); } } $this->log( "Found WordPress installation at '{$version_path}'" ); return; } // Ensure we haven't exceeded our max recursion depth if ( false !== $this->max_depth && $this->current_depth > $this->max_depth ) { $this->log( "Exceeded max depth. Skipping recursion into '{$path}'" ); return; } // Check all files and directories of this path to recurse // into subdirectories. try { $iterator = new RecursiveDirectoryIterator( $path, FilesystemIterator::SKIP_DOTS ); } catch ( Exception $e ) { $this->log( "Exception thrown '{$e->getMessage()}'. Skipping recursion into '{$path}'" ); return; } $this->log( "Recursing into '{$path}'" ); foreach ( $iterator as $file_info ) { if ( $file_info->isDir() ) { ++$this->current_depth; $this->recurse_directory( $file_info->getPathname() ); --$this->current_depth; } } } /** * Get the WordPress version for the installation, without executing the file. */ private static function get_wp_version( $path ) { $contents = file_get_contents( $path ); preg_match( '#\$wp_version\s?=\s?[\'"]([^\'"]+)[\'"]#', $contents, $matches ); return ! empty( $matches[1] ) ? $matches[1] : ''; } /** * Get the wp-config.php path for the installation. * * Adapted from WP_CLI\Utils\locate_wp_config() */ private static function get_wp_config_path( $installation_dir ) { $path = false; if ( file_exists( $installation_dir . 'wp-config.php' ) ) { $path = $installation_dir . 'wp-config.php'; } elseif ( file_exists( $installation_dir . '../wp-config.php' ) && ! file_exists( $installation_dir . '/../wp-settings.php' ) ) { $path = $installation_dir . '../wp-config.php'; } if ( $path ) { $path = realpath( $path ); } return $path; } /** * Log informational message to STDOUT depending on verbosity. */ private function log( $message ) { if ( $this->verbose ) { $elapsed_time = microtime( true ) - $this->start_time; WP_CLI::log( sprintf( '[%s] %s', self::format_log_timestamp( $elapsed_time ), $message ) ); } } /** * Format a log timestamp into something human-readable. * * @param integer $s Log time in seconds * @return string */ private static function format_log_timestamp( $s ) { $h = floor( $s / 3600 ); $s -= $h * 3600; $m = floor( $s / 60 ); $s -= $m * 60; return $h . ':' . sprintf( '%02d', $m ) . ':' . sprintf( '%02d', $s ); } }