',
esc_attr( $collapse_id ),
esc_attr( $body_id ),
esc_attr__( 'Collapse or expand the section', 'acez-seo' )
);
}
// The card header and the body opener are printed separately, each with
// a LITERAL format string. The body id used to travel as a ready-made
// ` id="…"` fragment in a variable, and a half-attribute like that can
// never be escaped at the point of output — esc_attr() escapes a value,
// not `id="x"` — which is exactly why that argument needed a suppression.
printf(
'
';
}
}
public function card_close(): void
{
echo '
';
}
public function form_open( string $tab ): void
{
printf( '';
}
/**
* A text field holding an attachment ID, with a "choose from Media
* Library" button (wp.media modal), a clear button, and a thumbnail
* preview. $field_id must be unique per page ($name may repeat across
* the separate per-language forms).
*/
public function media_picker_field( string $name, string $field_id, string $label, int $value, string $help_key = '' ): void
{
$preview = $value > 0 ? wp_get_attachment_image( $value, array( 80, 80 ) ) : '';
printf(
'
%3$s
' .
' ' .
' ' .
'' .
'
%9$s
' .
'
',
esc_attr( $field_id ),
esc_html( $label ),
'' !== $help_key ? wp_kses( HelpIcon::render( $help_key ), KsesTags::help_icon() ) : '',
esc_attr( $name ),
$value > 0 ? esc_attr( (string) $value ) : '',
esc_attr__( 'Choose logo', 'acez-seo' ),
esc_html__( 'Choose from library', 'acez-seo' ),
esc_html__( 'Clear', 'acez-seo' ),
wp_kses( $preview, KsesTags::attachment_image() )
);
}
/**
* @param string $pattern Optional HTML5 `pattern` character class (without the
* surrounding brackets or quantifier). Use it where the
* server-side sanitizer would otherwise DROP the rejected
* characters silently — the browser then refuses the
* submit with a visible message instead of the user
* finding an empty field after saving.
* @param string $title Message the browser shows when $pattern fails.
*/
public function text_field( string $name, string $label, string $value, string $placeholder = '', string $help_key = '', string $pattern = '', string $title = '', string $ai_field = '' ): void
{
printf(
'
',
esc_attr( $name ),
esc_html( $label ),
esc_textarea( $value ),
esc_attr( $placeholder ),
'' !== $help_key ? wp_kses( HelpIcon::render( $help_key ), KsesTags::help_icon() ) : '',
'' !== $ai_field ? wp_kses( self::ai_button( $ai_field, $name ), KsesTags::ai_button() ) : ''
);
}
/**
* The "generate with AI" trigger rendered under a settings field.
*
* Deliberately reuses the `.acez-seo-ai-generate` contract already
* implemented in assets/js/meta-box-ai.js (data-field = what to generate,
* data-target = the element id to fill) instead of shipping a second
* script for settings screens — the handler is field-agnostic, so the
* caller only has to enqueue that script and localize `AcezSeoAi`.
*
* @param string $ai_field Field key sent to the REST endpoint.
* @param string $target Element id the generated text is written into.
*/
private static function ai_button( string $ai_field, string $target ): string
{
return sprintf(
'',
esc_attr( $ai_field ),
esc_attr( $target ),
esc_html__( 'Generate with AI', 'acez-seo' )
);
}
public function checkbox_field( string $name, string $label, bool $checked, string $help_key = '' ): void
{
printf(
'