> Definitions keyed by stage id. Only registered stages carry fields. */ public static function all(): array { /** * Filters the file stages. Each entry is keyed by a stage id * (lowercase letters, digits and underscores) and carries: * * - `label` (string), shown in the wizard and the log. * - `base_dir` (callable returning the absolute folder). * - `writer` (optional callable receiving the folder and returning a FileWriter). * - `refusal` (optional callable receiving a relative path, returning why the stage's writer would refuse it or null). * - `protected` (optional callable receiving an absolute path, true for files the stage must never serve or sweep). * - `exclusions` (optional list of patterns the stage never carries). * * @param array> $stages Empty until a callback adds some. */ $registered = apply_filters( 'shiftora_file_stages', [] ); $stages = []; if ( is_array( $registered ) ) { foreach ( $registered as $key => $definition ) { if ( self::is_valid( $key, $definition ) ) { $stages[ $key ] = $definition; } } } $stages[ self::UPLOADS ] = []; return $stages; } /** @return string[] */ public static function keys(): array { return array_keys( self::all() ); } public static function has( string $key ): bool { return isset( self::all()[ $key ] ); } public static function label( string $key ): string { if ( self::UPLOADS === $key ) { return __( 'Uploads', 'shiftora' ); } return (string) ( self::all()[ $key ]['label'] ?? $key ); } /** * Absolute folder of a stage, no trailing separator. Empty for an unknown stage or one WordPress cannot place. */ public static function base_dir( string $key ): string { $stages = self::all(); if ( ! isset( $stages[ $key ] ) ) { return ''; } if ( self::UPLOADS === $key ) { $uploads = wp_upload_dir(); return rtrim( (string) ( $uploads['basedir'] ?? '' ), '/\\' ); } return rtrim( (string) ( $stages[ $key ]['base_dir'] )(), '/\\' ); } /** * What lands this stage's files. Anything without a writer of its own gets FileImporter, which only writes to uploads. */ public static function writer( string $key, string $base_dir ): FileWriter { $writer = self::all()[ $key ]['writer'] ?? null; if ( is_callable( $writer ) ) { $made = $writer( $base_dir ); if ( $made instanceof FileWriter ) { return $made; } } return new FileImporter( $base_dir ); } /** Reads a stage's folder, never serving what the stage marks as protected. */ public static function exporter( string $key, string $base_dir ): FileExporter { return new FileExporter( $base_dir, static fn( string $path ): bool => self::is_protected( $key, $path ) ); } /** * Why a path in this stage would be refused, or null. A stage with a writer of its own says so through * its `refusal` callable, and without one that writer is left to police itself. */ public static function refusal( string $key, string $relative_path ): ?string { $stages = self::all(); if ( ! isset( $stages[ $key ] ) ) { return null; } if ( is_callable( $stages[ $key ]['refusal'] ?? null ) ) { $why = $stages[ $key ]['refusal']( $relative_path ); return is_string( $why ) ? $why : null; } if ( is_callable( $stages[ $key ]['writer'] ?? null ) ) { return null; } return UploadsWriteGuard::refused_name( $relative_path ); } /** * Whether a stage declares an absolute path off limits to serve and sweep. */ public static function is_protected( string $key, string $absolute_path ): bool { $guard = self::all()[ $key ]['protected'] ?? null; return is_callable( $guard ) && (bool) $guard( $absolute_path ); } /** * @return string[] Patterns the stage never carries. */ public static function exclusions( string $key ): array { if ( self::UPLOADS === $key ) { return ExclusionMatcher::defaults_for_stage( self::UPLOADS ); } $patterns = self::all()[ $key ]['exclusions'] ?? []; return is_array( $patterns ) ? array_values( array_map( 'strval', $patterns ) ) : []; } private static function is_valid( mixed $key, mixed $definition ): bool { return is_string( $key ) && 1 === preg_match( '/^[a-z][a-z0-9_]{0,31}$/', $key ) && ! in_array( $key, self::RESERVED, true ) && is_array( $definition ) && is_string( $definition['label'] ?? null ) && is_callable( $definition['base_dir'] ?? null ); } }