|
Server : LiteSpeed System : Linux host 5.15.0-161-generic #171-Ubuntu SMP Sat Oct 11 08:17:01 UTC 2025 x86_64 User : idnco5810 ( 1093) PHP Version : 8.2.29 Disable Function : NONE Directory : /home/idn98.co/public_html/wp-content/plugins/amp/includes/sanitizers/ |
Upload File : |
<?php
/**
* Class AMP_Form_Sanitizer.
*
* @package AMP
* @since 0.7
*/
use AmpProject\AmpWP\ValidationExemption;
use AmpProject\DevMode;
use AmpProject\Dom\Document\Filter\MustacheScriptTemplates;
use AmpProject\Dom\Element;
use AmpProject\Html\Attribute;
/**
* Class AMP_Form_Sanitizer
*
* Strips and corrects attributes in forms.
*
* @since 0.7
* @internal
*/
class AMP_Form_Sanitizer extends AMP_Base_Sanitizer {
/**
* Validation error code emitted when there is a POST form with action-xhr but only native POST forms are used.
*
* @var string
*/
const POST_FORM_HAS_ACTION_XHR_WHEN_NATIVE_USED = 'POST_FORM_HAS_ACTION_XHR_WHEN_NATIVE_USED';
/**
* Placeholder for default args, to be set in child classes.
*
* @var array
*/
protected $DEFAULT_ARGS = [
'native_post_forms_allowed' => 'never',
];
/**
* Array of flags used to control sanitization.
*
* @var array {
* @type string $native_post_forms_allowed Whether to convert POST forms to use action-xhr instead. Can be 'never', 'always', or 'conditionally' (if more than comments form).
* }
*/
protected $args;
/**
* Tag.
*
* @var string HTML <form> tag to identify and process.
*
* @since 0.7
*/
public static $tag = 'form';
/**
* Sanitize the <form> elements from the HTML contained in this instance's Dom\Document.
*
* @link https://www.ampproject.org/docs/reference/components/amp-form
* @since 0.7
*/
public function sanitize() {
$form_elements = $this->dom->getElementsByTagName( self::$tag );
if ( 0 === $form_elements->length ) {
return;
}
/** @var Element[] $post_form_elements */
$post_form_elements = [];
foreach ( $form_elements as $form_element ) {
if ( ! $form_element instanceof Element || DevMode::hasExemptionForNode( $form_element ) ) {
continue;
}
// Normalize the method.
$method = 'get';
if ( $form_element->getAttribute( Attribute::METHOD ) ) {
$method = strtolower( $form_element->getAttribute( Attribute::METHOD ) );
} else {
$form_element->setAttribute( Attribute::METHOD, $method );
}
if ( 'get' === $method ) {
$this->normalize_action_attribute( $form_element );
$this->normalize_target_attribute( $form_element );
} elseif ( 'post' === $method ) {
$post_form_elements[] = $form_element;
}
}
// Convert post forms to use action-xhr if post forms are never allowed or if native forms are conditionally
// allowed but the only form is the comments form which we are able to safely convert to use action-xhr.
if (
'never' === $this->args['native_post_forms_allowed']
||
(
'conditionally' === $this->args['native_post_forms_allowed']
&&
count( $post_form_elements ) === 1
&&
$this->is_comments_form_element( $post_form_elements[0] )
)
) {
foreach ( $post_form_elements as $post_form_element ) {
$this->normalize_target_attribute( $post_form_element );
$this->convert_post_form_to_action_xhr( $post_form_element );
}
} else {
foreach ( $post_form_elements as $post_form_element ) {
$this->handle_native_post_form( $post_form_element );
}
}
}
/**
* Convert post form to use action-xhr.
*
* @param Element $post_form_element Post form.
*/
protected function convert_post_form_to_action_xhr( Element $post_form_element ) {
$action_url = $this->normalize_action_attribute( $post_form_element );
$action_xhr = $post_form_element->getAttribute( Attribute::ACTION_XHR );
$post_form_element->removeAttribute( Attribute::ACTION );
if ( ! $action_xhr ) {
// Record that action was converted to action-xhr.
$action_url = add_query_arg( AMP_HTTP::ACTION_XHR_CONVERTED_QUERY_VAR, 1, $action_url );
$post_form_element->setAttribute( Attribute::ACTION_XHR, $action_url );
// Append success/error handlers if not found.
$this->ensure_response_message_elements( $post_form_element );
} elseif ( 'http://' === substr( $action_xhr, 0, 7 ) ) {
$post_form_element->setAttribute( Attribute::ACTION_XHR, substr( $action_xhr, 5 ) );
}
}
/**
* Handle native post form.
*
* If native post forms are used, then mark any POST forms as being unvalidated for AMP. Note that it is
* an all or nothing proposition with forms, where there cannot be some POST forms with [action] and
* others with [action-xhr]. The former is incompatible with the amp-form extension but the latter
* fundamentally depends on it. So it's one or the other.
*
* @param Element $post_form_element Post form.
*/
protected function handle_native_post_form( Element $post_form_element ) {
if ( $post_form_element->hasAttribute( Attribute::ACTION_XHR ) ) {
// @todo Consider rewriting action-xhr to action? Or include a shim which implements the amp-form functionality?
$this->remove_invalid_child(
$post_form_element,
[ 'code' => self::POST_FORM_HAS_ACTION_XHR_WHEN_NATIVE_USED ]
);
} else {
ValidationExemption::mark_node_as_px_verified( $post_form_element );
}
}
/**
* Normalize form target attribute.
*
* The target "indicates where to display the form response after submitting the form.
* The value must be _blank or _top". The _self and _parent values are treated
* as synonymous with _top, and anything else is treated like _blank.
*
* @param Element $form_element Form element.
*/
protected function normalize_target_attribute( Element $form_element ) {
$target = $form_element->getAttribute( Attribute::TARGET );
if ( '_top' !== $target ) {
if ( ! $target || in_array( $target, [ '_self', '_parent' ], true ) ) {
$form_element->setAttribute( Attribute::TARGET, '_top' );
} elseif ( '_blank' !== $target ) {
$form_element->setAttribute( Attribute::TARGET, '_blank' );
}
}
}
/**
* Normalize form action attribute.
*
* @param Element $form_element Form element.
* @return string Normalized action URL.
*/
protected function normalize_action_attribute( Element $form_element ) {
$action_url = $this->get_action_url( $form_element->getAttribute( Attribute::ACTION ) );
$form_element->setAttribute( Attribute::ACTION, $action_url );
return $action_url;
}
/**
* Determine whether the form is for the comments.
*
* @param Element $form_element Form element.
* @return bool Is comments form.
*/
protected function is_comments_form_element( Element $form_element ) {
return (
'commentform' === $form_element->getAttribute( Attribute::ID )
&&
'wp-comments-post.php' === basename( wp_parse_url( $form_element->getAttribute( Attribute::ACTION ), PHP_URL_PATH ) )
);
}
/**
* Get the action URL for the form element.
*
* @see amp_get_current_url()
* @see AMP_HTTP::intercept_post_request_redirect()
* @param string $action_url Action URL.
* @return string Action URL.
*/
protected function get_action_url( $action_url ) {
$parsed_home_url = wp_parse_url( home_url() );
/*
* In HTML, the default action is just the current URL that the page is served from.
* The action "specifies a server endpoint to handle the form input. The value must be an
* https URL and must not be a link to a CDN".
*/
if ( ! $action_url ) {
$action_url = '//' . $parsed_home_url['host'];
if ( isset( $parsed_home_url['port'] ) ) {
$action_url .= ':' . $parsed_home_url['port'];
}
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitization is done below.
$action_url .= wp_unslash( $_SERVER['REQUEST_URI'] );
}
return esc_url_raw( $action_url );
}
$parsed_url = wp_parse_url( $action_url );
if (
// Ignore a malformed URL - it will be later sanitized.
false === $parsed_url
||
// Ignore HTTPS URLs, because there is nothing left to do.
( isset( $parsed_url['scheme'] ) && 'https' === $parsed_url['scheme'] )
||
// Ignore protocol-relative URLs, because there is also nothing left to do.
( ! isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) )
) {
return $action_url;
}
// Make URL protocol relative.
$parsed_url['scheme'] = '//';
// Set an empty path if none is defined but there is a host.
if ( ! isset( $parsed_url['path'] ) && isset( $parsed_url['host'] ) ) {
$parsed_url['path'] = '';
}
if ( ! isset( $parsed_url['host'] ) ) {
$parsed_url['host'] = $parsed_home_url['host'];
}
if ( ! isset( $parsed_url['port'] ) && isset( $parsed_home_url['port'] ) ) {
$parsed_url['port'] = $parsed_home_url['port'];
}
if ( ! isset( $parsed_url['path'] ) ) {
// If there is action URL path, use the one from the request.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
$parsed_url['path'] = trailingslashit( wp_unslash( $_SERVER['REQUEST_URI'] ) );
} elseif ( '' !== $parsed_url['path'] && '/' !== $parsed_url['path'][0] ) {
// If the path is relative, append it to the current request path.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
$parsed_url['path'] = trailingslashit( wp_unslash( $_SERVER['REQUEST_URI'] ) ) . trailingslashit( $parsed_url['path'] );
}
// Rebuild the URL.
$action_url = $parsed_url['scheme'];
if ( isset( $parsed_url['user'] ) ) {
$action_url .= $parsed_url['user'];
if ( isset( $parsed_url['pass'] ) ) {
$action_url .= ':' . $parsed_url['pass'];
}
$action_url .= '@';
}
$action_url .= $parsed_url['host'];
if ( isset( $parsed_url['port'] ) ) {
$action_url .= ':' . $parsed_url['port'];
}
$action_url .= $parsed_url['path'];
if ( isset( $parsed_url['query'] ) ) {
$action_url .= '?' . $parsed_url['query'];
}
if ( isset( $parsed_url['fragment'] ) ) {
$action_url .= '#' . $parsed_url['fragment'];
}
return esc_url_raw( $action_url );
}
/**
* Ensure that the form has a submit-success and submit-error element templates.
*
* @link https://www.ampproject.org/docs/reference/components/amp-form#success/error-response-rendering
* @since 1.2
*
* @param DOMElement $form The form node to check.
*/
public function ensure_response_message_elements( $form ) {
$elements = [
'submit-error' => null,
'submit-success' => null,
'submitting' => null,
];
$templates = $this->dom->xpath->query( MustacheScriptTemplates::XPATH_MUSTACHE_TEMPLATE_ELEMENTS_QUERY, $form );
foreach ( $templates as $template ) {
$parent = $template->parentNode;
if ( $parent instanceof DOMElement ) {
foreach ( array_keys( $elements ) as $attribute ) {
if ( $parent->hasAttribute( $attribute ) ) {
$elements[ $attribute ] = $parent;
}
}
}
}
foreach ( $elements as $attribute => $element ) {
if ( $element ) {
continue;
}
$div = $this->dom->createElement( 'div' );
$template = $this->dom->createElement( 'template' );
$div->setAttribute( 'class', 'amp-wp-default-form-message' );
if ( 'submitting' === $attribute ) {
$p = $this->dom->createElement( 'p' );
$p->appendChild( $this->dom->createTextNode( __( 'Submitting…', 'amp' ) ) );
$template->appendChild( $p );
} else {
$p = $this->dom->createElement( 'p' );
$p->setAttribute( 'class', '{{#redirecting}}amp-wp-form-redirecting{{/redirecting}}' );
// phpcs:ignore WordPressVIPMinimum.Security.Mustache.OutputNotation -- Sanitization applied via amp-mustache.
$p->appendChild( $this->dom->createTextNode( '{{#message}}{{{message}}}{{/message}}' ) );
// Show generic message for HTTP success/failure.
$p->appendChild( $this->dom->createTextNode( '{{^message}}' ) );
if ( 'submit-error' === $attribute ) {
$p->appendChild( $this->dom->createTextNode( __( 'Your submission failed.', 'amp' ) ) );
/* translators: %1$s: HTTP status text, %2$s: HTTP status code */
$reason = sprintf( __( 'The server responded with %1$s (code %2$s).', 'amp' ), '{{status_text}}', '{{status_code}}' );
} else {
$p->appendChild( $this->dom->createTextNode( __( 'It appears your submission was successful.', 'amp' ) ) );
$reason = __( 'Even though the server responded OK, it is possible the submission was not processed.', 'amp' );
}
$reason .= ' ' . __( 'Please contact the developer of this form processor to improve this message.', 'amp' );
$p->appendChild( $this->dom->createTextNode( ' ' ) );
$small = $this->dom->createElement( 'small' );
$small->appendChild( $this->dom->createTextNode( $reason ) );
$small->appendChild( $this->dom->createTextNode( ' ' ) );
$link = $this->dom->createElement( 'a' );
$link->setAttribute( 'href', 'https://amp-wp.org/?p=5463' );
$link->setAttribute( 'target', '_blank' );
$link->setAttribute( 'rel', 'nofollow noreferrer noopener' );
$link->appendChild( $this->dom->createTextNode( __( 'Learn More', 'amp' ) ) );
$small->appendChild( $link );
$p->appendChild( $small );
$p->appendChild( $this->dom->createTextNode( '{{/message}}' ) );
$template->appendChild( $p );
}
$div->setAttribute( $attribute, '' );
$template->setAttribute( 'type', 'amp-mustache' );
$div->appendChild( $template );
$form->appendChild( $div );
}
}
}