balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-widget-text.php (21301B)


      1 <?php
      2 /**
      3  * Widget API: WP_Widget_Text class
      4  *
      5  * @package WordPress
      6  * @subpackage Widgets
      7  * @since 4.4.0
      8  */
      9 
     10 /**
     11  * Core class used to implement a Text widget.
     12  *
     13  * @since 2.8.0
     14  *
     15  * @see WP_Widget
     16  */
     17 class WP_Widget_Text extends WP_Widget {
     18 
     19 	/**
     20 	 * Whether or not the widget has been registered yet.
     21 	 *
     22 	 * @since 4.8.1
     23 	 * @var bool
     24 	 */
     25 	protected $registered = false;
     26 
     27 	/**
     28 	 * Sets up a new Text widget instance.
     29 	 *
     30 	 * @since 2.8.0
     31 	 */
     32 	public function __construct() {
     33 		$widget_ops  = array(
     34 			'classname'                   => 'widget_text',
     35 			'description'                 => __( 'Arbitrary text.' ),
     36 			'customize_selective_refresh' => true,
     37 			'show_instance_in_rest'       => true,
     38 		);
     39 		$control_ops = array(
     40 			'width'  => 400,
     41 			'height' => 350,
     42 		);
     43 		parent::__construct( 'text', __( 'Text' ), $widget_ops, $control_ops );
     44 	}
     45 
     46 	/**
     47 	 * Add hooks for enqueueing assets when registering all widget instances of this widget class.
     48 	 *
     49 	 * @param int $number Optional. The unique order number of this widget instance
     50 	 *                    compared to other instances of the same class. Default -1.
     51 	 */
     52 	public function _register_one( $number = -1 ) {
     53 		parent::_register_one( $number );
     54 		if ( $this->registered ) {
     55 			return;
     56 		}
     57 		$this->registered = true;
     58 
     59 		wp_add_inline_script( 'text-widgets', sprintf( 'wp.textWidgets.idBases.push( %s );', wp_json_encode( $this->id_base ) ) );
     60 
     61 		if ( $this->is_preview() ) {
     62 			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) );
     63 		}
     64 
     65 		// Note that the widgets component in the customizer will also do
     66 		// the 'admin_print_scripts-widgets.php' action in WP_Customize_Widgets::print_scripts().
     67 		add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) );
     68 
     69 		// Note that the widgets component in the customizer will also do
     70 		// the 'admin_footer-widgets.php' action in WP_Customize_Widgets::print_footer_scripts().
     71 		add_action( 'admin_footer-widgets.php', array( 'WP_Widget_Text', 'render_control_template_scripts' ) );
     72 	}
     73 
     74 	/**
     75 	 * Determines whether a given instance is legacy and should bypass using TinyMCE.
     76 	 *
     77 	 * @since 4.8.1
     78 	 *
     79 	 * @param array $instance {
     80 	 *     Instance data.
     81 	 *
     82 	 *     @type string      $text   Content.
     83 	 *     @type bool|string $filter Whether autop or content filters should apply.
     84 	 *     @type bool        $legacy Whether widget is in legacy mode.
     85 	 * }
     86 	 * @return bool Whether Text widget instance contains legacy data.
     87 	 */
     88 	public function is_legacy_instance( $instance ) {
     89 
     90 		// Legacy mode when not in visual mode.
     91 		if ( isset( $instance['visual'] ) ) {
     92 			return ! $instance['visual'];
     93 		}
     94 
     95 		// Or, the widget has been added/updated in 4.8.0 then filter prop is 'content' and it is no longer legacy.
     96 		if ( isset( $instance['filter'] ) && 'content' === $instance['filter'] ) {
     97 			return false;
     98 		}
     99 
    100 		// If the text is empty, then nothing is preventing migration to TinyMCE.
    101 		if ( empty( $instance['text'] ) ) {
    102 			return false;
    103 		}
    104 
    105 		$wpautop         = ! empty( $instance['filter'] );
    106 		$has_line_breaks = ( false !== strpos( trim( $instance['text'] ), "\n" ) );
    107 
    108 		// If auto-paragraphs are not enabled and there are line breaks, then ensure legacy mode.
    109 		if ( ! $wpautop && $has_line_breaks ) {
    110 			return true;
    111 		}
    112 
    113 		// If an HTML comment is present, assume legacy mode.
    114 		if ( false !== strpos( $instance['text'], '<!--' ) ) {
    115 			return true;
    116 		}
    117 
    118 		// In the rare case that DOMDocument is not available we cannot reliably sniff content and so we assume legacy.
    119 		if ( ! class_exists( 'DOMDocument' ) ) {
    120 			// @codeCoverageIgnoreStart
    121 			return true;
    122 			// @codeCoverageIgnoreEnd
    123 		}
    124 
    125 		$doc = new DOMDocument();
    126 
    127 		// Suppress warnings generated by loadHTML.
    128 		$errors = libxml_use_internal_errors( true );
    129 		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
    130 		@$doc->loadHTML(
    131 			sprintf(
    132 				'<!DOCTYPE html><html><head><meta charset="%s"></head><body>%s</body></html>',
    133 				esc_attr( get_bloginfo( 'charset' ) ),
    134 				$instance['text']
    135 			)
    136 		);
    137 		libxml_use_internal_errors( $errors );
    138 
    139 		$body = $doc->getElementsByTagName( 'body' )->item( 0 );
    140 
    141 		// See $allowedposttags.
    142 		$safe_elements_attributes = array(
    143 			'strong'  => array(),
    144 			'em'      => array(),
    145 			'b'       => array(),
    146 			'i'       => array(),
    147 			'u'       => array(),
    148 			's'       => array(),
    149 			'ul'      => array(),
    150 			'ol'      => array(),
    151 			'li'      => array(),
    152 			'hr'      => array(),
    153 			'abbr'    => array(),
    154 			'acronym' => array(),
    155 			'code'    => array(),
    156 			'dfn'     => array(),
    157 			'a'       => array(
    158 				'href' => true,
    159 			),
    160 			'img'     => array(
    161 				'src' => true,
    162 				'alt' => true,
    163 			),
    164 		);
    165 		$safe_empty_elements      = array( 'img', 'hr', 'iframe' );
    166 
    167 		foreach ( $body->getElementsByTagName( '*' ) as $element ) {
    168 			/** @var DOMElement $element */
    169 			$tag_name = strtolower( $element->nodeName );
    170 
    171 			// If the element is not safe, then the instance is legacy.
    172 			if ( ! isset( $safe_elements_attributes[ $tag_name ] ) ) {
    173 				return true;
    174 			}
    175 
    176 			// If the element is not safely empty and it has empty contents, then legacy mode.
    177 			if ( ! in_array( $tag_name, $safe_empty_elements, true ) && '' === trim( $element->textContent ) ) {
    178 				return true;
    179 			}
    180 
    181 			// If an attribute is not recognized as safe, then the instance is legacy.
    182 			foreach ( $element->attributes as $attribute ) {
    183 				/** @var DOMAttr $attribute */
    184 				$attribute_name = strtolower( $attribute->nodeName );
    185 
    186 				if ( ! isset( $safe_elements_attributes[ $tag_name ][ $attribute_name ] ) ) {
    187 					return true;
    188 				}
    189 			}
    190 		}
    191 
    192 		// Otherwise, the text contains no elements/attributes that TinyMCE could drop, and therefore the widget does not need legacy mode.
    193 		return false;
    194 	}
    195 
    196 	/**
    197 	 * Filters gallery shortcode attributes.
    198 	 *
    199 	 * Prevents all of a site's attachments from being shown in a gallery displayed on a
    200 	 * non-singular template where a $post context is not available.
    201 	 *
    202 	 * @since 4.9.0
    203 	 *
    204 	 * @param array $attrs Attributes.
    205 	 * @return array Attributes.
    206 	 */
    207 	public function _filter_gallery_shortcode_attrs( $attrs ) {
    208 		if ( ! is_singular() && empty( $attrs['id'] ) && empty( $attrs['include'] ) ) {
    209 			$attrs['id'] = -1;
    210 		}
    211 		return $attrs;
    212 	}
    213 
    214 	/**
    215 	 * Outputs the content for the current Text widget instance.
    216 	 *
    217 	 * @since 2.8.0
    218 	 *
    219 	 * @global WP_Post $post Global post object.
    220 	 *
    221 	 * @param array $args     Display arguments including 'before_title', 'after_title',
    222 	 *                        'before_widget', and 'after_widget'.
    223 	 * @param array $instance Settings for the current Text widget instance.
    224 	 */
    225 	public function widget( $args, $instance ) {
    226 		global $post;
    227 
    228 		$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
    229 
    230 		/** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
    231 		$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
    232 
    233 		$text                  = ! empty( $instance['text'] ) ? $instance['text'] : '';
    234 		$is_visual_text_widget = ( ! empty( $instance['visual'] ) && ! empty( $instance['filter'] ) );
    235 
    236 		// In 4.8.0 only, visual Text widgets get filter=content, without visual prop; upgrade instance props just-in-time.
    237 		if ( ! $is_visual_text_widget ) {
    238 			$is_visual_text_widget = ( isset( $instance['filter'] ) && 'content' === $instance['filter'] );
    239 		}
    240 		if ( $is_visual_text_widget ) {
    241 			$instance['filter'] = true;
    242 			$instance['visual'] = true;
    243 		}
    244 
    245 		/*
    246 		 * Suspend legacy plugin-supplied do_shortcode() for 'widget_text' filter for the visual Text widget to prevent
    247 		 * shortcodes being processed twice. Now do_shortcode() is added to the 'widget_text_content' filter in core itself
    248 		 * and it applies after wpautop() to prevent corrupting HTML output added by the shortcode. When do_shortcode() is
    249 		 * added to 'widget_text_content' then do_shortcode() will be manually called when in legacy mode as well.
    250 		 */
    251 		$widget_text_do_shortcode_priority       = has_filter( 'widget_text', 'do_shortcode' );
    252 		$should_suspend_legacy_shortcode_support = ( $is_visual_text_widget && false !== $widget_text_do_shortcode_priority );
    253 		if ( $should_suspend_legacy_shortcode_support ) {
    254 			remove_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
    255 		}
    256 
    257 		// Override global $post so filters (and shortcodes) apply in a consistent context.
    258 		$original_post = $post;
    259 		if ( is_singular() ) {
    260 			// Make sure post is always the queried object on singular queries (not from another sub-query that failed to clean up the global $post).
    261 			$post = get_queried_object();
    262 		} else {
    263 			// Nullify the $post global during widget rendering to prevent shortcodes from running with the unexpected context on archive queries.
    264 			$post = null;
    265 		}
    266 
    267 		// Prevent dumping out all attachments from the media library.
    268 		add_filter( 'shortcode_atts_gallery', array( $this, '_filter_gallery_shortcode_attrs' ) );
    269 
    270 		/**
    271 		 * Filters the content of the Text widget.
    272 		 *
    273 		 * @since 2.3.0
    274 		 * @since 4.4.0 Added the `$widget` parameter.
    275 		 * @since 4.8.1 The `$widget` param may now be a `WP_Widget_Custom_HTML` object in addition to a `WP_Widget_Text` object.
    276 		 *
    277 		 * @param string                               $text     The widget content.
    278 		 * @param array                                $instance Array of settings for the current widget.
    279 		 * @param WP_Widget_Text|WP_Widget_Custom_HTML $widget   Current text or HTML widget instance.
    280 		 */
    281 		$text = apply_filters( 'widget_text', $text, $instance, $this );
    282 
    283 		if ( $is_visual_text_widget ) {
    284 
    285 			/**
    286 			 * Filters the content of the Text widget to apply changes expected from the visual (TinyMCE) editor.
    287 			 *
    288 			 * By default a subset of the_content filters are applied, including wpautop and wptexturize.
    289 			 *
    290 			 * @since 4.8.0
    291 			 *
    292 			 * @param string         $text     The widget content.
    293 			 * @param array          $instance Array of settings for the current widget.
    294 			 * @param WP_Widget_Text $widget   Current Text widget instance.
    295 			 */
    296 			$text = apply_filters( 'widget_text_content', $text, $instance, $this );
    297 		} else {
    298 			// Now in legacy mode, add paragraphs and line breaks when checkbox is checked.
    299 			if ( ! empty( $instance['filter'] ) ) {
    300 				$text = wpautop( $text );
    301 			}
    302 
    303 			/*
    304 			 * Manually do shortcodes on the content when the core-added filter is present. It is added by default
    305 			 * in core by adding do_shortcode() to the 'widget_text_content' filter to apply after wpautop().
    306 			 * Since the legacy Text widget runs wpautop() after 'widget_text' filters are applied, the widget in
    307 			 * legacy mode here manually applies do_shortcode() on the content unless the default
    308 			 * core filter for 'widget_text_content' has been removed, or if do_shortcode() has already
    309 			 * been applied via a plugin adding do_shortcode() to 'widget_text' filters.
    310 			 */
    311 			if ( has_filter( 'widget_text_content', 'do_shortcode' ) && ! $widget_text_do_shortcode_priority ) {
    312 				if ( ! empty( $instance['filter'] ) ) {
    313 					$text = shortcode_unautop( $text );
    314 				}
    315 				$text = do_shortcode( $text );
    316 			}
    317 		}
    318 
    319 		// Restore post global.
    320 		$post = $original_post;
    321 		remove_filter( 'shortcode_atts_gallery', array( $this, '_filter_gallery_shortcode_attrs' ) );
    322 
    323 		// Undo suspension of legacy plugin-supplied shortcode handling.
    324 		if ( $should_suspend_legacy_shortcode_support ) {
    325 			add_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
    326 		}
    327 
    328 		echo $args['before_widget'];
    329 		if ( ! empty( $title ) ) {
    330 			echo $args['before_title'] . $title . $args['after_title'];
    331 		}
    332 
    333 		$text = preg_replace_callback( '#<(video|iframe|object|embed)\s[^>]*>#i', array( $this, 'inject_video_max_width_style' ), $text );
    334 
    335 		// Adds 'noopener' relationship, without duplicating values, to all HTML A elements that have a target.
    336 		$text = wp_targeted_link_rel( $text );
    337 
    338 		?>
    339 			<div class="textwidget"><?php echo $text; ?></div>
    340 		<?php
    341 		echo $args['after_widget'];
    342 	}
    343 
    344 	/**
    345 	 * Inject max-width and remove height for videos too constrained to fit inside sidebars on frontend.
    346 	 *
    347 	 * @since 4.9.0
    348 	 *
    349 	 * @see WP_Widget_Media_Video::inject_video_max_width_style()
    350 	 *
    351 	 * @param array $matches Pattern matches from preg_replace_callback.
    352 	 * @return string HTML Output.
    353 	 */
    354 	public function inject_video_max_width_style( $matches ) {
    355 		$html = $matches[0];
    356 		$html = preg_replace( '/\sheight="\d+"/', '', $html );
    357 		$html = preg_replace( '/\swidth="\d+"/', '', $html );
    358 		$html = preg_replace( '/(?<=width:)\s*\d+px(?=;?)/', '100%', $html );
    359 		return $html;
    360 	}
    361 
    362 	/**
    363 	 * Handles updating settings for the current Text widget instance.
    364 	 *
    365 	 * @since 2.8.0
    366 	 *
    367 	 * @param array $new_instance New settings for this instance as input by the user via
    368 	 *                            WP_Widget::form().
    369 	 * @param array $old_instance Old settings for this instance.
    370 	 * @return array Settings to save or bool false to cancel saving.
    371 	 */
    372 	public function update( $new_instance, $old_instance ) {
    373 		$new_instance = wp_parse_args(
    374 			$new_instance,
    375 			array(
    376 				'title'  => '',
    377 				'text'   => '',
    378 				'filter' => false, // For back-compat.
    379 				'visual' => null,  // Must be explicitly defined.
    380 			)
    381 		);
    382 
    383 		$instance = $old_instance;
    384 
    385 		$instance['title'] = sanitize_text_field( $new_instance['title'] );
    386 		if ( current_user_can( 'unfiltered_html' ) ) {
    387 			$instance['text'] = $new_instance['text'];
    388 		} else {
    389 			$instance['text'] = wp_kses_post( $new_instance['text'] );
    390 		}
    391 
    392 		$instance['filter'] = ! empty( $new_instance['filter'] );
    393 
    394 		// Upgrade 4.8.0 format.
    395 		if ( isset( $old_instance['filter'] ) && 'content' === $old_instance['filter'] ) {
    396 			$instance['visual'] = true;
    397 		}
    398 		if ( 'content' === $new_instance['filter'] ) {
    399 			$instance['visual'] = true;
    400 		}
    401 
    402 		if ( isset( $new_instance['visual'] ) ) {
    403 			$instance['visual'] = ! empty( $new_instance['visual'] );
    404 		}
    405 
    406 		// Filter is always true in visual mode.
    407 		if ( ! empty( $instance['visual'] ) ) {
    408 			$instance['filter'] = true;
    409 		}
    410 
    411 		return $instance;
    412 	}
    413 
    414 	/**
    415 	 * Enqueue preview scripts.
    416 	 *
    417 	 * These scripts normally are enqueued just-in-time when a playlist shortcode is used.
    418 	 * However, in the customizer, a playlist shortcode may be used in a text widget and
    419 	 * dynamically added via selective refresh, so it is important to unconditionally enqueue them.
    420 	 *
    421 	 * @since 4.9.3
    422 	 */
    423 	public function enqueue_preview_scripts() {
    424 		require_once dirname( __DIR__ ) . '/media.php';
    425 
    426 		wp_playlist_scripts( 'audio' );
    427 		wp_playlist_scripts( 'video' );
    428 	}
    429 
    430 	/**
    431 	 * Loads the required scripts and styles for the widget control.
    432 	 *
    433 	 * @since 4.8.0
    434 	 */
    435 	public function enqueue_admin_scripts() {
    436 		wp_enqueue_editor();
    437 		wp_enqueue_media();
    438 		wp_enqueue_script( 'text-widgets' );
    439 		wp_add_inline_script( 'text-widgets', 'wp.textWidgets.init();', 'after' );
    440 	}
    441 
    442 	/**
    443 	 * Outputs the Text widget settings form.
    444 	 *
    445 	 * @since 2.8.0
    446 	 * @since 4.8.0 Form only contains hidden inputs which are synced with JS template.
    447 	 * @since 4.8.1 Restored original form to be displayed when in legacy mode.
    448 	 *
    449 	 * @see WP_Widget_Text::render_control_template_scripts()
    450 	 * @see _WP_Editors::editor()
    451 	 *
    452 	 * @param array $instance Current settings.
    453 	 */
    454 	public function form( $instance ) {
    455 		$instance = wp_parse_args(
    456 			(array) $instance,
    457 			array(
    458 				'title' => '',
    459 				'text'  => '',
    460 			)
    461 		);
    462 		?>
    463 		<?php if ( ! $this->is_legacy_instance( $instance ) ) : ?>
    464 			<?php
    465 
    466 			if ( user_can_richedit() ) {
    467 				add_filter( 'the_editor_content', 'format_for_editor', 10, 2 );
    468 				$default_editor = 'tinymce';
    469 			} else {
    470 				$default_editor = 'html';
    471 			}
    472 
    473 			/** This filter is documented in wp-includes/class-wp-editor.php */
    474 			$text = apply_filters( 'the_editor_content', $instance['text'], $default_editor );
    475 
    476 			// Reset filter addition.
    477 			if ( user_can_richedit() ) {
    478 				remove_filter( 'the_editor_content', 'format_for_editor' );
    479 			}
    480 
    481 			// Prevent premature closing of textarea in case format_for_editor() didn't apply or the_editor_content filter did a wrong thing.
    482 			$escaped_text = preg_replace( '#</textarea#i', '&lt;/textarea', $text );
    483 
    484 			?>
    485 			<input id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" class="title sync-input" type="hidden" value="<?php echo esc_attr( $instance['title'] ); ?>">
    486 			<textarea id="<?php echo $this->get_field_id( 'text' ); ?>" name="<?php echo $this->get_field_name( 'text' ); ?>" class="text sync-input" hidden><?php echo $escaped_text; ?></textarea>
    487 			<input id="<?php echo $this->get_field_id( 'filter' ); ?>" name="<?php echo $this->get_field_name( 'filter' ); ?>" class="filter sync-input" type="hidden" value="on">
    488 			<input id="<?php echo $this->get_field_id( 'visual' ); ?>" name="<?php echo $this->get_field_name( 'visual' ); ?>" class="visual sync-input" type="hidden" value="on">
    489 		<?php else : ?>
    490 			<input id="<?php echo $this->get_field_id( 'visual' ); ?>" name="<?php echo $this->get_field_name( 'visual' ); ?>" class="visual" type="hidden" value="">
    491 			<p>
    492 				<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
    493 				<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
    494 			</p>
    495 			<div class="notice inline notice-info notice-alt">
    496 				<?php if ( ! isset( $instance['visual'] ) ) : ?>
    497 					<p><?php _e( 'This widget may contain code that may work better in the &#8220;Custom HTML&#8221; widget. How about trying that widget instead?' ); ?></p>
    498 				<?php else : ?>
    499 					<p><?php _e( 'This widget may have contained code that may work better in the &#8220;Custom HTML&#8221; widget. If you haven&#8217;t yet, how about trying that widget instead?' ); ?></p>
    500 				<?php endif; ?>
    501 			</div>
    502 			<p>
    503 				<label for="<?php echo $this->get_field_id( 'text' ); ?>"><?php _e( 'Content:' ); ?></label>
    504 				<textarea class="widefat" rows="16" cols="20" id="<?php echo $this->get_field_id( 'text' ); ?>" name="<?php echo $this->get_field_name( 'text' ); ?>"><?php echo esc_textarea( $instance['text'] ); ?></textarea>
    505 			</p>
    506 			<p>
    507 				<input id="<?php echo $this->get_field_id( 'filter' ); ?>" name="<?php echo $this->get_field_name( 'filter' ); ?>" type="checkbox"<?php checked( ! empty( $instance['filter'] ) ); ?> />&nbsp;<label for="<?php echo $this->get_field_id( 'filter' ); ?>"><?php _e( 'Automatically add paragraphs' ); ?></label>
    508 			</p>
    509 			<?php
    510 		endif;
    511 	}
    512 
    513 	/**
    514 	 * Render form template scripts.
    515 	 *
    516 	 * @since 4.8.0
    517 	 * @since 4.9.0 The method is now static.
    518 	 */
    519 	public static function render_control_template_scripts() {
    520 		$dismissed_pointers = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) );
    521 		?>
    522 		<script type="text/html" id="tmpl-widget-text-control-fields">
    523 			<# var elementIdPrefix = 'el' + String( Math.random() ).replace( /\D/g, '' ) + '_' #>
    524 			<p>
    525 				<label for="{{ elementIdPrefix }}title"><?php esc_html_e( 'Title:' ); ?></label>
    526 				<input id="{{ elementIdPrefix }}title" type="text" class="widefat title">
    527 			</p>
    528 
    529 			<?php if ( ! in_array( 'text_widget_custom_html', $dismissed_pointers, true ) ) : ?>
    530 				<div hidden class="wp-pointer custom-html-widget-pointer wp-pointer-top">
    531 					<div class="wp-pointer-content">
    532 						<h3><?php _e( 'New Custom HTML Widget' ); ?></h3>
    533 						<?php if ( is_customize_preview() ) : ?>
    534 							<p><?php _e( 'Did you know there is a &#8220;Custom HTML&#8221; widget now? You can find it by pressing the &#8220;<a class="add-widget" href="#">Add a Widget</a>&#8221; button and searching for &#8220;HTML&#8221;. Check it out to add some custom code to your site!' ); ?></p>
    535 						<?php else : ?>
    536 							<p><?php _e( 'Did you know there is a &#8220;Custom HTML&#8221; widget now? You can find it by scanning the list of available widgets on this screen. Check it out to add some custom code to your site!' ); ?></p>
    537 						<?php endif; ?>
    538 						<div class="wp-pointer-buttons">
    539 							<a class="close" href="#"><?php _e( 'Dismiss' ); ?></a>
    540 						</div>
    541 					</div>
    542 					<div class="wp-pointer-arrow">
    543 						<div class="wp-pointer-arrow-inner"></div>
    544 					</div>
    545 				</div>
    546 			<?php endif; ?>
    547 
    548 			<?php if ( ! in_array( 'text_widget_paste_html', $dismissed_pointers, true ) ) : ?>
    549 				<div hidden class="wp-pointer paste-html-pointer wp-pointer-top">
    550 					<div class="wp-pointer-content">
    551 						<h3><?php _e( 'Did you just paste HTML?' ); ?></h3>
    552 						<p><?php _e( 'Hey there, looks like you just pasted HTML into the &#8220;Visual&#8221; tab of the Text widget. You may want to paste your code into the &#8220;Text&#8221; tab instead. Alternately, try out the new &#8220;Custom HTML&#8221; widget!' ); ?></p>
    553 						<div class="wp-pointer-buttons">
    554 							<a class="close" href="#"><?php _e( 'Dismiss' ); ?></a>
    555 						</div>
    556 					</div>
    557 					<div class="wp-pointer-arrow">
    558 						<div class="wp-pointer-arrow-inner"></div>
    559 					</div>
    560 				</div>
    561 			<?php endif; ?>
    562 
    563 			<p>
    564 				<label for="{{ elementIdPrefix }}text" class="screen-reader-text"><?php esc_html_e( 'Content:' ); ?></label>
    565 				<textarea id="{{ elementIdPrefix }}text" class="widefat text wp-editor-area" style="height: 200px" rows="16" cols="20"></textarea>
    566 			</p>
    567 		</script>
    568 		<?php
    569 	}
    570 }