Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 70 additions & 4 deletions includes/class-static-site-importer-font-materializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,15 +189,29 @@ private static function producer_faces( array $plan, array $resolved_plan, array
return new WP_Error( 'static_site_importer_font_materialization_producer_receipts_invalid', '', $diagnostics );
}
$normalized = array();
foreach ( $faces as $face ) {
foreach ( $faces as $face_index => $face ) {
if ( ! is_array( $face ) || 'declared' !== ( $face['state'] ?? '' ) || ! is_string( $face['id'] ?? null ) || ! isset( $imports[ $face['import_id'] ?? '' ] ) || ! isset( $receipts[ $face['id'] ] ) || ( $face['receipt_id'] ?? null ) !== $receipts[ $face['id'] ] || ! is_array( $face['axes'] ?? null ) || ! is_array( $face['unicode_ranges'] ?? null ) ) {
$diagnostics[] = self::diagnostic( 'producer_face_or_receipt_invalid' );
$diagnostics[] = self::diagnostic_with_detail(
'producer_face_or_receipt_invalid',
array(
'face_index' => $face_index,
'face_id' => is_array( $face ) && is_string( $face['id'] ?? null ) ? $face['id'] : null,
'invalid_fields' => self::invalid_producer_face_fields( $face, $imports, $receipts ),
)
);
return new WP_Error( 'static_site_importer_font_materialization_producer_face_invalid', '', $diagnostics );
}
$family = trim( (string) ( $face['family'] ?? '' ) );
$style = (string) ( $face['style'] ?? 'normal' );
if ( '' === $family || ! in_array( $style, array( 'normal', 'italic' ), true ) || ! self::valid_weight( $face['weight'] ?? null ) || ! self::valid_axes( $face['axes'] ) ) {
$diagnostics[] = self::diagnostic( 'producer_face_invalid' );
$diagnostics[] = self::diagnostic_with_detail(
'producer_face_invalid',
array(
'face_index' => $face_index,
'face_id' => $face['id'],
'invalid_fields' => self::invalid_producer_face_value_fields( $face ),
)
);
return new WP_Error( 'static_site_importer_font_materialization_producer_face_invalid', '', $diagnostics );
}
$face['family'] = $family;
Expand Down Expand Up @@ -267,13 +281,65 @@ private static function valid_weight( mixed $weight ): bool {

private static function valid_axes( array $axes ): bool {
foreach ( $axes as $axis => $value ) {
if ( ! is_string( $axis ) || ! preg_match( '/^[A-Za-z0-9]{4}$/', $axis ) || ! self::valid_weight( $value ) ) {
if ( ! is_string( $axis ) || ! preg_match( '/^[A-Za-z0-9]{4}$/', $axis ) || ! self::valid_axis( $axis, $value ) ) {
return false;
}
}
return true;
}

private static function valid_axis( string $axis, mixed $value ): bool {
if ( 'ital' === $axis && is_array( $value ) && 'static' === ( $value['kind'] ?? '' ) && is_int( $value['value'] ?? null ) ) {
return in_array( $value['value'], array( 0, 1 ), true );
}
return self::valid_weight( $value );
}

/** @param array<string,array<string,mixed>> $imports @param array<string,string> $receipts @return array<int,string> */
private static function invalid_producer_face_fields( mixed $face, array $imports, array $receipts ): array {
if ( ! is_array( $face ) ) {
return array( 'face' );
}
$invalid = array();
if ( 'declared' !== ( $face['state'] ?? '' ) ) {
$invalid[] = 'state';
}
if ( ! is_string( $face['id'] ?? null ) ) {
$invalid[] = 'id';
}
if ( ! isset( $imports[ $face['import_id'] ?? '' ] ) ) {
$invalid[] = 'import_id';
}
if ( ! isset( $receipts[ $face['id'] ?? '' ] ) || ( $face['receipt_id'] ?? null ) !== ( $receipts[ $face['id'] ?? '' ] ?? null ) ) {
$invalid[] = 'receipt_id';
}
if ( ! is_array( $face['axes'] ?? null ) ) {
$invalid[] = 'axes';
}
if ( ! is_array( $face['unicode_ranges'] ?? null ) ) {
$invalid[] = 'unicode_ranges';
}
return $invalid;
}

/** @param array<string,mixed> $face @return array<int,string> */
private static function invalid_producer_face_value_fields( array $face ): array {
$invalid = array();
if ( '' === trim( (string) ( $face['family'] ?? '' ) ) ) {
$invalid[] = 'family';
}
if ( ! in_array( (string) ( $face['style'] ?? 'normal' ), array( 'normal', 'italic' ), true ) ) {
$invalid[] = 'style';
}
if ( ! self::valid_weight( $face['weight'] ?? null ) ) {
$invalid[] = 'weight';
}
if ( ! self::valid_axes( $face['axes'] ?? array() ) ) {
$invalid[] = 'axes';
}
return $invalid;
}

/** @param array{faces:array<int,array<string,mixed>>,imports:array<string,array<string,mixed>>,receipts:array<string,string>} $producer @param array<int,array<string,string>> $diagnostics */
private static function materialize_producer_faces( array $producer, array &$diagnostics ) {
$writes = array();
Expand Down
11 changes: 11 additions & 0 deletions tests/smoke-webfont-producer-consumer.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,15 @@ function wp_remote_retrieve_body( $response ): string { return (string) ( $respo
$local_overlay_without_diagnostics = Static_Site_Importer_Font_Materializer::prepare_overlay( $local_plan, array( 'writes' => array( array( 'target_path' => 'functions.php', 'payload' => array( 'encoding' => 'utf8', 'data' => '<?php' ) ) ) ) );
$assert( ! is_wp_error( $local_overlay_without_diagnostics ) && array() === $local_overlay_without_diagnostics['writes'] && $request_count === count( $GLOBALS['ssi_webfont_requests'] ), 'an authoritative zero-face contract without diagnostics still suppresses legacy Google requests' );

$italic_axis_plan = $producer_plan;
$italic_axis_plan['webfont_contract']['faces'][0]['axes']['ital'] = array( 'kind' => 'static', 'value' => 0 );
$italic_axis_overlay = Static_Site_Importer_Font_Materializer::prepare_overlay( $italic_axis_plan, array( 'writes' => array( array( 'target_path' => 'functions.php', 'payload' => array( 'encoding' => 'utf8', 'data' => '<?php' ) ) ) ) );
$assert( ! is_wp_error( $italic_axis_overlay ), 'declared italic producer faces accept the canonical ital=0 axis value: ' . ( is_wp_error( $italic_axis_overlay ) ? $italic_axis_overlay->get_error_code() : '' ) );

$malformed_face_plan = $producer_plan;
$malformed_face_plan['webfont_contract']['faces'][0]['family'] = '';
$malformed_face_overlay = Static_Site_Importer_Font_Materializer::prepare_overlay( $malformed_face_plan, array( 'writes' => array( array( 'target_path' => 'functions.php', 'payload' => array( 'encoding' => 'utf8', 'data' => '<?php' ) ) ) ) );
$malformed_face_diagnostic = is_wp_error( $malformed_face_overlay ) ? $malformed_face_overlay->get_error_data()[0] ?? array() : array();
$assert( is_wp_error( $malformed_face_overlay ) && 'static_site_importer_font_materialization_producer_face_invalid' === $malformed_face_overlay->get_error_code() && 'producer_face_invalid' === ( $malformed_face_diagnostic['reason'] ?? '' ) && array( 'family' ) === ( $malformed_face_diagnostic['details']['invalid_fields'] ?? null ) && is_int( $malformed_face_diagnostic['details']['face_index'] ?? null ) && is_string( $malformed_face_diagnostic['details']['face_id'] ?? null ), 'malformed declared producer faces fail closed with the rejected face identity and fields' );

echo "Webfont producer-consumer smoke passed.\n";
Loading