$ini ini values, or empty to read them from PHP. * @param int $content_length CONTENT_LENGTH, or 0 when absent. */ public function __construct( private readonly array $ini = [], private readonly int $content_length = 0 ) {} /** Reads the live PHP settings. */ public static function from_php(): self { return new self( [ 'upload_max_filesize' => (string) ini_get( 'upload_max_filesize' ), 'post_max_size' => (string) ini_get( 'post_max_size' ), ], ServerVars::content_length() ); } /** upload_max_filesize in bytes, 0 when unlimited. */ public function upload_max_filesize(): int { return $this->parse( (string) ( $this->ini['upload_max_filesize'] ?? '' ) ); } /** post_max_size in bytes, 0 when unlimited. */ public function post_max_size(): int { return $this->parse( (string) ( $this->ini['post_max_size'] ?? '' ) ); } /** The ceiling that applies to one request, 0 when neither binds. */ public function effective(): int { $limits = array_filter( [ $this->upload_max_filesize(), $this->post_max_size() ] ); return [] === $limits ? 0 : min( $limits ); } /** Whether either setting is in force. */ public function caps(): bool { return $this->effective() > 0; } /** The binding setting, or null when neither is. */ public function binding_setting(): ?string { $effective = $this->effective(); if ( $effective <= 0 ) { return null; } foreach ( [ 'upload_max_filesize' => $this->upload_max_filesize(), 'post_max_size' => $this->post_max_size(), ] as $setting => $value ) { if ( $value === $effective ) { return $setting; } } return null; } /** Whether this server cannot accept a file of this size. */ public function refuses( int $bytes ): bool { $effective = $this->effective(); return $effective > 0 && $bytes > $effective; } /** * Whether the request body was larger than post_max_size. PHP drops $_POST and * $_FILES then, so the endpoint would otherwise report no file was provided. */ public function body_exceeded(): bool { $post_max = $this->post_max_size(); return $post_max > 0 && $this->content_length > $post_max; } /** * The limits as a report for a screen or diagnostics payload. Facts only; the * caller decides how to word them. * * @return array{effective_bytes: int, binding_setting: ?string, upload_max_filesize: int, post_max_size: int, caps: bool, human: string, summary: string} */ public function report(): array { $effective = $this->effective(); $binding = $this->binding_setting(); $human = $this->caps() ? sprintf( /* translators: 1: human-readable size, e.g. "8 MB", 2: the PHP setting that is binding, e.g. "post_max_size" */ __( '%1$s (%2$s)', 'shiftora' ), size_format( $effective ), (string) $binding ) : ''; return [ 'effective_bytes' => $effective, 'binding_setting' => $binding, 'upload_max_filesize' => $this->upload_max_filesize(), 'post_max_size' => $this->post_max_size(), 'caps' => $this->caps(), 'human' => $human, 'summary' => $this->caps() ? sprintf( /* translators: 1: human-readable size, e.g. "8 MB", 2: the PHP setting that is binding, e.g. "post_max_size" */ __( 'This server accepts up to %1$s per request, set by %2$s in php.ini.', 'shiftora' ), size_format( $effective ), (string) $binding ) : __( 'This server does not impose an upload size limit.', 'shiftora' ), ]; } /** * '64M' or '2097152' to bytes. Parsed here instead of wp_convert_hr_to_bytes() * so an unrecognized suffix counts as unlimited rather than casting leading digits. */ private function parse( string $value ): int { $value = trim( $value ); if ( '' === $value ) { return 0; } $unit = strtoupper( substr( $value, -1 ) ); $digits = trim( substr( $value, 0, -1 ) ); $multipliers = [ 'K' => 1024, 'M' => 1024 ** 2, 'G' => 1024 ** 3, 'T' => 1024 ** 4, 'P' => 1024 ** 5, ]; if ( isset( $multipliers[ $unit ] ) && is_numeric( $digits ) ) { return (int) ( (float) $digits * $multipliers[ $unit ] ); } if ( is_numeric( $value ) ) { return max( 0, (int) $value ); } return 0; } }