get_results( $wpdb->prepare( "SELECT post_id, meta_value FROM %i WHERE meta_key = '_wp_attached_file' AND meta_value NOT LIKE %s", $wpdb->postmeta, '%/%' ), ARRAY_A ); } else { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- one folder of a bounded check the administrator starts; it must read the current rows. $rows = $wpdb->get_results( $wpdb->prepare( "SELECT post_id, meta_value FROM %i WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s", $wpdb->postmeta, $wpdb->esc_like( $folder . '/' ) . '%' ), ARRAY_A ); } $names = array(); foreach ( (array) $rows as $row ) { $file = (string) $row['meta_value']; if ( ( '' === $folder ? '' : $folder ) !== ( str_contains( $file, '/' ) ? dirname( $file ) : '' ) ) { continue; } $names[ strtolower( wp_basename( $file ) ) ] = true; $meta = wp_get_attachment_metadata( (int) $row['post_id'] ); if ( is_array( $meta ) ) { foreach ( (array) ( $meta['sizes'] ?? array() ) as $size ) { if ( is_array( $size ) && is_string( $size['file'] ?? null ) ) { $names[ strtolower( wp_basename( $size['file'] ) ) ] = true; } } if ( is_string( $meta['original_image'] ?? null ) && '' !== $meta['original_image'] ) { $names[ strtolower( wp_basename( $meta['original_image'] ) ) ] = true; } } foreach ( (array) get_post_meta( (int) $row['post_id'], '_wp_attachment_backup_sizes', true ) as $backup ) { if ( is_array( $backup ) && is_string( $backup['file'] ?? null ) ) { $names[ strtolower( wp_basename( $backup['file'] ) ) ] = true; } } } return $names; } /** * Start a check, or continue the saved one, for about eight seconds. * * @param bool $start Start a new check. * @return array The saved check. * @throws \RuntimeException When the check may not run now. */ public static function step( bool $start ): array { $lock = Reviewed_Store::enter(); try { if ( ! current_user_can( 'manage_options' ) || ! current_user_can( 'upload_files' ) ) { throw new \RuntimeException( 'unknown_files_access' ); } $token = Repository::acquire( 'unknown_files', 300 ); if ( ! $token ) { throw new \RuntimeException( 'unknown_files_busy' ); } try { $base = self::base(); if ( '' === $base ) { throw new \RuntimeException( 'unknown_files_storage' ); } $s = self::state(); if ( $start ) { list( $checked, $other ) = self::folders( $base ); $s = array( 'v' => 1, 'started' => time(), 'updated' => time(), 'folders' => $checked, 'other' => $other, 'cursor' => 0, 'offset' => 0, 'files' => 0, 'listed' => array(), 'more' => 0, 'bytes' => 0, 'status' => 'more', ); } elseif ( ! $s || 'more' !== ( $s['status'] ?? '' ) ) { throw new \RuntimeException( 'unknown_files_stale' ); } $until = microtime( true ) + 8; while ( 'more' === $s['status'] && microtime( true ) < $until ) { if ( $s['cursor'] >= count( $s['folders'] ) ) { $s['status'] = 'done'; break; } $folder = (string) $s['folders'][ $s['cursor'] ]; $dir = '' === $folder ? $base : $base . '/' . $folder; $names = is_dir( $dir ) ? array_values( array_filter( (array) scandir( $dir ), static fn( $n ) => '.' !== $n[0] && is_file( $dir . '/' . $n ) ) ) : array(); $known = self::known( $folder ); foreach ( array_slice( $names, (int) $s['offset'], self::SLICE ) as $name ) { ++$s['files']; if ( isset( $known[ strtolower( $name ) ] ) || in_array( strtolower( $name ), self::IGNORED, true ) ) { continue; } $bytes = (int) filesize( $dir . '/' . $name ); $s['bytes'] += $bytes; if ( count( $s['listed'] ) < self::MAX_LISTED ) { $s['listed'][] = array( ( '' === $folder ? '' : $folder . '/' ) . $name, $bytes, (int) filemtime( $dir . '/' . $name ) ); } else { ++$s['more']; } } if ( (int) $s['offset'] + self::SLICE < count( $names ) ) { $s['offset'] += self::SLICE; } else { ++$s['cursor']; $s['offset'] = 0; } if ( ! Repository::owns( $token, 'unknown_files' ) ) { throw new \RuntimeException( 'unknown_files_busy' ); } } $s['updated'] = time(); if ( ! Reviewed_Store::update_option( self::OPTION, $s, false ) && self::state() !== $s ) { throw new \RuntimeException( 'unknown_files_storage' ); } return $s; } finally { Repository::release( $token, 'unknown_files' ); } } finally { Reviewed_Store::leave( $lock ); } } /** * Words for the check's error codes. * * @param string $code The code. * @return string */ public static function human_error( string $code ): string { $messages = array( 'unknown_files_access' => __( 'Only an administrator who may upload files can check the uploads folder.', 'affstackdev-image-manager' ), 'unknown_files_busy' => __( 'Another check of the uploads folder is running. Try again in a few minutes.', 'affstackdev-image-manager' ), 'unknown_files_storage' => __( 'The uploads folder could not be read, or the check could not be saved.', 'affstackdev-image-manager' ), 'unknown_files_stale' => __( 'There is no check to continue. Start a new one.', 'affstackdev-image-manager' ), ); return $messages[ $code ] ?? ''; } }