story = $story;
}
/**
* Renders the story.
*
* @since 1.0.0
*
* @return string The complete HTML markup for the story.
*/
public function render(): string {
$markup = $this->story->get_markup();
$markup = $this->fix_incorrect_charset( $markup );
$markup = $this->fix_malformed_script_link_tags( $markup );
$markup = $this->replace_html_head( $markup );
$markup = wp_replace_insecure_home_url( $markup );
$markup = $this->print_analytics( $markup );
$markup = $this->print_social_share( $markup );
return $markup;
}
/**
* Fix incorrect tags.
*
* React/JSX outputs the charset attribute name as "charSet",
* but libdom and the AMP toolbox only recognize lowercase "charset"
*
* @since 1.28.0
*
* @param string $content Story markup.
* @return string Filtered content
*/
public function fix_incorrect_charset( string $content ): string {
return (string) preg_replace( '//i', '', $content );
}
/**
* Fix malformed tags in the .
*
* On certain environments like WordPress.com VIP, there is additional KSES
* hardening that prevents saving ``
* and `https://cdn.ampproject.org/v0/amp-story-1.0.js`
* into ``.
*
* @since 1.13.0
*
* @param string $content Story markup.
* @return string Filtered content
*/
protected function fix_malformed_script_link_tags( string $content ): string {
$replaced_content = preg_replace_callback(
'/]+href="(?P[^"]+)"[^>]*>\1<\/a>/m',
static function ( $matches ) {
if ( str_starts_with( $matches['href'], 'https://cdn.ampproject.org/' ) ) {
$script_url = $matches['href'];
// Turns `https://cdn.ampproject.org/v0.js`
// into ``.
if ( 'https://cdn.ampproject.org/v0.js' === $script_url ) {
return ""; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
}
// Extract 'amp-story' from 'https://cdn.ampproject.org/v0/amp-story-1.0.js'.
$sub_matches = [];
preg_match( '/v0\/(?P[\w-]+)-[\d.]+\.js/', $script_url, $sub_matches );
$custom_element = $sub_matches['custom_element'];
// Turns `https://cdn.ampproject.org/v0/amp-story-1.0.js`
// into .
return ""; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
}
return $matches[0];
},
$content
);
// On errors the return value of preg_replace_callback() is null.
return $replaced_content ?: $content;
}
/**
* Returns the full HTML markup for a given story besides boilerplate.
*
* @since 1.0.0
*
* @return string Filtered content.
*/
protected function get_html_head_markup(): string {
ob_start();
?>
with dynamic content.
*
* @since 1.0.0
*
* @param string $content Story markup.
* @return string Filtered content.
*/
protected function replace_html_head( string $content ): string {
$start_tag = '';
$end_tag = '';
// Replace malformed meta tags with correct tags.
$content = (string) preg_replace( '//i', $start_tag, $content );
$content = (string) preg_replace( '//i', $end_tag, $content );
$start_tag_pos = strpos( $content, $start_tag );
$end_tag_pos = strpos( $content, $end_tag );
if ( false !== $start_tag_pos && false !== $end_tag_pos ) {
$end_tag_pos += \strlen( $end_tag );
$content = substr_replace( $content, $this->get_html_head_markup(), $start_tag_pos, $end_tag_pos - $start_tag_pos );
}
return $content;
}
/**
* Print analytics code before closing ``.
*
* @since 1.2.0
*
* @param string $content String to replace.
*/
protected function print_analytics( string $content ): string {
ob_start();
/**
* Fires before the closing tag.
*
* Can be used to print configuration.
*
* @since 1.1.0
*/
do_action( 'web_stories_print_analytics' );
$output = (string) ob_get_clean();
return str_replace( '', $output . '', $content );
}
/**
* Print amp-story-social-share before closing ``.
*
* @since 1.6.0
*
* @param string $content String to replace.
*/
protected function print_social_share( string $content ): string {
$share_providers = [
[
'provider' => 'twitter',
],
[
'provider' => 'linkedin',
],
[
'provider' => 'email',
],
[
'provider' => 'system',
],
];
/**
* Filters the list of sharing providers in the Web Stories sharing dialog.
*
* @since 1.3.0
*
* @link https://amp.dev/documentation/components/amp-social-share/?format=stories#pre-configured-providers
*
* @param array[] $share_providers List of sharing providers.
*/
$share_providers = apply_filters( 'web_stories_share_providers', $share_providers );
if ( empty( $share_providers ) ) {
return $content;
}
$config = [
'shareProviders' => $share_providers,
];
$social_share = sprintf(
'',
wp_json_encode( $config )
);
return str_replace( '', $social_share . '', $content );
}
}