*/ private $roots; /** * Directory queue file. * * @var string */ private $queue; /** * Output list file. * * @var string */ private $out; /** * Exclusion patterns. * * @var list */ private $patterns; /** * Constructor. * * @param list $roots Real root paths + archive prefixes. * @param string $queue Queue NDJSON path. * @param string $out Output NDJSON path. * @param list $patterns Exclusion patterns. */ public function __construct( array $roots, string $queue, string $out, array $patterns ) { $this->roots = $roots; $this->queue = $queue; $this->out = $out; $this->patterns = $patterns; } /** * Resolve live component roots. * * @param array $components Components (plugins/themes/uploads). * @return list */ public static function component_roots( array $components ): array { $cand = array(); if ( ! empty( $components['plugins'] ) ) { $cand[] = array( \BRVerify\Support\Working_Dir::plugins_root(), 'wp-content/plugins' ); } if ( ! empty( $components['themes'] ) ) { $cand[] = array( get_theme_root(), 'wp-content/themes' ); } if ( ! empty( $components['uploads'] ) ) { $upload = wp_upload_dir( null, false ); if ( ! empty( $upload['basedir'] ) ) { $cand[] = array( (string) $upload['basedir'], 'wp-content/uploads' ); } } $roots = array(); foreach ( $cand as $c ) { $real = is_dir( (string) $c[0] ) ? realpath( (string) $c[0] ) : false; if ( false !== $real ) { $roots[] = array( 'abs' => $real, 'rel' => $c[1], ); } } return $roots; } /** * Walk until done or the deadline. * * @param array $st Walker state (empty to start). * @param float $deadline microtime deadline. * @return array New state; 'done' => true when finished. * @throws State_Exception When a directory cannot be read. */ public function step( array $st, float $deadline ): array { $st = array_merge( array( 'seeded' => 0, 'q' => 0, 'pos' => 0, 'files' => 0, 'dirs' => 0, 'bytes' => 0, 'done' => 0, ), $st ); if ( ! empty( $st['done'] ) ) { return $st; } if ( empty( $st['seeded'] ) ) { $seed = array(); foreach ( $this->roots as $i => $root ) { $seed[] = array( 'r' => $i, 'd' => '', ); } State_Files::append_rows( $this->queue, $seed ); $st['seeded'] = 1; } $out_buf = ''; $q_buf = ''; $ops = 0; while ( true ) { $next = State_Files::read_rows( $this->queue, (int) $st['q'], 1 ); if ( empty( $next['rows'] ) ) { if ( '' !== $q_buf ) { // Newly queued directories are still buffered: flush and continue. State_Files::append( $this->queue, $q_buf ); $q_buf = ''; continue; } $st['done'] = 1; break; } $row = $next['rows'][0]; $ri = (int) ( $row['r'] ?? -1 ); $dir = (string) ( $row['d'] ?? '' ); if ( ! isset( $this->roots[ $ri ] ) ) { $st['q'] = $next['offset']; $st['pos'] = 0; continue; } $root = $this->roots[ $ri ]; $abs = $root['abs'] . ( '' === $dir ? '' : '/' . $dir ); $dh = @opendir( $abs ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged if ( false === $dh ) { throw new State_Exception( esc_html( State_Files::io_message( __( 'Unable to read directory', '007aj-backup-restore-verifier' ), $abs ) ) ); } $pos = 0; $skip = (int) $st['pos']; $stopped = false; while ( false !== ( $name = readdir( $dh ) ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition ++$pos; if ( $pos <= $skip ) { continue; } if ( '.' !== $name && '..' !== $name ) { $inside = '' === $dir ? $name : $dir . '/' . $name; $rel = $root['rel'] . '/' . $inside; $full = $abs . '/' . $name; $ls = @lstat( $full ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged if ( is_array( $ls ) ) { $type = $ls['mode'] & 0170000; if ( 0040000 === $type ) { if ( ! Working_Dir::is_excluded( $rel, $this->patterns ) && ! Working_Dir::is_excluded( $inside, $this->patterns ) ) { $q_buf .= State_Files::encode_row( array( 'r' => $ri, 'd' => $inside, ) ) . "\n"; ++$st['dirs']; } } elseif ( 0100000 === $type ) { if ( ! Working_Dir::is_excluded( $rel, $this->patterns ) && ! Working_Dir::is_excluded( $inside, $this->patterns ) ) { $out_buf .= State_Files::encode_row( array( 'r' => $ri, 'p' => $inside, 's' => (int) $ls['size'], 'm' => (int) $ls['mtime'], ) ) . "\n"; ++$st['files']; $st['bytes'] += (int) $ls['size']; } } // Symlinks and special files are never followed or archived. } } if ( 0 === ( ++$ops & 127 ) ) { if ( strlen( $out_buf ) > 262144 ) { State_Files::append( $this->out, $out_buf ); $out_buf = ''; } if ( strlen( $q_buf ) > 262144 ) { State_Files::append( $this->queue, $q_buf ); $q_buf = ''; } if ( microtime( true ) >= $deadline ) { $st['pos'] = $pos; $stopped = true; break; } } } closedir( $dh ); if ( $stopped ) { break; } $st['q'] = $next['offset']; $st['pos'] = 0; if ( microtime( true ) >= $deadline ) { break; } } State_Files::append( $this->out, $out_buf ); State_Files::append( $this->queue, $q_buf ); return $st; } }