From 6215533798b968d88dffe15de32a91371304e75f Mon Sep 17 00:00:00 2001
From: Ryan Kienstra <kienstraryan@gmail.com>
Date: Thu, 26 Mar 2020 01:18:07 -0600
Subject: [PATCH] Add AMP support for the Twitter Timeline widget

Use the <amp-twitter> component,
and add unit tests for several cases.
---
 modules/widgets/twitter-timeline.php          |  96 +++++++------
 .../widgets/test_twitter-timeline-widget.php  | 135 ++++++++++++++++++
 2 files changed, 189 insertions(+), 42 deletions(-)
 create mode 100644 tests/php/modules/widgets/test_twitter-timeline-widget.php

diff --git a/modules/widgets/twitter-timeline.php b/modules/widgets/twitter-timeline.php
index c5ae469537831..85b0e60d0c8b7 100644
--- a/modules/widgets/twitter-timeline.php
+++ b/modules/widgets/twitter-timeline.php
@@ -46,7 +46,9 @@ public function __construct() {
 	 * Enqueue scripts.
 	 */
 	public function enqueue_scripts() {
-		wp_enqueue_script( 'jetpack-twitter-timeline' );
+		if ( ! class_exists( 'Jetpack_AMP_Support' ) || ! Jetpack_AMP_Support::is_amp_request() ) {
+			wp_enqueue_script( 'jetpack-twitter-timeline' );
+		}
 	}
 
 	/**
@@ -84,42 +86,36 @@ public function admin_scripts( $hook ) {
 	 * @param array $instance Saved values from database.
 	 */
 	public function widget( $args, $instance ) {
+		$output = '';
+
 		// Twitter deprecated `data-widget-id` on 2018-05-25,
 		// with cease support deadline on 2018-07-27.
 		if ( isset( $instance['type'] ) && 'widget-id' === $instance['type'] ) {
 			if ( current_user_can( 'edit_theme_options' ) ) {
-				echo $args['before_widget'];
-				echo $args['before_title'] . esc_html__( 'Twitter Timeline', 'jetpack' ) . $args['after_title'];
-				echo '<p>' . esc_html__( "The Twitter Timeline widget can't display tweets based on searches or hashtags. To display a simple list of tweets instead, change the Widget ID to a Twitter username. Otherwise, delete this widget.", 'jetpack' ) . '</p>';
-				echo '<p>' . esc_html__( '(Only administrators will see this message.)', 'jetpack' ) . '</p>';
-				echo $args['after_widget'];
+				$output .= $args['before_widget']
+				. $args['before_title'] . esc_html__( 'Twitter Timeline', 'jetpack' ) . $args['after_title']
+				. '<p>' . esc_html__( "The Twitter Timeline widget can't display tweets based on searches or hashtags. To display a simple list of tweets instead, change the Widget ID to a Twitter username. Otherwise, delete this widget.", 'jetpack' ) . '</p>'
+				. '<p>' . esc_html__( '(Only administrators will see this message.)', 'jetpack' ) . '</p>'
+				. $args['after_widget'];
 			}
+
+			echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
 			return;
 		}
 
 		$instance['lang'] = substr( strtoupper( get_locale() ), 0, 2 );
 
-		echo $args['before_widget'];
+		$output .= $args['before_widget'];
 
 		$title = isset( $instance['title'] ) ? $instance['title'] : '';
 
 		/** This filter is documented in core/src/wp-includes/default-widgets.php */
 		$title = apply_filters( 'widget_title', $title );
 		if ( ! empty( $title ) ) {
-			echo $args['before_title'] . $title . $args['after_title'];
-		}
-
-		if ( isset( $instance['type'] ) && 'widget-id' === $instance['type'] && current_user_can( 'edit_theme_options' ) ) {
-			echo '<p>' . esc_html__( 'As of July 27, 2018, the Twitter Timeline widget will no longer display tweets based on searches or hashtags. To display a simple list of tweets instead, change the Widget ID to a Twitter username.', 'jetpack' ) . '</p>';
-			echo '<p>' . esc_html__( '(Only administrators will see this message.)', 'jetpack' ) . '</p>';
+			$output .= $args['before_title'] . $title . $args['after_title'];
 		}
 
-		// Start tag output
-		// This tag is transformed into the widget markup by Twitter's
-		// widgets.js code
-		echo '<a class="twitter-timeline"';
-
-		$data_attribs = array(
+		$possible_data_attribs = array(
 			'width',
 			'height',
 			'theme',
@@ -127,16 +123,17 @@ public function widget( $args, $instance ) {
 			'tweet-limit',
 			'lang',
 		);
-		foreach ( $data_attribs as $att ) {
+		$data_attrs            = '';
+		foreach ( $possible_data_attribs as $att ) {
 			if ( ! empty( $instance[ $att ] ) && ! is_array( $instance[ $att ] ) ) {
-				echo ' data-' . esc_attr( $att ) . '="' . esc_attr( $instance[ $att ] ) . '"';
+				$data_attrs .= ' data-' . esc_attr( $att ) . '="' . esc_attr( $instance[ $att ] ) . '"';
 			}
 		}
 
 		/** This filter is documented in modules/shortcodes/tweet.php */
 		$partner = apply_filters( 'jetpack_twitter_partner_id', 'jetpack' );
 		if ( ! empty( $partner ) ) {
-			echo ' data-partner="' . esc_attr( $partner ) . '"';
+			$data_attrs .= ' data-partner="' . esc_attr( $partner ) . '"';
 		}
 
 		/**
@@ -152,29 +149,13 @@ public function widget( $args, $instance ) {
 		 */
 		$dnt = apply_filters( 'jetpack_twitter_timeline_default_dnt', false );
 		if ( true === $dnt ) {
-			echo ' data-dnt="true"';
+			$data_attrs .= ' data-dnt="true"';
 		}
 
 		if ( ! empty( $instance['chrome'] ) && is_array( $instance['chrome'] ) ) {
-			echo ' data-chrome="' . esc_attr( join( ' ', $instance['chrome'] ) ) . '"';
+			$data_attrs .= ' data-chrome="' . esc_attr( join( ' ', $instance['chrome'] ) ) . '"';
 		}
 
-		$type      = ( isset( $instance['type'] ) ? $instance['type'] : '' );
-		$widget_id = ( isset( $instance['widget-id'] ) ? $instance['widget-id'] : '' );
-		switch ( $type ) {
-			case 'profile':
-				echo ' href="https://twitter.com/' . esc_attr( $widget_id ) . '"';
-				break;
-			case 'widget-id':
-			default:
-				echo ' data-widget-id="' . esc_attr( $widget_id ) . '"';
-				break;
-		}
-		echo ' href="https://twitter.com/' . esc_attr( $widget_id ) . '"';
-
-		// End tag output
-		echo '>';
-
 		$timeline_placeholder = __( 'My Tweets', 'jetpack' );
 
 		/**
@@ -188,11 +169,42 @@ public function widget( $args, $instance ) {
 		 */
 		$timeline_placeholder = apply_filters( 'jetpack_twitter_timeline_placeholder', $timeline_placeholder );
 
-		echo esc_html( $timeline_placeholder ) . '</a>';
+		$type      = ( isset( $instance['type'] ) ? $instance['type'] : '' );
+		$widget_id = ( isset( $instance['widget-id'] ) ? $instance['widget-id'] : '' );
+
+		if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
+			$width   = ! empty( $instance['width'] ) ? $instance['width'] : 600;
+			$height  = ! empty( $instance['height'] ) ? $instance['height'] : 480;
+			$output .= '<amp-twitter' . $data_attrs . ' layout="responsive" data-timeline-source-type="profile" data-timeline-screen-name="' . esc_attr( $widget_id ) . '" width="' . absint( $width ) . '" height="' . absint( $height ) . '">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+			$output .= esc_html( $timeline_placeholder ) . '</amp-twitter>';
+
+			echo $output . $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+			return;
+		}
+
+		// Start tag output
+		// This tag is transformed into the widget markup by Twitter's
+		// widgets.js code.
+		$output .= '<a class="twitter-timeline"' . $data_attrs; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+		switch ( $type ) {
+			case 'profile':
+				$output .= ' href="https://twitter.com/' . esc_attr( $widget_id ) . '"';
+				break;
+			case 'widget-id':
+			default:
+				$output .= ' data-widget-id="' . esc_attr( $widget_id ) . '"';
+				break;
+		}
+		$output .= ' href="https://twitter.com/' . esc_attr( $widget_id ) . '"';
+
+		// End tag output.
+		$output .= '>';
+
+		$output .= esc_html( $timeline_placeholder ) . '</a>';
 
 		// End tag output
 
-		echo $args['after_widget'];
+		echo $output . $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
 
 		/** This action is documented in modules/widgets/gravatar-profile.php */
 		do_action( 'jetpack_stats_extra', 'widget_view', 'twitter_timeline' );
diff --git a/tests/php/modules/widgets/test_twitter-timeline-widget.php b/tests/php/modules/widgets/test_twitter-timeline-widget.php
new file mode 100644
index 0000000000000..95e5cd4e3d6b8
--- /dev/null
+++ b/tests/php/modules/widgets/test_twitter-timeline-widget.php
@@ -0,0 +1,135 @@
+<?php // phpcs:ignore Wordpress.Files.FileName.NotHyphenatedLowercase
+/**
+ * Test Jetpack_Twitter_Timeline_Widget.
+ *
+ * @package Jetpack
+ */
+
+require dirname( __FILE__ ) . '/../../../../modules/widgets/twitter-timeline.php';
+
+/**
+ * Test Jetpack_Twitter_Timeline_Widget.
+ */
+class WP_Test_Twitter_Timeline_Widget extends WP_UnitTestCase {
+
+	/**
+	 * The tested instance.
+	 *
+	 * @var Jetpack_Twitter_Timeline_Widget
+	 */
+	public $instance;
+
+	/**
+	 * Sets up each test.
+	 *
+	 * @inheritDoc
+	 */
+	public function setUp() {
+		parent::setUp();
+		$this->instance = new Jetpack_Twitter_Timeline_Widget();
+	}
+
+	/**
+	 * Gets the test data for test_widget().
+	 *
+	 * @return array The test data.
+	 */
+	public function get_widget_data() {
+		return array(
+			'no_id'                    => array(
+				array(),
+				false,
+				'<div><a class="twitter-timeline" data-lang="EN" data-partner="jetpack" data-widget-id="" href="https://twitter.com/">My Tweets</a></div>',
+			),
+			'type_is_widget_id'        => array(
+				array( 'type' => 'widget-id' ),
+				false,
+				'<div><h1>Twitter Timeline</h1><p>The Twitter Timeline widget can&#039;t display tweets based on searches or hashtags. To display a simple list of tweets instead, change the Widget ID to a Twitter username. Otherwise, delete this widget.</p><p>(Only administrators will see this message.)</p></div>',
+			),
+			'only_widget_id_present'   => array(
+				array( 'widget-id' => 'wordpress' ),
+				false,
+				'<div><a class="twitter-timeline" data-lang="EN" data-partner="jetpack" data-widget-id="wordpress" href="https://twitter.com/wordpress">My Tweets</a></div>',
+			),
+			'type_is_profile'          => array(
+				array(
+					'widget-id' => 'wordpress',
+					'type'      => 'profile',
+				),
+				false,
+				'<div><a class="twitter-timeline" data-lang="EN" data-partner="jetpack" href="https://twitter.com/wordpress" href="https://twitter.com/wordpress">My Tweets</a></div>',
+			),
+			'with_data_attributes'     => array(
+				array(
+					'width'        => '200',
+					'height'       => '400',
+					'theme'        => 'dark',
+					'border-color' => '#ffffff',
+					'tweet-limit'  => '9',
+					'lang'         => 'es',
+				),
+				false,
+				'<div><a class="twitter-timeline" data-width="200" data-height="400" data-theme="dark" data-border-color="#ffffff" data-tweet-limit="9" data-lang="EN" data-partner="jetpack" data-widget-id="" href="https://twitter.com/">My Tweets</a></div>',
+			),
+			'data_chrome'              => array(
+				array(
+					'widget-id' => 'wordpress',
+					'chrome'    => array( 'noborders', 'nofooter' ),
+				),
+				false,
+				'<div><a class="twitter-timeline" data-lang="EN" data-partner="jetpack" data-chrome="noborders nofooter" data-widget-id="wordpress" href="https://twitter.com/wordpress">My Tweets</a></div>',
+			),
+			'amp_no_widget_id_present' => array(
+				array(),
+				true,
+				'<div><amp-twitter data-lang="EN" data-partner="jetpack" layout="responsive" data-timeline-source-type="profile" data-timeline-screen-name="" width="600" height="480">My Tweets</amp-twitter></div>',
+			),
+			'amp_widget_id_present'    => array(
+				array( 'widget-id' => 'wordpress' ),
+				true,
+				'<div><amp-twitter data-lang="EN" data-partner="jetpack" layout="responsive" data-timeline-source-type="profile" data-timeline-screen-name="wordpress" width="600" height="480">My Tweets</amp-twitter></div>',
+			),
+			'amp_with_data_attributes' => array(
+				array(
+					'width'        => '200',
+					'height'       => '800',
+					'theme'        => 'light',
+					'border-color' => '#ff0000',
+					'tweet-limit'  => '4',
+					'lang'         => 'cnr',
+				),
+				true,
+				'<div><amp-twitter data-width="200" data-height="800" data-theme="light" data-border-color="#ff0000" data-tweet-limit="4" data-lang="EN" data-partner="jetpack" layout="responsive" data-timeline-source-type="profile" data-timeline-screen-name="" width="200" height="800">My Tweets</amp-twitter></div>',
+			),
+		);
+	}
+
+	/**
+	 * Test the widget method that outputs the markup.
+	 *
+	 * @dataProvider get_widget_data
+	 * @covers Jetpack_Twitter_Timeline_Widget::widget()
+	 *
+	 * @param array  $instance The widget instance.
+	 * @param bool   $is_amp Whether this is on an AMP endpoint.
+	 * @param string $expected The expected output of the tested method.
+	 */
+	public function test_widget( $instance, $is_amp, $expected ) {
+		wp_set_current_user( $this->factory()->user->create( array( 'role' => 'administrator' ) ) );
+		if ( $is_amp ) {
+			add_filter( 'jetpack_is_amp_request', '__return_true' );
+		}
+
+		$args = array(
+			'before_widget' => '<div>',
+			'after_widget'  => '</div>',
+			'before_title'  => '<h1>',
+			'after_title'   => '</h1>',
+		);
+
+		ob_start();
+		$this->instance->widget( $args, $instance );
+
+		$this->assertEquals( $expected, ob_get_clean() );
+	}
+}