-
Notifications
You must be signed in to change notification settings - Fork 385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for amp-bind #895
Changes from all commits
d266233
cbe9d53
1e1c023
567f1d5
c85d72b
4338252
40d48e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,6 +53,14 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer { | |
*/ | ||
protected $layout_allowed_attributes; | ||
|
||
/** | ||
* Mapping of alternative names back to their primary names. | ||
* | ||
* @since 0.7 | ||
* @var array | ||
*/ | ||
protected $rev_alternate_attr_name_lookup = array(); | ||
|
||
/** | ||
* Stack. | ||
* | ||
|
@@ -91,16 +99,26 @@ public function __construct( $dom, $args = array() ) { | |
'amp_allowed_tags' => AMP_Allowed_Tags_Generated::get_allowed_tags(), | ||
'amp_globally_allowed_attributes' => AMP_Allowed_Tags_Generated::get_allowed_attributes(), | ||
'amp_layout_allowed_attributes' => AMP_Allowed_Tags_Generated::get_layout_attributes(), | ||
'amp_bind_placeholder_prefix' => AMP_DOM_Utils::get_amp_bind_placeholder_prefix(), | ||
); | ||
|
||
parent::__construct( $dom, $args ); | ||
|
||
/** | ||
* Prepare whitelists | ||
*/ | ||
$this->allowed_tags = $this->args['amp_allowed_tags']; | ||
$this->globally_allowed_attributes = $this->args['amp_globally_allowed_attributes']; | ||
$this->layout_allowed_attributes = $this->args['amp_layout_allowed_attributes']; | ||
// Prepare whitelists. | ||
$this->allowed_tags = $this->args['amp_allowed_tags']; | ||
foreach ( AMP_Rule_Spec::$additional_allowed_tags as $tag_name => $tag_rule_spec ) { | ||
$this->allowed_tags[ $tag_name ][] = $tag_rule_spec; | ||
} | ||
|
||
foreach ( $this->allowed_tags as &$tag_specs ) { | ||
foreach ( $tag_specs as &$tag_spec ) { | ||
if ( isset( $tag_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] ) ) { | ||
$tag_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] = $this->process_alternate_names( $tag_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] ); | ||
} | ||
} | ||
} | ||
$this->globally_allowed_attributes = $this->process_alternate_names( $this->args['amp_globally_allowed_attributes'] ); | ||
$this->layout_allowed_attributes = $this->process_alternate_names( $this->args['amp_layout_allowed_attributes'] ); | ||
} | ||
|
||
/** | ||
|
@@ -127,17 +145,41 @@ public function get_scripts() { | |
return $scripts; | ||
} | ||
|
||
/** | ||
* Process alternative names in attribute spec list. | ||
* | ||
* @since 0.7 | ||
* | ||
* @param array $attr_spec_list Attribute spec list. | ||
* @return array Modified attribute spec list. | ||
*/ | ||
private function process_alternate_names( $attr_spec_list ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is called for all tags and then for all attributes of each tag which is quite resource heavy. My understanding is that it is used to add the To avoid having to do all these loops, we could perhaps do the conditional check at the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually it should just be called once for each tag. It loops over all attributes itself. By doing it once at the constructor we avoid double-nested |
||
foreach ( $attr_spec_list as $attr_name => &$attr_spec ) { | ||
if ( '[' === $attr_name[0] ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, since such attributes aren't understood by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we could use another term than |
||
$placeholder_attr_name = $this->args['amp_bind_placeholder_prefix'] . trim( $attr_name, '[]' ); | ||
if ( ! isset( $attr_spec[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) { | ||
$attr_spec[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] = array(); | ||
} | ||
$attr_spec[ AMP_Rule_Spec::ALTERNATIVE_NAMES ][] = $placeholder_attr_name; | ||
} | ||
|
||
// Save all alternative names in lookup to improve performance. | ||
if ( isset( $attr_spec[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason not to cache this in the conditional statement above to avoid looping through the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about that, but there are other alternative names than just the bind ones. For example, |
||
foreach ( $attr_spec[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) { | ||
$this->rev_alternate_attr_name_lookup[ $alternative_name ] = $attr_name; | ||
} | ||
} | ||
} | ||
return $attr_spec_list; | ||
} | ||
|
||
/** | ||
* Sanitize the <video> elements from the HTML contained in this instance's DOMDocument. | ||
* | ||
* @since 0.5 | ||
*/ | ||
public function sanitize() { | ||
|
||
foreach ( AMP_Rule_Spec::$additional_allowed_tags as $tag_name => $tag_rule_spec ) { | ||
$this->allowed_tags[ $tag_name ][] = $tag_rule_spec; | ||
} | ||
|
||
/** | ||
* Add root of content to the stack. | ||
* | ||
|
@@ -279,19 +321,41 @@ private function process_node( $node ) { | |
return; | ||
} | ||
|
||
// Obtain list of component scripts required. | ||
if ( ! empty( $tag_spec['also_requires_tag_warning'] ) ) { | ||
$this->script_components[] = strtok( $tag_spec['also_requires_tag_warning'][0], ' ' ); | ||
} | ||
if ( ! empty( $tag_spec['requires_extension'] ) ) { | ||
$this->script_components = array_merge( $this->script_components, $tag_spec['requires_extension'] ); | ||
} | ||
$merged_attr_spec_list = array_merge( | ||
$this->globally_allowed_attributes, | ||
$attr_spec_list | ||
); | ||
|
||
// Remove any remaining disallowed attributes. | ||
$this->sanitize_disallowed_attributes_in_node( $node, $attr_spec_list ); | ||
$this->sanitize_disallowed_attributes_in_node( $node, $merged_attr_spec_list ); | ||
|
||
// Remove values that don't conform to the attr_spec. | ||
$this->sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list ); | ||
$this->sanitize_disallowed_attribute_values_in_node( $node, $merged_attr_spec_list ); | ||
|
||
// Add required AMP component scripts if the element is still in the document. | ||
if ( $node->parentNode ) { | ||
if ( ! empty( $tag_spec['also_requires_tag_warning'] ) ) { | ||
$this->script_components[] = strtok( $tag_spec['also_requires_tag_warning'][0], ' ' ); | ||
} | ||
if ( ! empty( $tag_spec['requires_extension'] ) ) { | ||
$this->script_components = array_merge( $this->script_components, $tag_spec['requires_extension'] ); | ||
} | ||
|
||
// Check if element needs amp-bind component. | ||
if ( $node instanceof DOMElement && ! in_array( 'amp-bind', $this->script_components, true ) ) { | ||
foreach ( $node->attributes as $name => $value ) { | ||
$is_bind_attribute = ( | ||
'[' === $name[0] | ||
|| | ||
( isset( $this->rev_alternate_attr_name_lookup[ $name ] ) && '[' === $this->rev_alternate_attr_name_lookup[ $name ][0] ) | ||
); | ||
if ( $is_bind_attribute ) { | ||
$this->script_components[] = 'amp-bind'; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -525,33 +589,12 @@ private function sanitize_disallowed_attributes_in_node( $node, $attr_spec_list | |
$attrs_to_remove = array(); | ||
foreach ( $node->attributes as $attr_name => $attr_node ) { | ||
if ( ! $this->is_amp_allowed_attribute( $attr_name, $attr_spec_list ) ) { | ||
/** | ||
* This attribute is not allowed for this node; plan to remove it. | ||
*/ | ||
$attrs_to_remove[] = $attr_name; | ||
} | ||
} | ||
|
||
if ( ! empty( $attrs_to_remove ) ) { | ||
/* | ||
* Ensure we are not removing attributes listed as an alternate | ||
* or allowed attributes, e.g. 'srcset' is an alternate for 'src'. | ||
*/ | ||
foreach ( $attr_spec_list as $attr_name => $attr_spec_rule_value ) { | ||
if ( isset( $attr_spec_rule_value[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) { | ||
foreach ( $attr_spec_rule_value[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) { | ||
$alt_name_keys = array_keys( $attrs_to_remove, $alternative_name, true ); | ||
if ( ! empty( $alt_name_keys ) ) { | ||
unset( $attrs_to_remove[ $alt_name_keys[0] ] ); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Remove the disallowed attributes. | ||
foreach ( $attrs_to_remove as $attr_name ) { | ||
$node->removeAttribute( $attr_name ); | ||
} | ||
foreach ( $attrs_to_remove as $attr_name ) { | ||
$node->removeAttribute( $attr_name ); | ||
} | ||
} | ||
|
||
|
@@ -1065,6 +1108,16 @@ private function is_amp_allowed_attribute( $attr_name, $attr_spec_list ) { | |
} | ||
} | ||
} | ||
|
||
$is_allowed_alt_name_attr = ( | ||
isset( $this->rev_alternate_attr_name_lookup[ $attr_name ] ) | ||
&& | ||
isset( $attr_spec_list[ $this->rev_alternate_attr_name_lookup[ $attr_name ] ] ) | ||
); | ||
if ( $is_allowed_alt_name_attr ) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
$this->allowed_tags[ $tag_name ]
always an array,amp-share-tracking
doesn't seem to be declared as an array?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic was just moved from the
sanitize
method to the constructor. Yes, it should always an array. The$additional_allowed_tags
is an array of arrays:https://github.com/Automattic/amp-wp/blob/216ec376ab5bc698e03a149e6e68fa771cffd2d0/includes/sanitizers/class-amp-rule-spec.php#L88-L95
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, though it is still looking for
$this->allowed_tags[ 'amp-share-tracking' ]
which doesn't exists isn't it? To make sure the array is conventionally declared, then we should have an:Like you have done here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potentially but it's just moved from existing code and it didn't have any problem. And actually, my use of
isset()
is actually overkill it seems. It's actually not required in PHP at all. For example:Results in:
There are no notices or warnings. It works all the way back to PHP 4.3.0 (and beyond potentially): https://3v4l.org/b8ZaG