dvadf
File manager - Edit - /home/theblueo/tv/wp-includes/pomo/lib/widgets.tar
Back
class-wp-widget-rss.php 0000666 00000007245 15214027662 0011115 0 ustar 00 <?php /** * Widget API: WP_Widget_RSS class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement a RSS widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_RSS extends WP_Widget { /** * Sets up a new RSS widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'description' => __( 'Entries from any RSS or Atom feed.' ), 'customize_selective_refresh' => true, ); $control_ops = array( 'width' => 400, 'height' => 200 ); parent::__construct( 'rss', __( 'RSS' ), $widget_ops, $control_ops ); } /** * Outputs the content for the current RSS widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current RSS widget instance. */ public function widget( $args, $instance ) { if ( isset($instance['error']) && $instance['error'] ) return; $url = ! empty( $instance['url'] ) ? $instance['url'] : ''; while ( stristr($url, 'http') != $url ) $url = substr($url, 1); if ( empty($url) ) return; // self-url destruction sequence if ( in_array( untrailingslashit( $url ), array( site_url(), home_url() ) ) ) return; $rss = fetch_feed($url); $title = $instance['title']; $desc = ''; $link = ''; if ( ! is_wp_error($rss) ) { $desc = esc_attr(strip_tags(@html_entity_decode($rss->get_description(), ENT_QUOTES, get_option('blog_charset')))); if ( empty($title) ) $title = strip_tags( $rss->get_title() ); $link = strip_tags( $rss->get_permalink() ); while ( stristr($link, 'http') != $link ) $link = substr($link, 1); } if ( empty($title) ) $title = empty($desc) ? __('Unknown Feed') : $desc; /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', $title, $instance, $this->id_base ); $url = strip_tags( $url ); $icon = includes_url( 'images/rss.png' ); if ( $title ) $title = '<a class="rsswidget" href="' . esc_url( $url ) . '"><img class="rss-widget-icon" style="border:0" width="14" height="14" src="' . esc_url( $icon ) . '" alt="RSS" /></a> <a class="rsswidget" href="' . esc_url( $link ) . '">'. esc_html( $title ) . '</a>'; echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } wp_widget_rss_output( $rss, $instance ); echo $args['after_widget']; if ( ! is_wp_error($rss) ) $rss->__destruct(); unset($rss); } /** * Handles updating settings for the current RSS widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings to save. */ public function update( $new_instance, $old_instance ) { $testurl = ( isset( $new_instance['url'] ) && ( !isset( $old_instance['url'] ) || ( $new_instance['url'] != $old_instance['url'] ) ) ); return wp_widget_rss_process( $new_instance, $testurl ); } /** * Outputs the settings form for the RSS widget. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { if ( empty( $instance ) ) { $instance = array( 'title' => '', 'url' => '', 'items' => 10, 'error' => false, 'show_summary' => 0, 'show_author' => 0, 'show_date' => 0 ); } $instance['number'] = $this->number; wp_widget_rss_form( $instance ); } } class-wp-widget-links.php 0000666 00000015507 15214027662 0011426 0 ustar 00 <?php /** * Widget API: WP_Widget_Links class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement a Links widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Links extends WP_Widget { /** * Sets up a new Links widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'description' => __( 'Your blogroll' ), 'customize_selective_refresh' => true, ); parent::__construct( 'links', __( 'Links' ), $widget_ops ); } /** * Outputs the content for the current Links widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Links widget instance. */ public function widget( $args, $instance ) { $show_description = isset($instance['description']) ? $instance['description'] : false; $show_name = isset($instance['name']) ? $instance['name'] : false; $show_rating = isset($instance['rating']) ? $instance['rating'] : false; $show_images = isset($instance['images']) ? $instance['images'] : true; $category = isset($instance['category']) ? $instance['category'] : false; $orderby = isset( $instance['orderby'] ) ? $instance['orderby'] : 'name'; $order = $orderby == 'rating' ? 'DESC' : 'ASC'; $limit = isset( $instance['limit'] ) ? $instance['limit'] : -1; $before_widget = preg_replace( '/id="[^"]*"/', 'id="%id"', $args['before_widget'] ); $widget_links_args = array( 'title_before' => $args['before_title'], 'title_after' => $args['after_title'], 'category_before' => $before_widget, 'category_after' => $args['after_widget'], 'show_images' => $show_images, 'show_description' => $show_description, 'show_name' => $show_name, 'show_rating' => $show_rating, 'category' => $category, 'class' => 'linkcat widget', 'orderby' => $orderby, 'order' => $order, 'limit' => $limit, ); /** * Filters the arguments for the Links widget. * * @since 2.6.0 * @since 4.4.0 The `$instance` parameter was added. * * @see wp_list_bookmarks() * * @param array $widget_links_args An array of arguments to retrieve the links list. * @param array $instance The settings for the particular instance of the widget. */ wp_list_bookmarks( apply_filters( 'widget_links_args', $widget_links_args, $instance ) ); } /** * Handles updating settings for the current Links widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings to save. */ public function update( $new_instance, $old_instance ) { $new_instance = (array) $new_instance; $instance = array( 'images' => 0, 'name' => 0, 'description' => 0, 'rating' => 0 ); foreach ( $instance as $field => $val ) { if ( isset($new_instance[$field]) ) $instance[$field] = 1; } $instance['orderby'] = 'name'; if ( in_array( $new_instance['orderby'], array( 'name', 'rating', 'id', 'rand' ) ) ) $instance['orderby'] = $new_instance['orderby']; $instance['category'] = intval( $new_instance['category'] ); $instance['limit'] = ! empty( $new_instance['limit'] ) ? intval( $new_instance['limit'] ) : -1; return $instance; } /** * Outputs the settings form for the Links widget. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { //Defaults $instance = wp_parse_args( (array) $instance, array( 'images' => true, 'name' => true, 'description' => false, 'rating' => false, 'category' => false, 'orderby' => 'name', 'limit' => -1 ) ); $link_cats = get_terms( 'link_category' ); if ( ! $limit = intval( $instance['limit'] ) ) $limit = -1; ?> <p> <label for="<?php echo $this->get_field_id('category'); ?>"><?php _e( 'Select Link Category:' ); ?></label> <select class="widefat" id="<?php echo $this->get_field_id('category'); ?>" name="<?php echo $this->get_field_name('category'); ?>"> <option value=""><?php _ex('All Links', 'links widget'); ?></option> <?php foreach ( $link_cats as $link_cat ) { echo '<option value="' . intval( $link_cat->term_id ) . '"' . selected( $instance['category'], $link_cat->term_id, false ) . '>' . $link_cat->name . "</option>\n"; } ?> </select> <label for="<?php echo $this->get_field_id('orderby'); ?>"><?php _e( 'Sort by:' ); ?></label> <select name="<?php echo $this->get_field_name('orderby'); ?>" id="<?php echo $this->get_field_id('orderby'); ?>" class="widefat"> <option value="name"<?php selected( $instance['orderby'], 'name' ); ?>><?php _e( 'Link title' ); ?></option> <option value="rating"<?php selected( $instance['orderby'], 'rating' ); ?>><?php _e( 'Link rating' ); ?></option> <option value="id"<?php selected( $instance['orderby'], 'id' ); ?>><?php _e( 'Link ID' ); ?></option> <option value="rand"<?php selected( $instance['orderby'], 'rand' ); ?>><?php _ex( 'Random', 'Links widget' ); ?></option> </select> </p> <p> <input class="checkbox" type="checkbox"<?php checked($instance['images'], true) ?> id="<?php echo $this->get_field_id('images'); ?>" name="<?php echo $this->get_field_name('images'); ?>" /> <label for="<?php echo $this->get_field_id('images'); ?>"><?php _e('Show Link Image'); ?></label><br /> <input class="checkbox" type="checkbox"<?php checked($instance['name'], true) ?> id="<?php echo $this->get_field_id('name'); ?>" name="<?php echo $this->get_field_name('name'); ?>" /> <label for="<?php echo $this->get_field_id('name'); ?>"><?php _e('Show Link Name'); ?></label><br /> <input class="checkbox" type="checkbox"<?php checked($instance['description'], true) ?> id="<?php echo $this->get_field_id('description'); ?>" name="<?php echo $this->get_field_name('description'); ?>" /> <label for="<?php echo $this->get_field_id('description'); ?>"><?php _e('Show Link Description'); ?></label><br /> <input class="checkbox" type="checkbox"<?php checked($instance['rating'], true) ?> id="<?php echo $this->get_field_id('rating'); ?>" name="<?php echo $this->get_field_name('rating'); ?>" /> <label for="<?php echo $this->get_field_id('rating'); ?>"><?php _e('Show Link Rating'); ?></label> </p> <p> <label for="<?php echo $this->get_field_id('limit'); ?>"><?php _e( 'Number of links to show:' ); ?></label> <input id="<?php echo $this->get_field_id('limit'); ?>" name="<?php echo $this->get_field_name('limit'); ?>" type="text" value="<?php echo $limit == -1 ? '' : intval( $limit ); ?>" size="3" /> </p> <?php } } class-wp-widget-media-video.php 0000666 00000020327 15214027662 0012465 0 ustar 00 <?php /** * Widget API: WP_Widget_Media_Video class * * @package WordPress * @subpackage Widgets * @since 4.8.0 */ /** * Core class that implements a video widget. * * @since 4.8.0 * * @see WP_Widget_Media * @see WP_Widget */ class WP_Widget_Media_Video extends WP_Widget_Media { /** * Constructor. * * @since 4.8.0 */ public function __construct() { parent::__construct( 'media_video', __( 'Video' ), array( 'description' => __( 'Displays a video from the media library or from YouTube, Vimeo, or another provider.' ), 'mime_type' => 'video', ) ); $this->l10n = array_merge( $this->l10n, array( 'no_media_selected' => __( 'No video selected' ), 'add_media' => _x( 'Add Video', 'label for button in the video widget' ), 'replace_media' => _x( 'Replace Video', 'label for button in the video widget; should preferably not be longer than ~13 characters long' ), 'edit_media' => _x( 'Edit Video', 'label for button in the video widget; should preferably not be longer than ~13 characters long' ), 'missing_attachment' => sprintf( /* translators: %s: URL to media library. */ __( 'We can’t find that video. Check your <a href="%s">media library</a> and make sure it wasn’t deleted.' ), esc_url( admin_url( 'upload.php' ) ) ), /* translators: %d: Widget count. */ 'media_library_state_multi' => _n_noop( 'Video Widget (%d)', 'Video Widget (%d)' ), 'media_library_state_single' => __( 'Video Widget' ), /* translators: %s: A list of valid video file extensions. */ 'unsupported_file_type' => sprintf( __( 'Sorry, we can’t load the video at the supplied URL. Please check that the URL is for a supported video file (%s) or stream (e.g. YouTube and Vimeo).' ), '<code>.' . implode( '</code>, <code>.', wp_get_video_extensions() ) . '</code>' ), ) ); } /** * Get schema for properties of a widget instance (item). * * @since 4.8.0 * * @see WP_REST_Controller::get_item_schema() * @see WP_REST_Controller::get_additional_fields() * @link https://core.trac.wordpress.org/ticket/35574 * * @return array Schema for properties. */ public function get_instance_schema() { $schema = array( 'preload' => array( 'type' => 'string', 'enum' => array( 'none', 'auto', 'metadata' ), 'default' => 'metadata', 'description' => __( 'Preload' ), 'should_preview_update' => false, ), 'loop' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Loop' ), 'should_preview_update' => false, ), 'content' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'wp_kses_post', 'description' => __( 'Tracks (subtitles, captions, descriptions, chapters, or metadata)' ), 'should_preview_update' => false, ), ); foreach ( wp_get_video_extensions() as $video_extension ) { $schema[ $video_extension ] = array( 'type' => 'string', 'default' => '', 'format' => 'uri', /* translators: %s: Video extension. */ 'description' => sprintf( __( 'URL to the %s video source file' ), $video_extension ), ); } return array_merge( $schema, parent::get_instance_schema() ); } /** * Render the media on the frontend. * * @since 4.8.0 * * @param array $instance Widget instance props. */ public function render_media( $instance ) { $instance = array_merge( wp_list_pluck( $this->get_instance_schema(), 'default' ), $instance ); $attachment = null; if ( $this->is_attachment_with_mime_type( $instance['attachment_id'], $this->widget_options['mime_type'] ) ) { $attachment = get_post( $instance['attachment_id'] ); } $src = $instance['url']; if ( $attachment ) { $src = wp_get_attachment_url( $attachment->ID ); } if ( empty( $src ) ) { return; } $youtube_pattern = '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#'; $vimeo_pattern = '#^https?://(.+\.)?vimeo\.com/.*#'; if ( $attachment || preg_match( $youtube_pattern, $src ) || preg_match( $vimeo_pattern, $src ) ) { add_filter( 'wp_video_shortcode', array( $this, 'inject_video_max_width_style' ) ); echo wp_video_shortcode( array_merge( $instance, compact( 'src' ) ), $instance['content'] ); remove_filter( 'wp_video_shortcode', array( $this, 'inject_video_max_width_style' ) ); } else { echo $this->inject_video_max_width_style( wp_oembed_get( $src ) ); } } /** * Inject max-width and remove height for videos too constrained to fit inside sidebars on frontend. * * @since 4.8.0 * * @param string $html Video shortcode HTML output. * @return string HTML Output. */ public function inject_video_max_width_style( $html ) { $html = preg_replace( '/\sheight="\d+"/', '', $html ); $html = preg_replace( '/\swidth="\d+"/', '', $html ); $html = preg_replace( '/(?<=width:)\s*\d+px(?=;?)/', '100%', $html ); return $html; } /** * Enqueue preview scripts. * * These scripts normally are enqueued just-in-time when a video shortcode is used. * In the customizer, however, widgets can be dynamically added and rendered via * selective refresh, and so it is important to unconditionally enqueue them in * case a widget does get added. * * @since 4.8.0 */ public function enqueue_preview_scripts() { /** This filter is documented in wp-includes/media.php */ if ( 'mediaelement' === apply_filters( 'wp_video_shortcode_library', 'mediaelement' ) ) { wp_enqueue_style( 'wp-mediaelement' ); wp_enqueue_script( 'mediaelement-vimeo' ); wp_enqueue_script( 'wp-mediaelement' ); } } /** * Loads the required scripts and styles for the widget control. * * @since 4.8.0 */ public function enqueue_admin_scripts() { parent::enqueue_admin_scripts(); $handle = 'media-video-widget'; wp_enqueue_script( $handle ); $exported_schema = array(); foreach ( $this->get_instance_schema() as $field => $field_schema ) { $exported_schema[ $field ] = wp_array_slice_assoc( $field_schema, array( 'type', 'default', 'enum', 'minimum', 'format', 'media_prop', 'should_preview_update' ) ); } wp_add_inline_script( $handle, sprintf( 'wp.mediaWidgets.modelConstructors[ %s ].prototype.schema = %s;', wp_json_encode( $this->id_base ), wp_json_encode( $exported_schema ) ) ); wp_add_inline_script( $handle, sprintf( ' wp.mediaWidgets.controlConstructors[ %1$s ].prototype.mime_type = %2$s; wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n = _.extend( {}, wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n, %3$s ); ', wp_json_encode( $this->id_base ), wp_json_encode( $this->widget_options['mime_type'] ), wp_json_encode( $this->l10n ) ) ); } /** * Render form template scripts. * * @since 4.8.0 */ public function render_control_template_scripts() { parent::render_control_template_scripts() ?> <script type="text/html" id="tmpl-wp-media-widget-video-preview"> <# if ( data.error && 'missing_attachment' === data.error ) { #> <div class="notice notice-error notice-alt notice-missing-attachment"> <p><?php echo $this->l10n['missing_attachment']; ?></p> </div> <# } else if ( data.error && 'unsupported_file_type' === data.error ) { #> <div class="notice notice-error notice-alt notice-missing-attachment"> <p><?php echo $this->l10n['unsupported_file_type']; ?></p> </div> <# } else if ( data.error ) { #> <div class="notice notice-error notice-alt"> <p><?php _e( 'Unable to preview media due to an unknown error.' ); ?></p> </div> <# } else if ( data.is_oembed && data.model.poster ) { #> <a href="{{ data.model.src }}" target="_blank" class="media-widget-video-link"> <img src="{{ data.model.poster }}" /> </a> <# } else if ( data.is_oembed ) { #> <a href="{{ data.model.src }}" target="_blank" class="media-widget-video-link no-poster"> <span class="dashicons dashicons-format-video"></span> </a> <# } else if ( data.model.src ) { #> <?php wp_underscore_video_template(); ?> <# } #> </script> <?php } } class-wp-widget-recent-posts.php 0000666 00000011330 15214027662 0012722 0 ustar 00 <?php /** * Widget API: WP_Widget_Recent_Posts class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement a Recent Posts widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Recent_Posts extends WP_Widget { /** * Sets up a new Recent Posts widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'classname' => 'widget_recent_entries', 'description' => __( 'Your site’s most recent Posts.' ), 'customize_selective_refresh' => true, ); parent::__construct( 'recent-posts', __( 'Recent Posts' ), $widget_ops ); $this->alt_option_name = 'widget_recent_entries'; } /** * Outputs the content for the current Recent Posts widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Recent Posts widget instance. */ public function widget( $args, $instance ) { if ( ! isset( $args['widget_id'] ) ) { $args['widget_id'] = $this->id; } $title = ( ! empty( $instance['title'] ) ) ? $instance['title'] : __( 'Recent Posts' ); /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', $title, $instance, $this->id_base ); $number = ( ! empty( $instance['number'] ) ) ? absint( $instance['number'] ) : 5; if ( ! $number ) $number = 5; $show_date = isset( $instance['show_date'] ) ? $instance['show_date'] : false; /** * Filters the arguments for the Recent Posts widget. * * @since 3.4.0 * * @see WP_Query::get_posts() * * @param array $args An array of arguments used to retrieve the recent posts. */ $r = new WP_Query( apply_filters( 'widget_posts_args', array( 'posts_per_page' => $number, 'no_found_rows' => true, 'post_status' => 'publish', 'ignore_sticky_posts' => true ) ) ); if ($r->have_posts()) : ?> <?php echo $args['before_widget']; ?> <?php if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } ?> <ul> <?php while ( $r->have_posts() ) : $r->the_post(); ?> <li> <a href="<?php the_permalink(); ?>"><?php get_the_title() ? the_title() : the_ID(); ?></a> <?php if ( $show_date ) : ?> <span class="post-date"><?php echo get_the_date(); ?></span> <?php endif; ?> </li> <?php endwhile; ?> </ul> <?php echo $args['after_widget']; ?> <?php // Reset the global $the_post as this query will have stomped on it wp_reset_postdata(); endif; } /** * Handles updating the settings for the current Recent Posts widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings to save. */ public function update( $new_instance, $old_instance ) { $instance = $old_instance; $instance['title'] = sanitize_text_field( $new_instance['title'] ); $instance['number'] = (int) $new_instance['number']; $instance['show_date'] = isset( $new_instance['show_date'] ) ? (bool) $new_instance['show_date'] : false; return $instance; } /** * Outputs the settings form for the Recent Posts widget. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { $title = isset( $instance['title'] ) ? esc_attr( $instance['title'] ) : ''; $number = isset( $instance['number'] ) ? absint( $instance['number'] ) : 5; $show_date = isset( $instance['show_date'] ) ? (bool) $instance['show_date'] : false; ?> <p><label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label> <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo $title; ?>" /></p> <p><label for="<?php echo $this->get_field_id( 'number' ); ?>"><?php _e( 'Number of posts to show:' ); ?></label> <input class="tiny-text" id="<?php echo $this->get_field_id( 'number' ); ?>" name="<?php echo $this->get_field_name( 'number' ); ?>" type="number" step="1" min="1" value="<?php echo $number; ?>" size="3" /></p> <p><input class="checkbox" type="checkbox"<?php checked( $show_date ); ?> id="<?php echo $this->get_field_id( 'show_date' ); ?>" name="<?php echo $this->get_field_name( 'show_date' ); ?>" /> <label for="<?php echo $this->get_field_id( 'show_date' ); ?>"><?php _e( 'Display post date?' ); ?></label></p> <?php } } class-wp-widget-media-image.php 0000666 00000027317 15214027662 0012447 0 ustar 00 <?php /** * Widget API: WP_Widget_Media_Image class * * @package WordPress * @subpackage Widgets * @since 4.8.0 */ /** * Core class that implements an image widget. * * @since 4.8.0 * * @see WP_Widget_Media * @see WP_Widget */ class WP_Widget_Media_Image extends WP_Widget_Media { /** * Constructor. * * @since 4.8.0 */ public function __construct() { parent::__construct( 'media_image', __( 'Image' ), array( 'description' => __( 'Displays an image.' ), 'mime_type' => 'image', ) ); $this->l10n = array_merge( $this->l10n, array( 'no_media_selected' => __( 'No image selected' ), 'add_media' => _x( 'Add Image', 'label for button in the image widget' ), 'replace_media' => _x( 'Replace Image', 'label for button in the image widget; should preferably not be longer than ~13 characters long' ), 'edit_media' => _x( 'Edit Image', 'label for button in the image widget; should preferably not be longer than ~13 characters long' ), 'missing_attachment' => sprintf( /* translators: %s: URL to media library. */ __( 'We can’t find that image. Check your <a href="%s">media library</a> and make sure it wasn’t deleted.' ), esc_url( admin_url( 'upload.php' ) ) ), /* translators: %d: Widget count. */ 'media_library_state_multi' => _n_noop( 'Image Widget (%d)', 'Image Widget (%d)' ), 'media_library_state_single' => __( 'Image Widget' ), ) ); } /** * Get schema for properties of a widget instance (item). * * @since 4.8.0 * * @see WP_REST_Controller::get_item_schema() * @see WP_REST_Controller::get_additional_fields() * @link https://core.trac.wordpress.org/ticket/35574 * * @return array Schema for properties. */ public function get_instance_schema() { return array_merge( array( 'size' => array( 'type' => 'string', 'enum' => array_merge( get_intermediate_image_sizes(), array( 'full', 'custom' ) ), 'default' => 'medium', 'description' => __( 'Size' ), ), 'width' => array( // Via 'customWidth', only when size=custom; otherwise via 'width'. 'type' => 'integer', 'minimum' => 0, 'default' => 0, 'description' => __( 'Width' ), ), 'height' => array( // Via 'customHeight', only when size=custom; otherwise via 'height'. 'type' => 'integer', 'minimum' => 0, 'default' => 0, 'description' => __( 'Height' ), ), 'caption' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'wp_kses_post', 'description' => __( 'Caption' ), 'should_preview_update' => false, ), 'alt' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field', 'description' => __( 'Alternative Text' ), ), 'link_type' => array( 'type' => 'string', 'enum' => array( 'none', 'file', 'post', 'custom' ), 'default' => 'custom', 'media_prop' => 'link', 'description' => __( 'Link To' ), 'should_preview_update' => true, ), 'link_url' => array( 'type' => 'string', 'default' => '', 'format' => 'uri', 'media_prop' => 'linkUrl', 'description' => __( 'URL' ), 'should_preview_update' => true, ), 'image_classes' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => array( $this, 'sanitize_token_list' ), 'media_prop' => 'extraClasses', 'description' => __( 'Image CSS Class' ), 'should_preview_update' => false, ), 'link_classes' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => array( $this, 'sanitize_token_list' ), 'media_prop' => 'linkClassName', 'should_preview_update' => false, 'description' => __( 'Link CSS Class' ), ), 'link_rel' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => array( $this, 'sanitize_token_list' ), 'media_prop' => 'linkRel', 'description' => __( 'Link Rel' ), 'should_preview_update' => false, ), 'link_target_blank' => array( 'type' => 'boolean', 'default' => false, 'media_prop' => 'linkTargetBlank', 'description' => __( 'Open link in a new tab' ), 'should_preview_update' => false, ), 'image_title' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field', 'media_prop' => 'title', 'description' => __( 'Image Title Attribute' ), 'should_preview_update' => false, ), /* * There are two additional properties exposed by the PostImage modal * that don't seem to be relevant, as they may only be derived read-only * values: * - originalUrl * - aspectRatio * - height (redundant when size is not custom) * - width (redundant when size is not custom) */ ), parent::get_instance_schema() ); } /** * Render the media on the frontend. * * @since 4.8.0 * * @param array $instance Widget instance props. */ public function render_media( $instance ) { $instance = array_merge( wp_list_pluck( $this->get_instance_schema(), 'default' ), $instance ); $instance = wp_parse_args( $instance, array( 'size' => 'thumbnail', ) ); $attachment = null; if ( $this->is_attachment_with_mime_type( $instance['attachment_id'], $this->widget_options['mime_type'] ) ) { $attachment = get_post( $instance['attachment_id'] ); } if ( $attachment ) { $caption = ''; if ( ! isset( $instance['caption'] ) ) { $caption = $attachment->post_excerpt; } elseif ( trim( $instance['caption'] ) ) { $caption = $instance['caption']; } $image_attributes = array( 'class' => sprintf( 'image wp-image-%d %s', $attachment->ID, $instance['image_classes'] ), 'style' => 'max-width: 100%; height: auto;', ); if ( ! empty( $instance['image_title'] ) ) { $image_attributes['title'] = $instance['image_title']; } if ( $instance['alt'] ) { $image_attributes['alt'] = $instance['alt']; } $size = $instance['size']; if ( 'custom' === $size || ! in_array( $size, array_merge( get_intermediate_image_sizes(), array( 'full' ) ), true ) ) { $size = array( $instance['width'], $instance['height'] ); } $image_attributes['class'] .= sprintf( ' attachment-%1$s size-%1$s', is_array( $size ) ? join( 'x', $size ) : $size ); $image = wp_get_attachment_image( $attachment->ID, $size, false, $image_attributes ); $caption_size = _wp_get_image_size_from_meta( $instance['size'], wp_get_attachment_metadata( $attachment->ID ) ); $width = empty( $caption_size[0] ) ? 0 : $caption_size[0]; } else { if ( empty( $instance['url'] ) ) { return; } $instance['size'] = 'custom'; $caption = $instance['caption']; $width = $instance['width']; $classes = 'image ' . $instance['image_classes']; if ( 0 === $instance['width'] ) { $instance['width'] = ''; } if ( 0 === $instance['height'] ) { $instance['height'] = ''; } $image = sprintf( '<img class="%1$s" src="%2$s" alt="%3$s" width="%4$s" height="%5$s" />', esc_attr( $classes ), esc_url( $instance['url'] ), esc_attr( $instance['alt'] ), esc_attr( $instance['width'] ), esc_attr( $instance['height'] ) ); } // End if(). $url = ''; if ( 'file' === $instance['link_type'] ) { $url = $attachment ? wp_get_attachment_url( $attachment->ID ) : $instance['url']; } elseif ( $attachment && 'post' === $instance['link_type'] ) { $url = get_attachment_link( $attachment->ID ); } elseif ( 'custom' === $instance['link_type'] && ! empty( $instance['link_url'] ) ) { $url = $instance['link_url']; } if ( $url ) { $link = sprintf( '<a href="%s"', esc_url( $url ) ); if ( ! empty( $instance['link_classes'] ) ) { $link .= sprintf( ' class="%s"', esc_attr( $instance['link_classes'] ) ); } if ( ! empty( $instance['link_rel'] ) ) { $link .= sprintf( ' rel="%s"', esc_attr( $instance['link_rel'] ) ); } if ( ! empty( $instance['link_target_blank'] ) ) { $link .= ' target="_blank"'; } $link .= '>'; $link .= $image; $link .= '</a>'; $image = wp_targeted_link_rel( $link ); } if ( $caption ) { $image = img_caption_shortcode( array( 'width' => $width, 'caption' => $caption, ), $image ); } echo $image; } /** * Loads the required media files for the media manager and scripts for media widgets. * * @since 4.8.0 */ public function enqueue_admin_scripts() { parent::enqueue_admin_scripts(); $handle = 'media-image-widget'; wp_enqueue_script( $handle ); $exported_schema = array(); foreach ( $this->get_instance_schema() as $field => $field_schema ) { $exported_schema[ $field ] = wp_array_slice_assoc( $field_schema, array( 'type', 'default', 'enum', 'minimum', 'format', 'media_prop', 'should_preview_update' ) ); } wp_add_inline_script( $handle, sprintf( 'wp.mediaWidgets.modelConstructors[ %s ].prototype.schema = %s;', wp_json_encode( $this->id_base ), wp_json_encode( $exported_schema ) ) ); wp_add_inline_script( $handle, sprintf( ' wp.mediaWidgets.controlConstructors[ %1$s ].prototype.mime_type = %2$s; wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n = _.extend( {}, wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n, %3$s ); ', wp_json_encode( $this->id_base ), wp_json_encode( $this->widget_options['mime_type'] ), wp_json_encode( $this->l10n ) ) ); } /** * Render form template scripts. * * @since 4.8.0 */ public function render_control_template_scripts() { parent::render_control_template_scripts(); ?> <script type="text/html" id="tmpl-wp-media-widget-image-fields"> <# var elementIdPrefix = 'el' + String( Math.random() ) + '_'; #> <# if ( data.url ) { #> <p class="media-widget-image-link"> <label for="{{ elementIdPrefix }}linkUrl"><?php esc_html_e( 'Link to:' ); ?></label> <input id="{{ elementIdPrefix }}linkUrl" type="text" class="widefat link" value="{{ data.link_url }}" placeholder="https://" pattern="((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#).*"> </p> <# } #> </script> <script type="text/html" id="tmpl-wp-media-widget-image-preview"> <# if ( data.error && 'missing_attachment' === data.error ) { #> <div class="notice notice-error notice-alt notice-missing-attachment"> <p><?php echo $this->l10n['missing_attachment']; ?></p> </div> <# } else if ( data.error ) { #> <div class="notice notice-error notice-alt"> <p><?php _e( 'Unable to preview media due to an unknown error.' ); ?></p> </div> <# } else if ( data.url ) { #> <img class="attachment-thumb" src="{{ data.url }}" draggable="false" alt="{{ data.alt }}" <# if ( ! data.alt && data.currentFilename ) { #> aria-label=" <?php echo esc_attr( sprintf( /* translators: %s: The image file name. */ __( 'The current image has no alternative text. The file name is: %s' ), '{{ data.currentFilename }}' ) ); ?> " <# } #> /> <# } #> </script> <?php } } class-wp-widget-categories.php 0000666 00000013050 15214027662 0012422 0 ustar 00 <?php /** * Widget API: WP_Widget_Categories class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement a Categories widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Categories extends WP_Widget { /** * Sets up a new Categories widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'classname' => 'widget_categories', 'description' => __( 'A list or dropdown of categories.' ), 'customize_selective_refresh' => true, ); parent::__construct( 'categories', __( 'Categories' ), $widget_ops ); } /** * Outputs the content for the current Categories widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Categories widget instance. */ public function widget( $args, $instance ) { static $first_dropdown = true; /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? __( 'Categories' ) : $instance['title'], $instance, $this->id_base ); $c = ! empty( $instance['count'] ) ? '1' : '0'; $h = ! empty( $instance['hierarchical'] ) ? '1' : '0'; $d = ! empty( $instance['dropdown'] ) ? '1' : '0'; echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } $cat_args = array( 'orderby' => 'name', 'show_count' => $c, 'hierarchical' => $h ); if ( $d ) { $dropdown_id = ( $first_dropdown ) ? 'cat' : "{$this->id_base}-dropdown-{$this->number}"; $first_dropdown = false; echo '<label class="screen-reader-text" for="' . esc_attr( $dropdown_id ) . '">' . $title . '</label>'; $cat_args['show_option_none'] = __( 'Select Category' ); $cat_args['id'] = $dropdown_id; /** * Filters the arguments for the Categories widget drop-down. * * @since 2.8.0 * * @see wp_dropdown_categories() * * @param array $cat_args An array of Categories widget drop-down arguments. */ wp_dropdown_categories( apply_filters( 'widget_categories_dropdown_args', $cat_args ) ); ?> <script type='text/javascript'> /* <![CDATA[ */ (function() { var dropdown = document.getElementById( "<?php echo esc_js( $dropdown_id ); ?>" ); function onCatChange() { if ( dropdown.options[ dropdown.selectedIndex ].value > 0 ) { location.href = "<?php echo home_url(); ?>/?cat=" + dropdown.options[ dropdown.selectedIndex ].value; } } dropdown.onchange = onCatChange; })(); /* ]]> */ </script> <?php } else { ?> <ul> <?php $cat_args['title_li'] = ''; /** * Filters the arguments for the Categories widget. * * @since 2.8.0 * * @param array $cat_args An array of Categories widget options. */ wp_list_categories( apply_filters( 'widget_categories_args', $cat_args ) ); ?> </ul> <?php } echo $args['after_widget']; } /** * Handles updating settings for the current Categories widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings to save. */ public function update( $new_instance, $old_instance ) { $instance = $old_instance; $instance['title'] = sanitize_text_field( $new_instance['title'] ); $instance['count'] = !empty($new_instance['count']) ? 1 : 0; $instance['hierarchical'] = !empty($new_instance['hierarchical']) ? 1 : 0; $instance['dropdown'] = !empty($new_instance['dropdown']) ? 1 : 0; return $instance; } /** * Outputs the settings form for the Categories widget. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { //Defaults $instance = wp_parse_args( (array) $instance, array( 'title' => '') ); $title = sanitize_text_field( $instance['title'] ); $count = isset($instance['count']) ? (bool) $instance['count'] :false; $hierarchical = isset( $instance['hierarchical'] ) ? (bool) $instance['hierarchical'] : false; $dropdown = isset( $instance['dropdown'] ) ? (bool) $instance['dropdown'] : false; ?> <p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e( 'Title:' ); ?></label> <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( $title ); ?>" /></p> <p><input type="checkbox" class="checkbox" id="<?php echo $this->get_field_id('dropdown'); ?>" name="<?php echo $this->get_field_name('dropdown'); ?>"<?php checked( $dropdown ); ?> /> <label for="<?php echo $this->get_field_id('dropdown'); ?>"><?php _e( 'Display as dropdown' ); ?></label><br /> <input type="checkbox" class="checkbox" id="<?php echo $this->get_field_id('count'); ?>" name="<?php echo $this->get_field_name('count'); ?>"<?php checked( $count ); ?> /> <label for="<?php echo $this->get_field_id('count'); ?>"><?php _e( 'Show post counts' ); ?></label><br /> <input type="checkbox" class="checkbox" id="<?php echo $this->get_field_id('hierarchical'); ?>" name="<?php echo $this->get_field_name('hierarchical'); ?>"<?php checked( $hierarchical ); ?> /> <label for="<?php echo $this->get_field_id('hierarchical'); ?>"><?php _e( 'Show hierarchy' ); ?></label></p> <?php } } class-wp-widget-text.php 0000666 00000010026 15214027662 0011261 0 ustar 00 <?php /** * Widget API: WP_Widget_Text class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement a Text widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Text extends WP_Widget { /** * Sets up a new Text widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'classname' => 'widget_text', 'description' => __( 'Arbitrary text or HTML.' ), 'customize_selective_refresh' => true, ); $control_ops = array( 'width' => 400, 'height' => 350 ); parent::__construct( 'text', __( 'Text' ), $widget_ops, $control_ops ); } /** * Outputs the content for the current Text widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Text widget instance. */ public function widget( $args, $instance ) { /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base ); $widget_text = ! empty( $instance['text'] ) ? $instance['text'] : ''; /** * Filters the content of the Text widget. * * @since 2.3.0 * @since 4.4.0 Added the `$this` parameter. * * @param string $widget_text The widget content. * @param array $instance Array of settings for the current widget. * @param WP_Widget_Text $this Current Text widget instance. */ $text = apply_filters( 'widget_text', $widget_text, $instance, $this ); echo $args['before_widget']; if ( ! empty( $title ) ) { echo $args['before_title'] . $title . $args['after_title']; } ?> <div class="textwidget"><?php echo !empty( $instance['filter'] ) ? wpautop( $text ) : $text; ?></div> <?php echo $args['after_widget']; } /** * Handles updating settings for the current Text widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Settings to save or bool false to cancel saving. */ public function update( $new_instance, $old_instance ) { $instance = $old_instance; $instance['title'] = sanitize_text_field( $new_instance['title'] ); if ( current_user_can( 'unfiltered_html' ) ) { $instance['text'] = $new_instance['text']; } else { $instance['text'] = wp_kses_post( $new_instance['text'] ); } $instance['filter'] = ! empty( $new_instance['filter'] ); return $instance; } /** * Outputs the Text widget settings form. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, array( 'title' => '', 'text' => '' ) ); $filter = isset( $instance['filter'] ) ? $instance['filter'] : 0; $title = sanitize_text_field( $instance['title'] ); ?> <p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label> <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($title); ?>" /></p> <p><label for="<?php echo $this->get_field_id( 'text' ); ?>"><?php _e( 'Content:' ); ?></label> <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></p> <p><input id="<?php echo $this->get_field_id('filter'); ?>" name="<?php echo $this->get_field_name('filter'); ?>" type="checkbox"<?php checked( $filter ); ?> /> <label for="<?php echo $this->get_field_id('filter'); ?>"><?php _e('Automatically add paragraphs'); ?></label></p> <?php } } class-wp-widget-archives.php 0000666 00000012117 15214027662 0012104 0 ustar 00 <?php /** * Widget API: WP_Widget_Archives class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement the Archives widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Archives extends WP_Widget { /** * Sets up a new Archives widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'classname' => 'widget_archive', 'description' => __( 'A monthly archive of your site’s Posts.' ), 'customize_selective_refresh' => true, ); parent::__construct('archives', __('Archives'), $widget_ops); } /** * Outputs the content for the current Archives widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Archives widget instance. */ public function widget( $args, $instance ) { $c = ! empty( $instance['count'] ) ? '1' : '0'; $d = ! empty( $instance['dropdown'] ) ? '1' : '0'; /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? __( 'Archives' ) : $instance['title'], $instance, $this->id_base ); echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } if ( $d ) { $dropdown_id = "{$this->id_base}-dropdown-{$this->number}"; ?> <label class="screen-reader-text" for="<?php echo esc_attr( $dropdown_id ); ?>"><?php echo $title; ?></label> <select id="<?php echo esc_attr( $dropdown_id ); ?>" name="archive-dropdown" onchange='document.location.href=this.options[this.selectedIndex].value;'> <?php /** * Filters the arguments for the Archives widget drop-down. * * @since 2.8.0 * * @see wp_get_archives() * * @param array $args An array of Archives widget drop-down arguments. */ $dropdown_args = apply_filters( 'widget_archives_dropdown_args', array( 'type' => 'monthly', 'format' => 'option', 'show_post_count' => $c ) ); switch ( $dropdown_args['type'] ) { case 'yearly': $label = __( 'Select Year' ); break; case 'monthly': $label = __( 'Select Month' ); break; case 'daily': $label = __( 'Select Day' ); break; case 'weekly': $label = __( 'Select Week' ); break; default: $label = __( 'Select Post' ); break; } ?> <option value=""><?php echo esc_attr( $label ); ?></option> <?php wp_get_archives( $dropdown_args ); ?> </select> <?php } else { ?> <ul> <?php /** * Filters the arguments for the Archives widget. * * @since 2.8.0 * * @see wp_get_archives() * * @param array $args An array of Archives option arguments. */ wp_get_archives( apply_filters( 'widget_archives_args', array( 'type' => 'monthly', 'show_post_count' => $c ) ) ); ?> </ul> <?php } echo $args['after_widget']; } /** * Handles updating settings for the current Archives widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget_Archives::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings to save. */ public function update( $new_instance, $old_instance ) { $instance = $old_instance; $new_instance = wp_parse_args( (array) $new_instance, array( 'title' => '', 'count' => 0, 'dropdown' => '') ); $instance['title'] = sanitize_text_field( $new_instance['title'] ); $instance['count'] = $new_instance['count'] ? 1 : 0; $instance['dropdown'] = $new_instance['dropdown'] ? 1 : 0; return $instance; } /** * Outputs the settings form for the Archives widget. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, array( 'title' => '', 'count' => 0, 'dropdown' => '') ); $title = sanitize_text_field( $instance['title'] ); ?> <p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label> <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($title); ?>" /></p> <p> <input class="checkbox" type="checkbox"<?php checked( $instance['dropdown'] ); ?> id="<?php echo $this->get_field_id('dropdown'); ?>" name="<?php echo $this->get_field_name('dropdown'); ?>" /> <label for="<?php echo $this->get_field_id('dropdown'); ?>"><?php _e('Display as dropdown'); ?></label> <br/> <input class="checkbox" type="checkbox"<?php checked( $instance['count'] ); ?> id="<?php echo $this->get_field_id('count'); ?>" name="<?php echo $this->get_field_name('count'); ?>" /> <label for="<?php echo $this->get_field_id('count'); ?>"><?php _e('Show post counts'); ?></label> </p> <?php } } class-wp-nav-menu-widget.php 0000666 00000012410 15214027662 0012022 0 ustar 00 <?php /** * Widget API: WP_Nav_Menu_Widget class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement the Custom Menu widget. * * @since 3.0.0 * * @see WP_Widget */ class WP_Nav_Menu_Widget extends WP_Widget { /** * Sets up a new Custom Menu widget instance. * * @since 3.0.0 * @access public */ public function __construct() { $widget_ops = array( 'description' => __( 'Add a custom menu to your sidebar.' ), 'customize_selective_refresh' => true, ); parent::__construct( 'nav_menu', __('Custom Menu'), $widget_ops ); } /** * Outputs the content for the current Custom Menu widget instance. * * @since 3.0.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Custom Menu widget instance. */ public function widget( $args, $instance ) { // Get menu $nav_menu = ! empty( $instance['nav_menu'] ) ? wp_get_nav_menu_object( $instance['nav_menu'] ) : false; if ( !$nav_menu ) return; /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $instance['title'] = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base ); echo $args['before_widget']; if ( !empty($instance['title']) ) echo $args['before_title'] . $instance['title'] . $args['after_title']; $nav_menu_args = array( 'fallback_cb' => '', 'menu' => $nav_menu ); /** * Filters the arguments for the Custom Menu widget. * * @since 4.2.0 * @since 4.4.0 Added the `$instance` parameter. * * @param array $nav_menu_args { * An array of arguments passed to wp_nav_menu() to retrieve a custom menu. * * @type callable|bool $fallback_cb Callback to fire if the menu doesn't exist. Default empty. * @type mixed $menu Menu ID, slug, or name. * } * @param WP_Term $nav_menu Nav menu object for the current menu. * @param array $args Display arguments for the current widget. * @param array $instance Array of settings for the current widget. */ wp_nav_menu( apply_filters( 'widget_nav_menu_args', $nav_menu_args, $nav_menu, $args, $instance ) ); echo $args['after_widget']; } /** * Handles updating settings for the current Custom Menu widget instance. * * @since 3.0.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings to save. */ public function update( $new_instance, $old_instance ) { $instance = array(); if ( ! empty( $new_instance['title'] ) ) { $instance['title'] = sanitize_text_field( $new_instance['title'] ); } if ( ! empty( $new_instance['nav_menu'] ) ) { $instance['nav_menu'] = (int) $new_instance['nav_menu']; } return $instance; } /** * Outputs the settings form for the Custom Menu widget. * * @since 3.0.0 * @access public * * @param array $instance Current settings. * @global WP_Customize_Manager $wp_customize */ public function form( $instance ) { global $wp_customize; $title = isset( $instance['title'] ) ? $instance['title'] : ''; $nav_menu = isset( $instance['nav_menu'] ) ? $instance['nav_menu'] : ''; // Get menus $menus = wp_get_nav_menus(); // If no menus exists, direct the user to go and create some. ?> <p class="nav-menu-widget-no-menus-message" <?php if ( ! empty( $menus ) ) { echo ' style="display:none" '; } ?>> <?php if ( $wp_customize instanceof WP_Customize_Manager ) { $url = 'javascript: wp.customize.panel( "nav_menus" ).focus();'; } else { $url = admin_url( 'nav-menus.php' ); } ?> <?php echo sprintf( __( 'No menus have been created yet. <a href="%s">Create some</a>.' ), esc_attr( $url ) ); ?> </p> <div class="nav-menu-widget-form-controls" <?php if ( empty( $menus ) ) { echo ' style="display:none" '; } ?>> <p> <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ) ?></label> <input type="text" class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" value="<?php echo esc_attr( $title ); ?>"/> </p> <p> <label for="<?php echo $this->get_field_id( 'nav_menu' ); ?>"><?php _e( 'Select Menu:' ); ?></label> <select id="<?php echo $this->get_field_id( 'nav_menu' ); ?>" name="<?php echo $this->get_field_name( 'nav_menu' ); ?>"> <option value="0"><?php _e( '— Select —' ); ?></option> <?php foreach ( $menus as $menu ) : ?> <option value="<?php echo esc_attr( $menu->term_id ); ?>" <?php selected( $nav_menu, $menu->term_id ); ?>> <?php echo esc_html( $menu->name ); ?> </option> <?php endforeach; ?> </select> </p> <?php if ( $wp_customize instanceof WP_Customize_Manager ) : ?> <p class="edit-selected-nav-menu" style="<?php if ( ! $nav_menu ) { echo 'display: none;'; } ?>"> <button type="button" class="button"><?php _e( 'Edit Menu' ) ?></button> </p> <?php endif; ?> </div> <?php } } class-wp-widget-search.php 0000666 00000005146 15214027662 0011551 0 ustar 00 <?php /** * Widget API: WP_Widget_Search class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement a Search widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Search extends WP_Widget { /** * Sets up a new Search widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'classname' => 'widget_search', 'description' => __( 'A search form for your site.' ), 'customize_selective_refresh' => true, ); parent::__construct( 'search', _x( 'Search', 'Search widget' ), $widget_ops ); } /** * Outputs the content for the current Search widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Search widget instance. */ public function widget( $args, $instance ) { /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base ); echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } // Use current theme search form if it exists get_search_form(); echo $args['after_widget']; } /** * Outputs the settings form for the Search widget. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, array( 'title' => '') ); $title = $instance['title']; ?> <p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?> <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($title); ?>" /></label></p> <?php } /** * Handles updating settings for the current Search widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings. */ public function update( $new_instance, $old_instance ) { $instance = $old_instance; $new_instance = wp_parse_args((array) $new_instance, array( 'title' => '')); $instance['title'] = sanitize_text_field( $new_instance['title'] ); return $instance; } } class-wp-widget-media-gallery.php 0000666 00000016166 15214027662 0013024 0 ustar 00 <?php /** * Widget API: WP_Widget_Media_Gallery class * * @package WordPress * @subpackage Widgets * @since 4.9.0 */ /** * Core class that implements a gallery widget. * * @since 4.9.0 * * @see WP_Widget_Media * @see WP_Widget */ class WP_Widget_Media_Gallery extends WP_Widget_Media { /** * Constructor. * * @since 4.9.0 */ public function __construct() { parent::__construct( 'media_gallery', __( 'Gallery' ), array( 'description' => __( 'Displays an image gallery.' ), 'mime_type' => 'image', ) ); $this->l10n = array_merge( $this->l10n, array( 'no_media_selected' => __( 'No images selected' ), 'add_media' => _x( 'Add Images', 'label for button in the gallery widget; should not be longer than ~13 characters long' ), 'replace_media' => '', 'edit_media' => _x( 'Edit Gallery', 'label for button in the gallery widget; should not be longer than ~13 characters long' ), ) ); } /** * Get schema for properties of a widget instance (item). * * @since 4.9.0 * * @see WP_REST_Controller::get_item_schema() * @see WP_REST_Controller::get_additional_fields() * @link https://core.trac.wordpress.org/ticket/35574 * * @return array Schema for properties. */ public function get_instance_schema() { $schema = array( 'title' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field', 'description' => __( 'Title for the widget' ), 'should_preview_update' => false, ), 'ids' => array( 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ), 'columns' => array( 'type' => 'integer', 'default' => 3, 'minimum' => 1, 'maximum' => 9, ), 'size' => array( 'type' => 'string', 'enum' => array_merge( get_intermediate_image_sizes(), array( 'full', 'custom' ) ), 'default' => 'thumbnail', ), 'link_type' => array( 'type' => 'string', 'enum' => array( 'post', 'file', 'none' ), 'default' => 'post', 'media_prop' => 'link', 'should_preview_update' => false, ), 'orderby_random' => array( 'type' => 'boolean', 'default' => false, 'media_prop' => '_orderbyRandom', 'should_preview_update' => false, ), ); /** This filter is documented in wp-includes/widgets/class-wp-widget-media.php */ $schema = apply_filters( "widget_{$this->id_base}_instance_schema", $schema, $this ); return $schema; } /** * Render the media on the frontend. * * @since 4.9.0 * * @param array $instance Widget instance props. */ public function render_media( $instance ) { $instance = array_merge( wp_list_pluck( $this->get_instance_schema(), 'default' ), $instance ); $shortcode_atts = array_merge( $instance, array( 'link' => $instance['link_type'], ) ); // @codeCoverageIgnoreStart if ( $instance['orderby_random'] ) { $shortcode_atts['orderby'] = 'rand'; } // @codeCoverageIgnoreEnd echo gallery_shortcode( $shortcode_atts ); } /** * Loads the required media files for the media manager and scripts for media widgets. * * @since 4.9.0 */ public function enqueue_admin_scripts() { parent::enqueue_admin_scripts(); $handle = 'media-gallery-widget'; wp_enqueue_script( $handle ); $exported_schema = array(); foreach ( $this->get_instance_schema() as $field => $field_schema ) { $exported_schema[ $field ] = wp_array_slice_assoc( $field_schema, array( 'type', 'default', 'enum', 'minimum', 'format', 'media_prop', 'should_preview_update', 'items' ) ); } wp_add_inline_script( $handle, sprintf( 'wp.mediaWidgets.modelConstructors[ %s ].prototype.schema = %s;', wp_json_encode( $this->id_base ), wp_json_encode( $exported_schema ) ) ); wp_add_inline_script( $handle, sprintf( ' wp.mediaWidgets.controlConstructors[ %1$s ].prototype.mime_type = %2$s; _.extend( wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n, %3$s ); ', wp_json_encode( $this->id_base ), wp_json_encode( $this->widget_options['mime_type'] ), wp_json_encode( $this->l10n ) ) ); } /** * Render form template scripts. * * @since 4.9.0 */ public function render_control_template_scripts() { parent::render_control_template_scripts(); ?> <script type="text/html" id="tmpl-wp-media-widget-gallery-preview"> <# var ids = _.filter( data.ids, function( id ) { return ( id in data.attachments ); } ); #> <# if ( ids.length ) { #> <ul class="gallery media-widget-gallery-preview" role="list"> <# _.each( ids, function( id, index ) { #> <# var attachment = data.attachments[ id ]; #> <# if ( index < 6 ) { #> <li class="gallery-item"> <div class="gallery-icon"> <img alt="{{ attachment.alt }}" <# if ( index === 5 && data.ids.length > 6 ) { #> aria-hidden="true" <# } #> <# if ( attachment.sizes.thumbnail ) { #> src="{{ attachment.sizes.thumbnail.url }}" width="{{ attachment.sizes.thumbnail.width }}" height="{{ attachment.sizes.thumbnail.height }}" <# } else { #> src="{{ attachment.url }}" <# } #> <# if ( ! attachment.alt && attachment.filename ) { #> aria-label=" <?php echo esc_attr( sprintf( /* translators: %s: The image file name. */ __( 'The current image has no alternative text. The file name is: %s' ), '{{ attachment.filename }}' ) ); ?> " <# } #> /> <# if ( index === 5 && data.ids.length > 6 ) { #> <div class="gallery-icon-placeholder"> <p class="gallery-icon-placeholder-text" aria-label=" <?php printf( /* translators: %s: The amount of additional, not visible images in the gallery widget preview. */ __( 'Additional images added to this gallery: %s' ), '{{ data.ids.length - 5 }}' ); ?> ">+{{ data.ids.length - 5 }}</p> </div> <# } #> </div> </li> <# } #> <# } ); #> </ul> <# } else { #> <div class="attachment-media-view"> <button type="button" class="placeholder button-add-media"><?php echo esc_html( $this->l10n['add_media'] ); ?></button> </div> <# } #> </script> <?php } /** * Whether the widget has content to show. * * @since 4.9.0 * @access protected * * @param array $instance Widget instance props. * @return bool Whether widget has content. */ protected function has_content( $instance ) { if ( ! empty( $instance['ids'] ) ) { $attachments = wp_parse_id_list( $instance['ids'] ); foreach ( $attachments as $attachment ) { if ( 'attachment' !== get_post_type( $attachment ) ) { return false; } } return true; } return false; } } class-wp-widget-tag-cloud.php 0000666 00000011700 15214027662 0012154 0 ustar 00 <?php /** * Widget API: WP_Widget_Tag_Cloud class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement a Tag cloud widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Tag_Cloud extends WP_Widget { /** * Sets up a new Tag Cloud widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'description' => __( 'A cloud of your most used tags.' ), 'customize_selective_refresh' => true, ); parent::__construct( 'tag_cloud', __( 'Tag Cloud' ), $widget_ops ); } /** * Outputs the content for the current Tag Cloud widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Tag Cloud widget instance. */ public function widget( $args, $instance ) { $current_taxonomy = $this->_get_current_taxonomy($instance); if ( !empty($instance['title']) ) { $title = $instance['title']; } else { if ( 'post_tag' == $current_taxonomy ) { $title = __('Tags'); } else { $tax = get_taxonomy($current_taxonomy); $title = $tax->labels->name; } } /** * Filters the taxonomy used in the Tag Cloud widget. * * @since 2.8.0 * @since 3.0.0 Added taxonomy drop-down. * * @see wp_tag_cloud() * * @param array $args Args used for the tag cloud widget. */ $tag_cloud = wp_tag_cloud( apply_filters( 'widget_tag_cloud_args', array( 'taxonomy' => $current_taxonomy, 'echo' => false ) ) ); if ( empty( $tag_cloud ) ) { return; } /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', $title, $instance, $this->id_base ); echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } echo '<div class="tagcloud">'; echo $tag_cloud; echo "</div>\n"; echo $args['after_widget']; } /** * Handles updating settings for the current Tag Cloud widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Settings to save or bool false to cancel saving. */ public function update( $new_instance, $old_instance ) { $instance = array(); $instance['title'] = sanitize_text_field( $new_instance['title'] ); $instance['taxonomy'] = stripslashes($new_instance['taxonomy']); return $instance; } /** * Outputs the Tag Cloud widget settings form. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { $current_taxonomy = $this->_get_current_taxonomy($instance); $title_id = $this->get_field_id( 'title' ); $instance['title'] = ! empty( $instance['title'] ) ? esc_attr( $instance['title'] ) : ''; echo '<p><label for="' . $title_id .'">' . __( 'Title:' ) . '</label> <input type="text" class="widefat" id="' . $title_id .'" name="' . $this->get_field_name( 'title' ) .'" value="' . $instance['title'] .'" /> </p>'; $taxonomies = get_taxonomies( array( 'show_tagcloud' => true ), 'object' ); $id = $this->get_field_id( 'taxonomy' ); $name = $this->get_field_name( 'taxonomy' ); $input = '<input type="hidden" id="' . $id . '" name="' . $name . '" value="%s" />'; switch ( count( $taxonomies ) ) { // No tag cloud supporting taxonomies found, display error message case 0: echo '<p>' . __( 'The tag cloud will not be displayed since there are no taxonomies that support the tag cloud widget.' ) . '</p>'; printf( $input, '' ); break; // Just a single tag cloud supporting taxonomy found, no need to display options case 1: $keys = array_keys( $taxonomies ); $taxonomy = reset( $keys ); printf( $input, esc_attr( $taxonomy ) ); break; // More than one tag cloud supporting taxonomy found, display options default: printf( '<p><label for="%1$s">%2$s</label>' . '<select class="widefat" id="%1$s" name="%3$s">', $id, __( 'Taxonomy:' ), $name ); foreach ( $taxonomies as $taxonomy => $tax ) { printf( '<option value="%s"%s>%s</option>', esc_attr( $taxonomy ), selected( $taxonomy, $current_taxonomy, false ), $tax->labels->name ); } echo '</select></p>'; } } /** * Retrieves the taxonomy for the current Tag cloud widget instance. * * @since 4.4.0 * @access public * * @param array $instance Current settings. * @return string Name of the current taxonomy if set, otherwise 'post_tag'. */ public function _get_current_taxonomy($instance) { if ( !empty($instance['taxonomy']) && taxonomy_exists($instance['taxonomy']) ) return $instance['taxonomy']; return 'post_tag'; } } class-wp-widget-meta.php 0000666 00000006662 15214027662 0011236 0 ustar 00 <?php /** * Widget API: WP_Widget_Meta class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement a Meta widget. * * Displays log in/out, RSS feed links, etc. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Meta extends WP_Widget { /** * Sets up a new Meta widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'classname' => 'widget_meta', 'description' => __( 'Login, RSS, & WordPress.org links.' ), 'customize_selective_refresh' => true, ); parent::__construct( 'meta', __( 'Meta' ), $widget_ops ); } /** * Outputs the content for the current Meta widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Meta widget instance. */ public function widget( $args, $instance ) { /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', empty($instance['title']) ? __( 'Meta' ) : $instance['title'], $instance, $this->id_base ); echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } ?> <ul> <?php wp_register(); ?> <li><?php wp_loginout(); ?></li> <li><a href="<?php echo esc_url( get_bloginfo( 'rss2_url' ) ); ?>"><?php _e('Entries <abbr title="Really Simple Syndication">RSS</abbr>'); ?></a></li> <li><a href="<?php echo esc_url( get_bloginfo( 'comments_rss2_url' ) ); ?>"><?php _e('Comments <abbr title="Really Simple Syndication">RSS</abbr>'); ?></a></li> <?php /** * Filters the "Powered by WordPress" text in the Meta widget. * * @since 3.6.0 * * @param string $title_text Default title text for the WordPress.org link. */ echo apply_filters( 'widget_meta_poweredby', sprintf( '<li><a href="%s" title="%s">%s</a></li>', esc_url( __( 'https://wordpress.org/' ) ), esc_attr__( 'Powered by WordPress, state-of-the-art semantic personal publishing platform.' ), _x( 'WordPress.org', 'meta widget link text' ) ) ); wp_meta(); ?> </ul> <?php echo $args['after_widget']; } /** * Handles updating settings for the current Meta widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings to save. */ public function update( $new_instance, $old_instance ) { $instance = $old_instance; $instance['title'] = sanitize_text_field( $new_instance['title'] ); return $instance; } /** * Outputs the settings form for the Meta widget. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); $title = sanitize_text_field( $instance['title'] ); ?> <p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label> <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($title); ?>" /></p> <?php } } class-wp-widget-recent-comments.php 0000666 00000013255 15214027662 0013407 0 ustar 00 <?php /** * Widget API: WP_Widget_Recent_Comments class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement a Recent Comments widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Recent_Comments extends WP_Widget { /** * Sets up a new Recent Comments widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'classname' => 'widget_recent_comments', 'description' => __( 'Your site’s most recent comments.' ), 'customize_selective_refresh' => true, ); parent::__construct( 'recent-comments', __( 'Recent Comments' ), $widget_ops ); $this->alt_option_name = 'widget_recent_comments'; if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) { add_action( 'wp_head', array( $this, 'recent_comments_style' ) ); } } /** * Outputs the default styles for the Recent Comments widget. * * @since 2.8.0 * @access public */ public function recent_comments_style() { /** * Filters the Recent Comments default widget styles. * * @since 3.1.0 * * @param bool $active Whether the widget is active. Default true. * @param string $id_base The widget ID. */ if ( ! current_theme_supports( 'widgets' ) // Temp hack #14876 || ! apply_filters( 'show_recent_comments_widget_style', true, $this->id_base ) ) return; ?> <style type="text/css">.recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}</style> <?php } /** * Outputs the content for the current Recent Comments widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Recent Comments widget instance. */ public function widget( $args, $instance ) { if ( ! isset( $args['widget_id'] ) ) $args['widget_id'] = $this->id; $output = ''; $title = ( ! empty( $instance['title'] ) ) ? $instance['title'] : __( 'Recent Comments' ); /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', $title, $instance, $this->id_base ); $number = ( ! empty( $instance['number'] ) ) ? absint( $instance['number'] ) : 5; if ( ! $number ) $number = 5; /** * Filters the arguments for the Recent Comments widget. * * @since 3.4.0 * * @see WP_Comment_Query::query() for information on accepted arguments. * * @param array $comment_args An array of arguments used to retrieve the recent comments. */ $comments = get_comments( apply_filters( 'widget_comments_args', array( 'number' => $number, 'status' => 'approve', 'post_status' => 'publish' ) ) ); $output .= $args['before_widget']; if ( $title ) { $output .= $args['before_title'] . $title . $args['after_title']; } $output .= '<ul id="recentcomments">'; if ( is_array( $comments ) && $comments ) { // Prime cache for associated posts. (Prime post term cache if we need it for permalinks.) $post_ids = array_unique( wp_list_pluck( $comments, 'comment_post_ID' ) ); _prime_post_caches( $post_ids, strpos( get_option( 'permalink_structure' ), '%category%' ), false ); foreach ( (array) $comments as $comment ) { $output .= '<li class="recentcomments">'; /* translators: comments widget: 1: comment author, 2: post link */ $output .= sprintf( _x( '%1$s on %2$s', 'widgets' ), '<span class="comment-author-link">' . get_comment_author_link( $comment ) . '</span>', '<a href="' . esc_url( get_comment_link( $comment ) ) . '">' . get_the_title( $comment->comment_post_ID ) . '</a>' ); $output .= '</li>'; } } $output .= '</ul>'; $output .= $args['after_widget']; echo $output; } /** * Handles updating settings for the current Recent Comments widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings to save. */ public function update( $new_instance, $old_instance ) { $instance = $old_instance; $instance['title'] = sanitize_text_field( $new_instance['title'] ); $instance['number'] = absint( $new_instance['number'] ); return $instance; } /** * Outputs the settings form for the Recent Comments widget. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { $title = isset( $instance['title'] ) ? $instance['title'] : ''; $number = isset( $instance['number'] ) ? absint( $instance['number'] ) : 5; ?> <p><label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label> <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( $title ); ?>" /></p> <p><label for="<?php echo $this->get_field_id( 'number' ); ?>"><?php _e( 'Number of comments to show:' ); ?></label> <input class="tiny-text" id="<?php echo $this->get_field_id( 'number' ); ?>" name="<?php echo $this->get_field_name( 'number' ); ?>" type="number" step="1" min="1" value="<?php echo $number; ?>" size="3" /></p> <?php } /** * Flushes the Recent Comments widget cache. * * @since 2.8.0 * @access public * * @deprecated 4.4.0 Fragment caching was removed in favor of split queries. */ public function flush_widget_cache() { _deprecated_function( __METHOD__, '4.4.0' ); } } class-wp-widget-media.php 0000666 00000033360 15214027662 0011362 0 ustar 00 <?php /** * Widget API: WP_Media_Widget class * * @package WordPress * @subpackage Widgets * @since 4.8.0 */ /** * Core class that implements a media widget. * * @since 4.8.0 * * @see WP_Widget */ abstract class WP_Widget_Media extends WP_Widget { /** * Translation labels. * * @since 4.8.0 * @var array */ public $l10n = array( 'add_to_widget' => '', 'replace_media' => '', 'edit_media' => '', 'media_library_state_multi' => '', 'media_library_state_single' => '', 'missing_attachment' => '', 'no_media_selected' => '', 'add_media' => '', ); /** * Whether or not the widget has been registered yet. * * @since 4.8.1 * @var bool */ protected $registered = false; /** * Constructor. * * @since 4.8.0 * * @param string $id_base Base ID for the widget, lowercase and unique. * @param string $name Name for the widget displayed on the configuration page. * @param array $widget_options Optional. Widget options. See wp_register_sidebar_widget() for * information on accepted arguments. Default empty array. * @param array $control_options Optional. Widget control options. See wp_register_widget_control() * for information on accepted arguments. Default empty array. */ public function __construct( $id_base, $name, $widget_options = array(), $control_options = array() ) { $widget_opts = wp_parse_args( $widget_options, array( 'description' => __( 'A media item.' ), 'customize_selective_refresh' => true, 'mime_type' => '', ) ); $control_opts = wp_parse_args( $control_options, array() ); $l10n_defaults = array( 'no_media_selected' => __( 'No media selected' ), 'add_media' => _x( 'Add Media', 'label for button in the media widget' ), 'replace_media' => _x( 'Replace Media', 'label for button in the media widget; should preferably not be longer than ~13 characters long' ), 'edit_media' => _x( 'Edit Media', 'label for button in the media widget; should preferably not be longer than ~13 characters long' ), 'add_to_widget' => __( 'Add to Widget' ), 'missing_attachment' => sprintf( /* translators: %s: URL to media library. */ __( 'We can’t find that file. Check your <a href="%s">media library</a> and make sure it wasn’t deleted.' ), esc_url( admin_url( 'upload.php' ) ) ), /* translators: %d: Widget count. */ 'media_library_state_multi' => _n_noop( 'Media Widget (%d)', 'Media Widget (%d)' ), 'media_library_state_single' => __( 'Media Widget' ), 'unsupported_file_type' => __( 'Looks like this isn’t the correct kind of file. Please link to an appropriate file instead.' ), ); $this->l10n = array_merge( $l10n_defaults, array_filter( $this->l10n ) ); parent::__construct( $id_base, $name, $widget_opts, $control_opts ); } /** * Add hooks while registering all widget instances of this widget class. * * @since 4.8.0 * * @param integer $number Optional. The unique order number of this widget instance * compared to other instances of the same class. Default -1. */ public function _register_one( $number = -1 ) { parent::_register_one( $number ); if ( $this->registered ) { return; } $this->registered = true; // Note that the widgets component in the customizer will also do // the 'admin_print_scripts-widgets.php' action in WP_Customize_Widgets::print_scripts(). add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) ); if ( $this->is_preview() ) { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) ); } // Note that the widgets component in the customizer will also do // the 'admin_footer-widgets.php' action in WP_Customize_Widgets::print_footer_scripts(). add_action( 'admin_footer-widgets.php', array( $this, 'render_control_template_scripts' ) ); add_filter( 'display_media_states', array( $this, 'display_media_state' ), 10, 2 ); } /** * Get schema for properties of a widget instance (item). * * @since 4.8.0 * * @see WP_REST_Controller::get_item_schema() * @see WP_REST_Controller::get_additional_fields() * @link https://core.trac.wordpress.org/ticket/35574 * * @return array Schema for properties. */ public function get_instance_schema() { $schema = array( 'attachment_id' => array( 'type' => 'integer', 'default' => 0, 'minimum' => 0, 'description' => __( 'Attachment post ID' ), 'media_prop' => 'id', ), 'url' => array( 'type' => 'string', 'default' => '', 'format' => 'uri', 'description' => __( 'URL to the media file' ), ), 'title' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field', 'description' => __( 'Title for the widget' ), 'should_preview_update' => false, ), ); /** * Filters the media widget instance schema to add additional properties. * * @since 4.9.0 * * @param array $schema Instance schema. * @param WP_Widget_Media $this Widget object. */ $schema = apply_filters( "widget_{$this->id_base}_instance_schema", $schema, $this ); return $schema; } /** * Determine if the supplied attachment is for a valid attachment post with the specified MIME type. * * @since 4.8.0 * * @param int|WP_Post $attachment Attachment post ID or object. * @param string $mime_type MIME type. * @return bool Is matching MIME type. */ public function is_attachment_with_mime_type( $attachment, $mime_type ) { if ( empty( $attachment ) ) { return false; } $attachment = get_post( $attachment ); if ( ! $attachment ) { return false; } if ( 'attachment' !== $attachment->post_type ) { return false; } return wp_attachment_is( $mime_type, $attachment ); } /** * Sanitize a token list string, such as used in HTML rel and class attributes. * * @since 4.8.0 * * @link http://w3c.github.io/html/infrastructure.html#space-separated-tokens * @link https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList * @param string|array $tokens List of tokens separated by spaces, or an array of tokens. * @return string Sanitized token string list. */ public function sanitize_token_list( $tokens ) { if ( is_string( $tokens ) ) { $tokens = preg_split( '/\s+/', trim( $tokens ) ); } $tokens = array_map( 'sanitize_html_class', $tokens ); $tokens = array_filter( $tokens ); return join( ' ', $tokens ); } /** * Displays the widget on the front-end. * * @since 4.8.0 * * @see WP_Widget::widget() * * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget. * @param array $instance Saved setting from the database. */ public function widget( $args, $instance ) { $instance = wp_parse_args( $instance, wp_list_pluck( $this->get_instance_schema(), 'default' ) ); // Short-circuit if no media is selected. if ( ! $this->has_content( $instance ) ) { return; } echo $args['before_widget']; /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ); if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } /** * Filters the media widget instance prior to rendering the media. * * @since 4.8.0 * * @param array $instance Instance data. * @param array $args Widget args. * @param WP_Widget_Media $this Widget object. */ $instance = apply_filters( "widget_{$this->id_base}_instance", $instance, $args, $this ); $this->render_media( $instance ); echo $args['after_widget']; } /** * Sanitizes the widget form values as they are saved. * * @since 4.8.0 * * @see WP_Widget::update() * @see WP_REST_Request::has_valid_params() * @see WP_REST_Request::sanitize_params() * * @param array $new_instance Values just sent to be saved. * @param array $instance Previously saved values from database. * @return array Updated safe values to be saved. */ public function update( $new_instance, $instance ) { $schema = $this->get_instance_schema(); foreach ( $schema as $field => $field_schema ) { if ( ! array_key_exists( $field, $new_instance ) ) { continue; } $value = $new_instance[ $field ]; /* * Workaround for rest_validate_value_from_schema() due to the fact that * rest_is_boolean( '' ) === false, while rest_is_boolean( '1' ) is true. */ if ( 'boolean' === $field_schema['type'] && '' === $value ) { $value = false; } if ( true !== rest_validate_value_from_schema( $value, $field_schema, $field ) ) { continue; } $value = rest_sanitize_value_from_schema( $value, $field_schema ); // @codeCoverageIgnoreStart if ( is_wp_error( $value ) ) { continue; // Handle case when rest_sanitize_value_from_schema() ever returns WP_Error as its phpdoc @return tag indicates. } // @codeCoverageIgnoreEnd if ( isset( $field_schema['sanitize_callback'] ) ) { $value = call_user_func( $field_schema['sanitize_callback'], $value ); } if ( is_wp_error( $value ) ) { continue; } $instance[ $field ] = $value; } return $instance; } /** * Render the media on the frontend. * * @since 4.8.0 * * @param array $instance Widget instance props. * @return string */ abstract public function render_media( $instance ); /** * Outputs the settings update form. * * Note that the widget UI itself is rendered with JavaScript via `MediaWidgetControl#render()`. * * @since 4.8.0 * * @see \WP_Widget_Media::render_control_template_scripts() Where the JS template is located. * * @param array $instance Current settings. */ final public function form( $instance ) { $instance_schema = $this->get_instance_schema(); $instance = wp_array_slice_assoc( wp_parse_args( (array) $instance, wp_list_pluck( $instance_schema, 'default' ) ), array_keys( $instance_schema ) ); foreach ( $instance as $name => $value ) : ?> <input type="hidden" data-property="<?php echo esc_attr( $name ); ?>" class="media-widget-instance-property" name="<?php echo esc_attr( $this->get_field_name( $name ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( $name ) ); // Needed specifically by wpWidgets.appendTitle(). ?>" value="<?php echo esc_attr( is_array( $value ) ? join( ',', $value ) : strval( $value ) ); ?>" /> <?php endforeach; } /** * Filters the default media display states for items in the Media list table. * * @since 4.8.0 * * @param array $states An array of media states. * @param WP_Post $post The current attachment object. * @return array */ public function display_media_state( $states, $post = null ) { if ( ! $post ) { $post = get_post(); } // Count how many times this attachment is used in widgets. $use_count = 0; foreach ( $this->get_settings() as $instance ) { if ( isset( $instance['attachment_id'] ) && $instance['attachment_id'] === $post->ID ) { $use_count++; } } if ( 1 === $use_count ) { $states[] = $this->l10n['media_library_state_single']; } elseif ( $use_count > 0 ) { $states[] = sprintf( translate_nooped_plural( $this->l10n['media_library_state_multi'], $use_count ), number_format_i18n( $use_count ) ); } return $states; } /** * Enqueue preview scripts. * * These scripts normally are enqueued just-in-time when a widget is rendered. * In the customizer, however, widgets can be dynamically added and rendered via * selective refresh, and so it is important to unconditionally enqueue them in * case a widget does get added. * * @since 4.8.0 */ public function enqueue_preview_scripts() {} /** * Loads the required scripts and styles for the widget control. * * @since 4.8.0 */ public function enqueue_admin_scripts() { wp_enqueue_media(); wp_enqueue_script( 'media-widgets' ); } /** * Render form template scripts. * * @since 4.8.0 */ public function render_control_template_scripts() { ?> <script type="text/html" id="tmpl-widget-media-<?php echo esc_attr( $this->id_base ); ?>-control"> <# var elementIdPrefix = 'el' + String( Math.random() ) + '_' #> <p> <label for="{{ elementIdPrefix }}title"><?php esc_html_e( 'Title:' ); ?></label> <input id="{{ elementIdPrefix }}title" type="text" class="widefat title"> </p> <div class="media-widget-preview <?php echo esc_attr( $this->id_base ); ?>"> <div class="attachment-media-view"> <button type="button" class="select-media button-add-media not-selected"> <?php echo esc_html( $this->l10n['add_media'] ); ?> </button> </div> </div> <p class="media-widget-buttons"> <button type="button" class="button edit-media selected"> <?php echo esc_html( $this->l10n['edit_media'] ); ?> </button> <?php if ( ! empty( $this->l10n['replace_media'] ) ) : ?> <button type="button" class="button change-media select-media selected"> <?php echo esc_html( $this->l10n['replace_media'] ); ?> </button> <?php endif; ?> </p> <div class="media-widget-fields"> </div> </script> <?php } /** * Whether the widget has content to show. * * @since 4.8.0 * * @param array $instance Widget instance props. * @return bool Whether widget has content. */ protected function has_content( $instance ) { return ( $instance['attachment_id'] && 'attachment' === get_post_type( $instance['attachment_id'] ) ) || $instance['url']; } } class-wp-widget-media-audio.php 0000666 00000013704 15214027662 0012461 0 ustar 00 <?php /** * Widget API: WP_Widget_Media_Audio class * * @package WordPress * @subpackage Widgets * @since 4.8.0 */ /** * Core class that implements an audio widget. * * @since 4.8.0 * * @see WP_Widget_Media * @see WP_Widget */ class WP_Widget_Media_Audio extends WP_Widget_Media { /** * Constructor. * * @since 4.8.0 */ public function __construct() { parent::__construct( 'media_audio', __( 'Audio' ), array( 'description' => __( 'Displays an audio player.' ), 'mime_type' => 'audio', ) ); $this->l10n = array_merge( $this->l10n, array( 'no_media_selected' => __( 'No audio selected' ), 'add_media' => _x( 'Add Audio', 'label for button in the audio widget' ), 'replace_media' => _x( 'Replace Audio', 'label for button in the audio widget; should preferably not be longer than ~13 characters long' ), 'edit_media' => _x( 'Edit Audio', 'label for button in the audio widget; should preferably not be longer than ~13 characters long' ), 'missing_attachment' => sprintf( /* translators: %s: URL to media library. */ __( 'We can’t find that audio file. Check your <a href="%s">media library</a> and make sure it wasn’t deleted.' ), esc_url( admin_url( 'upload.php' ) ) ), /* translators: %d: Widget count. */ 'media_library_state_multi' => _n_noop( 'Audio Widget (%d)', 'Audio Widget (%d)' ), 'media_library_state_single' => __( 'Audio Widget' ), 'unsupported_file_type' => __( 'Looks like this isn’t the correct kind of file. Please link to an audio file instead.' ), ) ); } /** * Get schema for properties of a widget instance (item). * * @since 4.8.0 * * @see WP_REST_Controller::get_item_schema() * @see WP_REST_Controller::get_additional_fields() * @link https://core.trac.wordpress.org/ticket/35574 * * @return array Schema for properties. */ public function get_instance_schema() { $schema = array( 'preload' => array( 'type' => 'string', 'enum' => array( 'none', 'auto', 'metadata' ), 'default' => 'none', 'description' => __( 'Preload' ), ), 'loop' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Loop' ), ), ); foreach ( wp_get_audio_extensions() as $audio_extension ) { $schema[ $audio_extension ] = array( 'type' => 'string', 'default' => '', 'format' => 'uri', /* translators: %s: Audio extension. */ 'description' => sprintf( __( 'URL to the %s audio source file' ), $audio_extension ), ); } return array_merge( $schema, parent::get_instance_schema() ); } /** * Render the media on the frontend. * * @since 4.8.0 * * @param array $instance Widget instance props. */ public function render_media( $instance ) { $instance = array_merge( wp_list_pluck( $this->get_instance_schema(), 'default' ), $instance ); $attachment = null; if ( $this->is_attachment_with_mime_type( $instance['attachment_id'], $this->widget_options['mime_type'] ) ) { $attachment = get_post( $instance['attachment_id'] ); } if ( $attachment ) { $src = wp_get_attachment_url( $attachment->ID ); } else { $src = $instance['url']; } echo wp_audio_shortcode( array_merge( $instance, compact( 'src' ) ) ); } /** * Enqueue preview scripts. * * These scripts normally are enqueued just-in-time when an audio shortcode is used. * In the customizer, however, widgets can be dynamically added and rendered via * selective refresh, and so it is important to unconditionally enqueue them in * case a widget does get added. * * @since 4.8.0 */ public function enqueue_preview_scripts() { /** This filter is documented in wp-includes/media.php */ if ( 'mediaelement' === apply_filters( 'wp_audio_shortcode_library', 'mediaelement' ) ) { wp_enqueue_style( 'wp-mediaelement' ); wp_enqueue_script( 'wp-mediaelement' ); } } /** * Loads the required media files for the media manager and scripts for media widgets. * * @since 4.8.0 */ public function enqueue_admin_scripts() { parent::enqueue_admin_scripts(); wp_enqueue_style( 'wp-mediaelement' ); wp_enqueue_script( 'wp-mediaelement' ); $handle = 'media-audio-widget'; wp_enqueue_script( $handle ); $exported_schema = array(); foreach ( $this->get_instance_schema() as $field => $field_schema ) { $exported_schema[ $field ] = wp_array_slice_assoc( $field_schema, array( 'type', 'default', 'enum', 'minimum', 'format', 'media_prop', 'should_preview_update' ) ); } wp_add_inline_script( $handle, sprintf( 'wp.mediaWidgets.modelConstructors[ %s ].prototype.schema = %s;', wp_json_encode( $this->id_base ), wp_json_encode( $exported_schema ) ) ); wp_add_inline_script( $handle, sprintf( ' wp.mediaWidgets.controlConstructors[ %1$s ].prototype.mime_type = %2$s; wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n = _.extend( {}, wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n, %3$s ); ', wp_json_encode( $this->id_base ), wp_json_encode( $this->widget_options['mime_type'] ), wp_json_encode( $this->l10n ) ) ); } /** * Render form template scripts. * * @since 4.8.0 */ public function render_control_template_scripts() { parent::render_control_template_scripts() ?> <script type="text/html" id="tmpl-wp-media-widget-audio-preview"> <# if ( data.error && 'missing_attachment' === data.error ) { #> <div class="notice notice-error notice-alt notice-missing-attachment"> <p><?php echo $this->l10n['missing_attachment']; ?></p> </div> <# } else if ( data.error ) { #> <div class="notice notice-error notice-alt"> <p><?php _e( 'Unable to preview media due to an unknown error.' ); ?></p> </div> <# } else if ( data.model && data.model.src ) { #> <?php wp_underscore_audio_template(); ?> <# } #> </script> <?php } } class-wp-widget-pages.php 0000666 00000011273 15214027662 0011401 0 ustar 00 <?php /** * Widget API: WP_Widget_Pages class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement a Pages widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Pages extends WP_Widget { /** * Sets up a new Pages widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'classname' => 'widget_pages', 'description' => __( 'A list of your site’s Pages.' ), 'customize_selective_refresh' => true, ); parent::__construct( 'pages', __( 'Pages' ), $widget_ops ); } /** * Outputs the content for the current Pages widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Pages widget instance. */ public function widget( $args, $instance ) { /** * Filters the widget title. * * @since 2.6.0 * * @param string $title The widget title. Default 'Pages'. * @param array $instance An array of the widget's settings. * @param mixed $id_base The widget ID. */ $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? __( 'Pages' ) : $instance['title'], $instance, $this->id_base ); $sortby = empty( $instance['sortby'] ) ? 'menu_order' : $instance['sortby']; $exclude = empty( $instance['exclude'] ) ? '' : $instance['exclude']; if ( $sortby == 'menu_order' ) $sortby = 'menu_order, post_title'; /** * Filters the arguments for the Pages widget. * * @since 2.8.0 * * @see wp_list_pages() * * @param array $args An array of arguments to retrieve the pages list. */ $out = wp_list_pages( apply_filters( 'widget_pages_args', array( 'title_li' => '', 'echo' => 0, 'sort_column' => $sortby, 'exclude' => $exclude ) ) ); if ( ! empty( $out ) ) { echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } ?> <ul> <?php echo $out; ?> </ul> <?php echo $args['after_widget']; } } /** * Handles updating settings for the current Pages widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings to save. */ public function update( $new_instance, $old_instance ) { $instance = $old_instance; $instance['title'] = sanitize_text_field( $new_instance['title'] ); if ( in_array( $new_instance['sortby'], array( 'post_title', 'menu_order', 'ID' ) ) ) { $instance['sortby'] = $new_instance['sortby']; } else { $instance['sortby'] = 'menu_order'; } $instance['exclude'] = sanitize_text_field( $new_instance['exclude'] ); return $instance; } /** * Outputs the settings form for the Pages widget. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { //Defaults $instance = wp_parse_args( (array) $instance, array( 'sortby' => 'post_title', 'title' => '', 'exclude' => '') ); ?> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php _e( 'Title:' ); ?></label> <input class="widefat" id="<?php echo esc_attr( $this->get_field_id('title') ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" /> </p> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'sortby' ) ); ?>"><?php _e( 'Sort by:' ); ?></label> <select name="<?php echo esc_attr( $this->get_field_name( 'sortby' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'sortby' ) ); ?>" class="widefat"> <option value="post_title"<?php selected( $instance['sortby'], 'post_title' ); ?>><?php _e('Page title'); ?></option> <option value="menu_order"<?php selected( $instance['sortby'], 'menu_order' ); ?>><?php _e('Page order'); ?></option> <option value="ID"<?php selected( $instance['sortby'], 'ID' ); ?>><?php _e( 'Page ID' ); ?></option> </select> </p> <p> <label for="<?php echo esc_attr( $this->get_field_id( 'exclude' ) ); ?>"><?php _e( 'Exclude:' ); ?></label> <input type="text" value="<?php echo esc_attr( $instance['exclude'] ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'exclude' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'exclude' ) ); ?>" class="widefat" /> <br /> <small><?php _e( 'Page IDs, separated by commas.' ); ?></small> </p> <?php } } class-wp-widget-custom-html.php 0000666 00000027556 15214027662 0012571 0 ustar 00 <?php /** * Widget API: WP_Widget_Custom_HTML class * * @package WordPress * @subpackage Widgets * @since 4.8.1 */ /** * Core class used to implement a Custom HTML widget. * * @since 4.8.1 * * @see WP_Widget */ class WP_Widget_Custom_HTML extends WP_Widget { /** * Whether or not the widget has been registered yet. * * @since 4.9.0 * @var bool */ protected $registered = false; /** * Default instance. * * @since 4.8.1 * @var array */ protected $default_instance = array( 'title' => '', 'content' => '', ); /** * Sets up a new Custom HTML widget instance. * * @since 4.8.1 */ public function __construct() { $widget_ops = array( 'classname' => 'widget_custom_html', 'description' => __( 'Arbitrary HTML code.' ), 'customize_selective_refresh' => true, ); $control_ops = array( 'width' => 400, 'height' => 350, ); parent::__construct( 'custom_html', __( 'Custom HTML' ), $widget_ops, $control_ops ); } /** * Add hooks for enqueueing assets when registering all widget instances of this widget class. * * @since 4.9.0 * * @param integer $number Optional. The unique order number of this widget instance * compared to other instances of the same class. Default -1. */ public function _register_one( $number = -1 ) { parent::_register_one( $number ); if ( $this->registered ) { return; } $this->registered = true; wp_add_inline_script( 'custom-html-widgets', sprintf( 'wp.customHtmlWidgets.idBases.push( %s );', wp_json_encode( $this->id_base ) ) ); // Note that the widgets component in the customizer will also do // the 'admin_print_scripts-widgets.php' action in WP_Customize_Widgets::print_scripts(). add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) ); // Note that the widgets component in the customizer will also do // the 'admin_footer-widgets.php' action in WP_Customize_Widgets::print_footer_scripts(). add_action( 'admin_footer-widgets.php', array( 'WP_Widget_Custom_HTML', 'render_control_template_scripts' ) ); // Note this action is used to ensure the help text is added to the end. add_action( 'admin_head-widgets.php', array( 'WP_Widget_Custom_HTML', 'add_help_text' ) ); } /** * Filter gallery shortcode attributes. * * Prevents all of a site's attachments from being shown in a gallery displayed on a * non-singular template where a $post context is not available. * * @since 4.9.0 * * @param array $attrs Attributes. * @return array Attributes. */ public function _filter_gallery_shortcode_attrs( $attrs ) { if ( ! is_singular() && empty( $attrs['id'] ) && empty( $attrs['include'] ) ) { $attrs['id'] = -1; } return $attrs; } /** * Outputs the content for the current Custom HTML widget instance. * * @since 4.8.1 * * @global WP_Post $post Global post object. * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Custom HTML widget instance. */ public function widget( $args, $instance ) { global $post; // Override global $post so filters (and shortcodes) apply in a consistent context. $original_post = $post; if ( is_singular() ) { // Make sure post is always the queried object on singular queries (not from another sub-query that failed to clean up the global $post). $post = get_queried_object(); } else { // Nullify the $post global during widget rendering to prevent shortcodes from running with the unexpected context on archive queries. $post = null; } // Prevent dumping out all attachments from the media library. add_filter( 'shortcode_atts_gallery', array( $this, '_filter_gallery_shortcode_attrs' ) ); $instance = array_merge( $this->default_instance, $instance ); /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ); // Prepare instance data that looks like a normal Text widget. $simulated_text_widget_instance = array_merge( $instance, array( 'text' => isset( $instance['content'] ) ? $instance['content'] : '', 'filter' => false, // Because wpautop is not applied. 'visual' => false, // Because it wasn't created in TinyMCE. ) ); unset( $simulated_text_widget_instance['content'] ); // Was moved to 'text' prop. /** This filter is documented in wp-includes/widgets/class-wp-widget-text.php */ $content = apply_filters( 'widget_text', $instance['content'], $simulated_text_widget_instance, $this ); // Adds noreferrer and noopener relationships, without duplicating values, to all HTML A elements that have a target. $content = wp_targeted_link_rel( $content ); /** * Filters the content of the Custom HTML widget. * * @since 4.8.1 * * @param string $content The widget content. * @param array $instance Array of settings for the current widget. * @param WP_Widget_Custom_HTML $this Current Custom HTML widget instance. */ $content = apply_filters( 'widget_custom_html_content', $content, $instance, $this ); // Restore post global. $post = $original_post; remove_filter( 'shortcode_atts_gallery', array( $this, '_filter_gallery_shortcode_attrs' ) ); // Inject the Text widget's container class name alongside this widget's class name for theme styling compatibility. $args['before_widget'] = preg_replace( '/(?<=\sclass=["\'])/', 'widget_text ', $args['before_widget'] ); echo $args['before_widget']; if ( ! empty( $title ) ) { echo $args['before_title'] . $title . $args['after_title']; } echo '<div class="textwidget custom-html-widget">'; // The textwidget class is for theme styling compatibility. echo $content; echo '</div>'; echo $args['after_widget']; } /** * Handles updating settings for the current Custom HTML widget instance. * * @since 4.8.1 * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Settings to save or bool false to cancel saving. */ public function update( $new_instance, $old_instance ) { $instance = array_merge( $this->default_instance, $old_instance ); $instance['title'] = sanitize_text_field( $new_instance['title'] ); if ( current_user_can( 'unfiltered_html' ) ) { $instance['content'] = $new_instance['content']; } else { $instance['content'] = wp_kses_post( $new_instance['content'] ); } return $instance; } /** * Loads the required scripts and styles for the widget control. * * @since 4.9.0 */ public function enqueue_admin_scripts() { $settings = wp_enqueue_code_editor( array( 'type' => 'text/html', 'codemirror' => array( 'indentUnit' => 2, 'tabSize' => 2, ), ) ); wp_enqueue_script( 'custom-html-widgets' ); if ( empty( $settings ) ) { $settings = array( 'disabled' => true, ); } wp_add_inline_script( 'custom-html-widgets', sprintf( 'wp.customHtmlWidgets.init( %s );', wp_json_encode( $settings ) ), 'after' ); $l10n = array( 'errorNotice' => array( /* translators: %d: Error count. */ 'singular' => _n( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.', 1 ), /* translators: %d: Error count. */ 'plural' => _n( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.', 2 ), // @todo This is lacking, as some languages have a dedicated dual form. For proper handling of plurals in JS, see #20491. ), ); wp_add_inline_script( 'custom-html-widgets', sprintf( 'jQuery.extend( wp.customHtmlWidgets.l10n, %s );', wp_json_encode( $l10n ) ), 'after' ); } /** * Outputs the Custom HTML widget settings form. * * @since 4.8.1 * @since 4.9.0 The form contains only hidden sync inputs. For the control UI, see `WP_Widget_Custom_HTML::render_control_template_scripts()`. * * @see WP_Widget_Custom_HTML::render_control_template_scripts() * * @param array $instance Current instance. */ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, $this->default_instance ); ?> <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'] ); ?>"/> <textarea id="<?php echo $this->get_field_id( 'content' ); ?>" name="<?php echo $this->get_field_name( 'content' ); ?>" class="content sync-input" hidden><?php echo esc_textarea( $instance['content'] ); ?></textarea> <?php } /** * Render form template scripts. * * @since 4.9.0 */ public static function render_control_template_scripts() { ?> <script type="text/html" id="tmpl-widget-custom-html-control-fields"> <# var elementIdPrefix = 'el' + String( Math.random() ).replace( /\D/g, '' ) + '_' #> <p> <label for="{{ elementIdPrefix }}title"><?php esc_html_e( 'Title:' ); ?></label> <input id="{{ elementIdPrefix }}title" type="text" class="widefat title"> </p> <p> <label for="{{ elementIdPrefix }}content" id="{{ elementIdPrefix }}content-label"><?php esc_html_e( 'Content:' ); ?></label> <textarea id="{{ elementIdPrefix }}content" class="widefat code content" rows="16" cols="20"></textarea> </p> <?php if ( ! current_user_can( 'unfiltered_html' ) ) : ?> <?php $probably_unsafe_html = array( 'script', 'iframe', 'form', 'input', 'style' ); $allowed_html = wp_kses_allowed_html( 'post' ); $disallowed_html = array_diff( $probably_unsafe_html, array_keys( $allowed_html ) ); ?> <?php if ( ! empty( $disallowed_html ) ) : ?> <# if ( data.codeEditorDisabled ) { #> <p> <?php _e( 'Some HTML tags are not permitted, including:' ); ?> <code><?php echo join( '</code>, <code>', $disallowed_html ); ?></code> </p> <# } #> <?php endif; ?> <?php endif; ?> <div class="code-editor-error-container"></div> </script> <?php } /** * Add help text to widgets admin screen. * * @since 4.9.0 */ public static function add_help_text() { $screen = get_current_screen(); $content = '<p>'; $content .= __( 'Use the Custom HTML widget to add arbitrary HTML code to your widget areas.' ); $content .= '</p>'; if ( 'false' !== wp_get_current_user()->syntax_highlighting ) { $content .= '<p>'; $content .= sprintf( /* translators: 1: Link to user profile, 2: Additional link attributes, 3: Accessibility text. */ __( 'The edit field automatically highlights code syntax. You can disable this in your <a href="%1$s" %2$s>user profile%3$s</a> to work in plain text mode.' ), esc_url( get_edit_profile_url() ), 'class="external-link" target="_blank"', sprintf( '<span class="screen-reader-text"> %s</span>', /* translators: Accessibility text. */ __( '(opens in a new tab)' ) ) ); $content .= '</p>'; $content .= '<p id="editor-keyboard-trap-help-1">' . __( 'When using a keyboard to navigate:' ) . '</p>'; $content .= '<ul>'; $content .= '<li id="editor-keyboard-trap-help-2">' . __( 'In the editing area, the Tab key enters a tab character.' ) . '</li>'; $content .= '<li id="editor-keyboard-trap-help-3">' . __( 'To move away from this area, press the Esc key followed by the Tab key.' ) . '</li>'; $content .= '<li id="editor-keyboard-trap-help-4">' . __( 'Screen reader users: when in forms mode, you may need to press the Esc key twice.' ) . '</li>'; $content .= '</ul>'; } $screen->add_help_tab( array( 'id' => 'custom_html_widget', 'title' => __( 'Custom HTML Widget' ), 'content' => $content, ) ); } } class-wp-widget-calendar.php 0000666 00000005602 15214027662 0012052 0 ustar 00 <?php /** * Widget API: WP_Widget_Calendar class * * @package WordPress * @subpackage Widgets * @since 4.4.0 */ /** * Core class used to implement the Calendar widget. * * @since 2.8.0 * * @see WP_Widget */ class WP_Widget_Calendar extends WP_Widget { /** * Ensure that the ID attribute only appears in the markup once * * @since 4.4.0 * * @static * @access private * @var int */ private static $instance = 0; /** * Sets up a new Calendar widget instance. * * @since 2.8.0 * @access public */ public function __construct() { $widget_ops = array( 'classname' => 'widget_calendar', 'description' => __( 'A calendar of your site’s Posts.' ), 'customize_selective_refresh' => true, ); parent::__construct( 'calendar', __( 'Calendar' ), $widget_ops ); } /** * Outputs the content for the current Calendar widget instance. * * @since 2.8.0 * @access public * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance The settings for the particular instance of the widget. */ public function widget( $args, $instance ) { /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base ); echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } if ( 0 === self::$instance ) { echo '<div id="calendar_wrap" class="calendar_wrap">'; } else { echo '<div class="calendar_wrap">'; } get_calendar(); echo '</div>'; echo $args['after_widget']; self::$instance++; } /** * Handles updating settings for the current Calendar widget instance. * * @since 2.8.0 * @access public * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Updated settings to save. */ public function update( $new_instance, $old_instance ) { $instance = $old_instance; $instance['title'] = sanitize_text_field( $new_instance['title'] ); return $instance; } /** * Outputs the settings form for the Calendar widget. * * @since 2.8.0 * @access public * * @param array $instance Current settings. */ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) ); $title = sanitize_text_field( $instance['title'] ); ?> <p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label> <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($title); ?>" /></p> <?php } } media-audio-widget.min.js 0000666 00000002647 15214027740 0011342 0 ustar 00 /*! This file is auto-generated */ !function(t){"use strict";var a=wp.media.view.MediaFrame.AudioDetails.extend({createStates:function(){this.states.add([new wp.media.controller.AudioDetails({media:this.media}),new wp.media.controller.MediaLibrary({type:"audio",id:"add-audio-source",title:wp.media.view.l10n.audioAddSourceTitle,toolbar:"add-audio-source",media:this.media,menu:!1})])}}),e=t.MediaWidgetModel.extend({}),d=t.MediaWidgetControl.extend({showDisplaySettings:!1,mapModelToMediaFrameProps:function(e){e=t.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call(this,e);return e.link="embed",e},renderPreview:function(){var e,t=this,d=t.model.get("attachment_id"),a=t.model.get("url");(d||a)&&(e=t.$el.find(".media-widget-preview"),d=wp.template("wp-media-widget-audio-preview"),e.html(d({model:{attachment_id:t.model.get("attachment_id"),src:a},error:t.model.get("error")})),wp.mediaelement.initialize())},editMedia:function(){var t=this,e=t.mapModelToMediaFrameProps(t.model.toJSON()),d=new a({frame:"audio",state:"audio-details",metadata:e});(wp.media.frame=d).$el.addClass("media-widget"),e=function(e){t.selectedAttachment.set(e),t.model.set(_.extend(t.model.defaults(),t.mapMediaToModelProps(e),{error:!1}))},d.state("audio-details").on("update",e),d.state("replace-audio").on("replace",e),d.on("close",function(){d.detach()}),d.open()}});t.controlConstructors.media_audio=d,t.modelConstructors.media_audio=e}(wp.mediaWidgets); media-widgets.min.js 0000666 00000033622 15214027740 0010423 0 ustar 00 /*! This file is auto-generated */ wp.mediaWidgets=function(c){"use strict";var m={controlConstructors:{},modelConstructors:{}};return m.PersistentDisplaySettingsLibrary=wp.media.controller.Library.extend({initialize:function(e){_.bindAll(this,"handleDisplaySettingChange"),wp.media.controller.Library.prototype.initialize.call(this,e)},handleDisplaySettingChange:function(e){this.get("selectedDisplaySettings").set(e.attributes)},display:function(e){var t=this.get("selectedDisplaySettings"),e=wp.media.controller.Library.prototype.display.call(this,e);return e.off("change",this.handleDisplaySettingChange),e.set(t.attributes),"custom"===t.get("link_type")&&(e.linkUrl=t.get("link_url")),e.on("change",this.handleDisplaySettingChange),e}}),m.MediaEmbedView=wp.media.view.Embed.extend({initialize:function(e){var t=this;wp.media.view.Embed.prototype.initialize.call(t,e),"image"!==t.controller.options.mimeType&&(t=t.controller.states.get("embed")).off("scan",t.scanImage,t)},refresh:function(){var e="image"===this.controller.options.mimeType?wp.media.view.EmbedImage:wp.media.view.EmbedLink.extend({setAddToWidgetButtonDisabled:function(e){this.views.parent.views.parent.views.get(".media-frame-toolbar")[0].$el.find(".media-button-select").prop("disabled",e)},setErrorNotice:function(e){var t=this.views.parent.$el.find("> .notice:first-child");e?(t.length||((t=c('<div class="media-widget-embed-notice notice notice-error notice-alt"></div>')).hide(),this.views.parent.$el.prepend(t)),t.empty(),t.append(c("<p>",{html:e})),t.slideDown("fast")):t.length&&t.slideUp("fast")},updateoEmbed:function(){var e=this,t=e.model.get("url");if(!t)return e.setErrorNotice(""),void e.setAddToWidgetButtonDisabled(!0);t.match(/^(http|https):\/\/.+\//)||(e.controller.$el.find("#embed-url-field").addClass("invalid"),e.setAddToWidgetButtonDisabled(!0)),wp.media.view.EmbedLink.prototype.updateoEmbed.call(e)},fetch:function(){var t,e,i,n=this,d=n.model.get("url");if(n.dfd&&"pending"===n.dfd.state()&&n.dfd.abort(),t=function(e){n.renderoEmbed({data:{body:e}}),n.controller.$el.find("#embed-url-field").removeClass("invalid"),n.setErrorNotice(""),n.setAddToWidgetButtonDisabled(!1)},(e=document.createElement("a")).href=d,e=e.pathname.toLowerCase().match(/\.(\w+)$/))return i=e[1],void(!wp.media.view.settings.embedMimes[i]||0!==wp.media.view.settings.embedMimes[i].indexOf(n.controller.options.mimeType)?n.renderFail():t("\x3c!--success--\x3e"));(i=/https?:\/\/www\.youtube\.com\/embed\/([^/]+)/.exec(d))&&(d="https://www.youtube.com/watch?v="+i[1],n.model.attributes.url=d),n.dfd=wp.apiRequest({url:wp.media.view.settings.oEmbedProxyUrl,data:{url:d,maxwidth:n.model.get("width"),maxheight:n.model.get("height"),discover:!1},type:"GET",dataType:"json",context:n}),n.dfd.done(function(e){n.controller.options.mimeType===e.type?t(e.html):n.renderFail()}),n.dfd.fail(_.bind(n.renderFail,n))},renderFail:function(){var e=this;e.controller.$el.find("#embed-url-field").addClass("invalid"),e.setErrorNotice(e.controller.options.invalidEmbedTypeError||"ERROR"),e.setAddToWidgetButtonDisabled(!0)}});this.settings(new e({controller:this.controller,model:this.model.props,priority:40}))}}),m.MediaFrameSelect=wp.media.view.MediaFrame.Post.extend({createStates:function(){var t=this.options.mimeType,i=[];_.each(wp.media.view.settings.embedMimes,function(e){0===e.indexOf(t)&&i.push(e)}),0<i.length&&(t=i),this.states.add([new m.PersistentDisplaySettingsLibrary({id:"insert",title:this.options.title,selection:this.options.selection,priority:20,toolbar:"main-insert",filterable:"dates",library:wp.media.query({type:t}),multiple:!1,editable:!0,selectedDisplaySettings:this.options.selectedDisplaySettings,displaySettings:!!_.isUndefined(this.options.showDisplaySettings)||this.options.showDisplaySettings,displayUserSettings:!1}),new wp.media.controller.EditImage({model:this.options.editImage}),new wp.media.controller.Embed({metadata:this.options.metadata,type:"image"===this.options.mimeType?"image":"link",invalidEmbedTypeError:this.options.invalidEmbedTypeError})])},mainInsertToolbar:function(e){var i=this;e.set("insert",{style:"primary",priority:80,text:i.options.text,requires:{selection:!0},click:function(){var e=i.state(),t=e.get("selection");i.close(),e.trigger("insert",t).reset()}})},mainEmbedToolbar:function(e){e.view=new wp.media.view.Toolbar.Embed({controller:this,text:this.options.text,event:"insert"})},embedContent:function(){var e=new m.MediaEmbedView({controller:this,model:this.state()}).render();this.content.set(e)}}),m.MediaWidgetControl=Backbone.View.extend({l10n:{add_to_widget:"{{add_to_widget}}",add_media:"{{add_media}}"},id_base:"",mime_type:"",events:{"click .notice-missing-attachment a":"handleMediaLibraryLinkClick","click .select-media":"selectMedia","click .placeholder":"selectMedia","click .edit-media":"editMedia"},showDisplaySettings:!0,initialize:function(e){var i=this;if(Backbone.View.prototype.initialize.call(i,e),!(i.model instanceof m.MediaWidgetModel))throw new Error("Missing options.model");if(!e.el)throw new Error("Missing options.el");if(!e.syncContainer)throw new Error("Missing options.syncContainer");if(i.syncContainer=e.syncContainer,i.$el.addClass("media-widget-control"),_.bindAll(i,"syncModelToInputs","render","updateSelectedAttachment","renderPreview"),!i.id_base&&(_.find(m.controlConstructors,function(e,t){return i instanceof e&&(i.id_base=t,!0)}),!i.id_base))throw new Error("Missing id_base.");i.previewTemplateProps=new Backbone.Model(i.mapModelToPreviewTemplateProps()),i.selectedAttachment=new wp.media.model.Attachment,i.renderPreview=_.debounce(i.renderPreview),i.listenTo(i.previewTemplateProps,"change",i.renderPreview),i.model.on("change:attachment_id",i.updateSelectedAttachment),i.model.on("change:url",i.updateSelectedAttachment),i.updateSelectedAttachment(),i.listenTo(i.model,"change",i.syncModelToInputs),i.listenTo(i.model,"change",i.syncModelToPreviewProps),i.listenTo(i.model,"change",i.render),i.$el.on("input change",".title",function(){i.model.set({title:c.trim(c(this).val())})}),i.$el.on("input change",".link",function(){var e=c.trim(c(this).val()),t="custom";i.selectedAttachment.get("linkUrl")===e||i.selectedAttachment.get("link")===e?t="post":i.selectedAttachment.get("url")===e&&(t="file"),i.model.set({link_url:e,link_type:t}),i.displaySettings.set({link:t,linkUrl:e})}),i.displaySettings=new Backbone.Model(_.pick(i.mapModelToMediaFrameProps(_.extend(i.model.defaults(),i.model.toJSON())),_.keys(wp.media.view.settings.defaultProps)))},updateSelectedAttachment:function(){var e,t=this;0===t.model.get("attachment_id")?(t.selectedAttachment.clear(),t.model.set("error",!1)):t.model.get("attachment_id")!==t.selectedAttachment.get("id")&&(e=new wp.media.model.Attachment({id:t.model.get("attachment_id")})).fetch().done(function(){t.model.set("error",!1),t.selectedAttachment.set(e.toJSON())}).fail(function(){t.model.set("error","missing_attachment")})},syncModelToPreviewProps:function(){this.previewTemplateProps.set(this.mapModelToPreviewTemplateProps())},syncModelToInputs:function(){var n=this;n.syncContainer.find(".media-widget-instance-property").each(function(){var e=c(this),t=e.data("property"),i=n.model.get(t);_.isUndefined(i)||(i="array"===n.model.schema[t].type&&_.isArray(i)?i.join(","):"boolean"===n.model.schema[t].type?i?"1":"":String(i),e.val()!==i&&(e.val(i),e.trigger("change")))})},template:function(){if(!c("#tmpl-widget-media-"+this.id_base+"-control").length)throw new Error("Missing widget control template for "+this.id_base);return wp.template("widget-media-"+this.id_base+"-control")},render:function(){var e,t=this;t.templateRendered||(t.$el.html(t.template()(t.model.toJSON())),t.renderPreview(),t.templateRendered=!0),(e=t.$el.find(".title")).is(document.activeElement)||e.val(t.model.get("title")),t.$el.toggleClass("selected",t.isSelected())},renderPreview:function(){throw new Error("renderPreview must be implemented")},isSelected:function(){return!this.model.get("error")&&Boolean(this.model.get("attachment_id")||this.model.get("url"))},handleMediaLibraryLinkClick:function(e){e.preventDefault(),this.selectMedia()},selectMedia:function(){var e,i,t,n=this,d=[];n.isSelected()&&0!==n.model.get("attachment_id")&&d.push(n.selectedAttachment),e=new wp.media.model.Selection(d,{multiple:!1}),(d=n.mapModelToMediaFrameProps(n.model.toJSON())).size&&n.displaySettings.set("size",d.size),i=new m.MediaFrameSelect({title:n.l10n.add_media,frame:"post",text:n.l10n.add_to_widget,selection:e,mimeType:n.mime_type,selectedDisplaySettings:n.displaySettings,showDisplaySettings:n.showDisplaySettings,metadata:d,state:n.isSelected()&&0===n.model.get("attachment_id")?"embed":"insert",invalidEmbedTypeError:n.l10n.unsupported_file_type}),(wp.media.frame=i).on("insert",function(){var e={},t=i.state();"embed"===t.get("id")?_.extend(e,{id:0},t.props.toJSON()):_.extend(e,t.get("selection").first().toJSON()),n.selectedAttachment.set(e),n.model.set("error",!1),n.model.set(n.getModelPropsFromMediaFrame(i))}),t=wp.media.model.Attachment.prototype.sync,wp.media.model.Attachment.prototype.sync=function(e){return"delete"===e?t.apply(this,arguments):c.Deferred().rejectWith(this).promise()},i.on("close",function(){wp.media.model.Attachment.prototype.sync=t}),i.$el.addClass("media-widget"),i.open(),e&&e.on("destroy",function(e){n.model.get("attachment_id")===e.get("id")&&n.model.set({attachment_id:0,url:""})}),i.$el.find(".media-frame-menu .media-menu-item.active").focus()},getModelPropsFromMediaFrame:function(e){var t,i,n=this,d=e.state();if("insert"===d.get("id"))(t=d.get("selection").first().toJSON()).postUrl=t.link,n.showDisplaySettings&&_.extend(t,e.content.get(".attachments-browser").sidebar.get("display").model.toJSON()),t.sizes&&t.size&&t.sizes[t.size]&&(t.url=t.sizes[t.size].url);else{if("embed"!==d.get("id"))throw new Error("Unexpected state: "+d.get("id"));t=_.extend(d.props.toJSON(),{attachment_id:0},n.model.getEmbedResetProps())}return t.id&&(t.attachment_id=t.id),i=n.mapMediaToModelProps(t),_.each(wp.media.view.settings.embedExts,function(e){e in n.model.schema&&i.url!==i[e]&&(i[e]="")}),i},mapMediaToModelProps:function(e){var t,i=this,n={},d={};return _.each(i.model.schema,function(e,t){"title"!==t&&(n[e.media_prop||t]=t)}),_.each(e,function(e,t){t=n[t]||t;i.model.schema[t]&&(d[t]=e)}),"custom"===e.size&&(d.width=e.customWidth,d.height=e.customHeight),"post"===e.link?d.link_url=e.postUrl||e.linkUrl:"file"===e.link&&(d.link_url=e.url),!e.attachment_id&&e.id&&(d.attachment_id=e.id),e.url&&(t=e.url.replace(/#.*$/,"").replace(/\?.*$/,"").split(".").pop().toLowerCase())in i.model.schema&&(d[t]=e.url),_.omit(d,"title")},mapModelToMediaFrameProps:function(e){var n=this,d={};return _.each(e,function(e,t){var i=n.model.schema[t]||{};d[i.media_prop||t]=e}),d.attachment_id=d.id,"custom"===d.size&&(d.customWidth=n.model.get("width"),d.customHeight=n.model.get("height")),d},mapModelToPreviewTemplateProps:function(){var i=this,n={};return _.each(i.model.schema,function(e,t){e.hasOwnProperty("should_preview_update")&&!e.should_preview_update||(n[t]=i.model.get(t))}),n.error=i.model.get("error"),n},editMedia:function(){throw new Error("editMedia not implemented")}}),m.MediaWidgetModel=Backbone.Model.extend({idAttribute:"widget_id",schema:{title:{type:"string",default:""},attachment_id:{type:"integer",default:0},url:{type:"string",default:""}},defaults:function(){var i={};return _.each(this.schema,function(e,t){i[t]=e.default}),i},set:function(e,t,i){var n,d,o=this;return null===e?o:(i="object"==typeof e?(n=e,t):((n={})[e]=t,i),d={},_.each(n,function(e,t){var i;o.schema[t]?"array"===(i=o.schema[t].type)?(d[t]=e,_.isArray(d[t])||(d[t]=d[t].split(/,/)),o.schema[t].items&&"integer"===o.schema[t].items.type&&(d[t]=_.filter(_.map(d[t],function(e){return parseInt(e,10)},function(e){return"number"==typeof e})))):d[t]="integer"===i?parseInt(e,10):"boolean"===i?!(!e||"0"===e||"false"===e):e:d[t]=e}),Backbone.Model.prototype.set.call(this,d,i))},getEmbedResetProps:function(){return{id:0}}}),m.modelCollection=new(Backbone.Collection.extend({model:m.MediaWidgetModel})),m.widgetControls={},m.handleWidgetAdded=function(e,t){var i,n,d,o,a,s=t.find("> .widget-inside > .form, > .widget-inside > form"),r=s.find("> .id_base").val(),l=s.find("> .widget-id").val();m.widgetControls[l]||(i=m.controlConstructors[r])&&(o=m.modelConstructors[r]||m.MediaWidgetModel,s=c("<div></div>"),(r=t.find(".widget-content:first")).before(s),n={},r.find(".media-widget-instance-property").each(function(){var e=c(this);n[e.data("property")]=e.val()}),n.widget_id=l,o=new o(n),d=new i({el:s,syncContainer:r,model:o}),(a=function(){t.hasClass("open")?d.render():setTimeout(a,50)})(),m.modelCollection.add([o]),m.widgetControls[o.get("widget_id")]=d)},m.setupAccessibleMode=function(){var e,t,i,n,d,o=c(".editwidget > form");0!==o.length&&(d=o.find("> .widget-control-actions > .id_base").val(),(t=m.controlConstructors[d])&&(e=o.find("> .widget-control-actions > .widget-id").val(),i=m.modelConstructors[d]||m.MediaWidgetModel,d=c("<div></div>"),(o=o.find("> .widget-inside")).before(d),n={},o.find(".media-widget-instance-property").each(function(){var e=c(this);n[e.data("property")]=e.val()}),n.widget_id=e,i=new t({el:d,syncContainer:o,model:new i(n)}),m.modelCollection.add([i.model]),(m.widgetControls[i.model.get("widget_id")]=i).render()))},m.handleWidgetUpdated=function(e,t){var i={},n=t.find("> .widget-inside > .form, > .widget-inside > form"),t=n.find("> .widget-id").val(),t=m.widgetControls[t];t&&(n.find("> .widget-content").find(".media-widget-instance-property").each(function(){var e=c(this).data("property");i[e]=c(this).val()}),t.stopListening(t.model,"change",t.syncModelToInputs),t.model.set(i),t.listenTo(t.model,"change",t.syncModelToInputs))},m.init=function(){var e=c(document);e.on("widget-added",m.handleWidgetAdded),e.on("widget-synced widget-updated",m.handleWidgetUpdated),c(function(){"widgets"===window.pagenow&&(c(".widgets-holder-wrap:not(#available-widgets)").find("div.widget").one("click.toggle-widget-expanded",function(){var e=c(this);m.handleWidgetAdded(new jQuery.Event("widget-added"),e)}),c(window).on("load",function(){m.setupAccessibleMode()}))})},m}(jQuery); text-widgets.min.js 0000666 00000013375 15214027740 0010333 0 ustar 00 /*! This file is auto-generated */ wp.textWidgets=function(r){"use strict";var u={dismissedPointers:[],idBases:["text"]};return u.TextWidgetControl=Backbone.View.extend({events:{},initialize:function(t){var n=this;if(!t.el)throw new Error("Missing options.el");if(!t.syncContainer)throw new Error("Missing options.syncContainer");Backbone.View.prototype.initialize.call(n,t),n.syncContainer=t.syncContainer,n.$el.addClass("text-widget-fields"),n.$el.html(wp.template("widget-text-control-fields")),n.customHtmlWidgetPointer=n.$el.find(".wp-pointer.custom-html-widget-pointer"),n.customHtmlWidgetPointer.length&&(n.customHtmlWidgetPointer.find(".close").on("click",function(t){t.preventDefault(),n.customHtmlWidgetPointer.hide(),r("#"+n.fields.text.attr("id")+"-html").focus(),n.dismissPointers(["text_widget_custom_html"])}),n.customHtmlWidgetPointer.find(".add-widget").on("click",function(t){t.preventDefault(),n.customHtmlWidgetPointer.hide(),n.openAvailableWidgetsPanel()})),n.pasteHtmlPointer=n.$el.find(".wp-pointer.paste-html-pointer"),n.pasteHtmlPointer.length&&n.pasteHtmlPointer.find(".close").on("click",function(t){t.preventDefault(),n.pasteHtmlPointer.hide(),n.editor.focus(),n.dismissPointers(["text_widget_custom_html","text_widget_paste_html"])}),n.fields={title:n.$el.find(".title"),text:n.$el.find(".text")},_.each(n.fields,function(e,i){e.on("input change",function(){var t=n.syncContainer.find(".sync-input."+i);t.val()!==e.val()&&(t.val(e.val()),t.trigger("change"))}),e.val(n.syncContainer.find(".sync-input."+i).val())})},dismissPointers:function(t){_.each(t,function(t){wp.ajax.post("dismiss-wp-pointer",{pointer:t}),u.dismissedPointers.push(t)})},openAvailableWidgetsPanel:function(){var e;wp.customize.section.each(function(t){t.extended(wp.customize.Widgets.SidebarSection)&&t.expanded()&&(e=wp.customize.control("sidebars_widgets["+t.params.sidebarId+"]"))}),e&&setTimeout(function(){wp.customize.Widgets.availableWidgetsPanel.open(e),wp.customize.Widgets.availableWidgetsPanel.$search.val("HTML").trigger("keyup")})},updateFields:function(){var t,e=this;e.fields.title.is(document.activeElement)||(t=e.syncContainer.find(".sync-input.title"),e.fields.title.val(t.val())),t=e.syncContainer.find(".sync-input.text"),e.fields.text.is(":visible")?e.fields.text.is(document.activeElement)||e.fields.text.val(t.val()):e.editor&&!e.editorFocused&&t.val()!==e.fields.text.val()&&e.editor.setContent(wp.editor.autop(t.val()))},initializeEditor:function(){var d,t,o,e,s=this,a=1e3,l=!1,c=!1;t=s.fields.text,d=t.attr("id"),e=t.val(),o=function(){s.editor.isDirty()&&(wp.customize&&wp.customize.state&&(wp.customize.state("processing").set(wp.customize.state("processing").get()+1),_.delay(function(){wp.customize.state("processing").set(wp.customize.state("processing").get()-1)},300)),s.editor.isHidden()||s.editor.save()),c&&e!==t.val()&&(t.trigger("change"),c=!1,e=t.val())},s.syncContainer.closest(".widget").find("[name=savewidget]:first").on("click",function(){o()}),function t(){var e,i,n;if(document.getElementById(d))if(void 0!==window.tinymce){if(tinymce.get(d)&&(l=tinymce.get(d).isHidden(),wp.editor.remove(d)),r(document).one("wp-before-tinymce-init.text-widget-init",function(t,e){e.plugins&&(/\bwpview\b/.test(e.plugins)||(e.plugins+=",wpview"))}),wp.editor.initialize(d,{tinymce:{wpautop:!0},quicktags:!0,mediaButtons:!0}),n=function(t){t.show(),t.find(".close").focus(),wp.a11y.speak(t.find("h3, p").map(function(){return r(this).text()}).get().join("\n\n"))},!(e=window.tinymce.get(d)))throw new Error("Failed to initialize editor");i=function(){r(e.getWin()).on("unload",function(){_.defer(t)}),l&&switchEditors.go(d,"html"),r("#"+d+"-html").on("click",function(){s.pasteHtmlPointer.hide(),-1===u.dismissedPointers.indexOf("text_widget_custom_html")&&n(s.customHtmlWidgetPointer)}),r("#"+d+"-tmce").on("click",function(){s.customHtmlWidgetPointer.hide()}),e.on("pastepreprocess",function(t){t=t.content,-1===u.dismissedPointers.indexOf("text_widget_paste_html")&&t&&/<\w+.*?>/.test(t)&&_.delay(function(){n(s.pasteHtmlPointer)},250)})},e.initialized?i():e.on("init",i),s.editorFocused=!1,e.on("focus",function(){s.editorFocused=!0}),e.on("paste",function(){e.setDirty(!0),o()}),e.on("NodeChange",function(){c=!0}),e.on("NodeChange",_.debounce(o,a)),e.on("blur hide",function(){s.editorFocused=!1,o()}),s.editor=e}else wp.editor.initialize(d,{quicktags:!0,mediaButtons:!0})}()}}),u.widgetControls={},u.handleWidgetAdded=function(t,e){var i,n,d,o=e.find("> .widget-inside > .form, > .widget-inside > form"),s=o.find("> .id_base").val();-1!==u.idBases.indexOf(s)&&(n=o.find(".widget-id").val(),u.widgetControls[n]||o.find(".visual").val()&&(s=r("<div></div>"),(o=e.find(".widget-content:first")).before(s),i=new u.TextWidgetControl({el:s,syncContainer:o}),u.widgetControls[n]=i,(d=function(){e.hasClass("open")?i.initializeEditor():setTimeout(d,50)})()))},u.setupAccessibleMode=function(){var t,e=r(".editwidget > form");0!==e.length&&(t=e.find("> .widget-control-actions > .id_base").val(),-1!==u.idBases.indexOf(t)&&e.find(".visual").val()&&(t=r("<div></div>"),(e=e.find("> .widget-inside")).before(t),new u.TextWidgetControl({el:t,syncContainer:e}).initializeEditor()))},u.handleWidgetUpdated=function(t,e){var i=e.find("> .widget-inside > .form, > .widget-inside > form"),e=i.find("> .id_base").val();-1!==u.idBases.indexOf(e)&&(i=i.find("> .widget-id").val(),(i=u.widgetControls[i])&&i.updateFields())},u.init=function(){var t=r(document);t.on("widget-added",u.handleWidgetAdded),t.on("widget-synced widget-updated",u.handleWidgetUpdated),r(function(){"widgets"===window.pagenow&&(r(".widgets-holder-wrap:not(#available-widgets)").find("div.widget").one("click.toggle-widget-expanded",function(){var t=r(this);u.handleWidgetAdded(new jQuery.Event("widget-added"),t)}),r(window).on("load",function(){u.setupAccessibleMode()}))})},u}(jQuery); media-video-widget.min.js 0000666 00000005220 15214027740 0011335 0 ustar 00 /*! This file is auto-generated */ !function(t){"use strict";var i=wp.media.view.MediaFrame.VideoDetails.extend({createStates:function(){this.states.add([new wp.media.controller.VideoDetails({media:this.media}),new wp.media.controller.MediaLibrary({type:"video",id:"add-video-source",title:wp.media.view.l10n.videoAddSourceTitle,toolbar:"add-video-source",media:this.media,menu:!1}),new wp.media.controller.MediaLibrary({type:"text",id:"add-track",title:wp.media.view.l10n.videoAddTrackTitle,toolbar:"add-track",media:this.media,menu:"video-details"})])}}),e=t.MediaWidgetModel.extend({}),d=t.MediaWidgetControl.extend({showDisplaySettings:!1,oembedResponses:{},mapModelToMediaFrameProps:function(e){e=t.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call(this,e);return e.link="embed",e},fetchEmbed:function(){var t=this,d=t.model.get("url");t.oembedResponses[d]||(t.fetchEmbedDfd&&"pending"===t.fetchEmbedDfd.state()&&t.fetchEmbedDfd.abort(),t.fetchEmbedDfd=wp.apiRequest({url:wp.media.view.settings.oEmbedProxyUrl,data:{url:t.model.get("url"),maxwidth:t.model.get("width"),maxheight:t.model.get("height"),discover:!1},type:"GET",dataType:"json",context:t}),t.fetchEmbedDfd.done(function(e){t.oembedResponses[d]=e,t.renderPreview()}),t.fetchEmbedDfd.fail(function(){t.oembedResponses[d]=null}))},isHostedVideo:function(){return!0},renderPreview:function(){var e,t,d,i=this,o="",a=!1,s=i.model.get("attachment_id"),m=i.model.get("url"),n=i.model.get("error");(s||m)&&((t=i.selectedAttachment.get("mime"))&&s?_.contains(_.values(wp.media.view.settings.embedMimes),t)||(n="unsupported_file_type"):s||((d=document.createElement("a")).href=m,(d=d.pathname.toLowerCase().match(/\.(\w+)$/))?_.contains(_.keys(wp.media.view.settings.embedMimes),d[1])||(n="unsupported_file_type"):a=!0),a&&(i.fetchEmbed(),i.oembedResponses[m]&&(e=i.oembedResponses[m].thumbnail_url,o=i.oembedResponses[m].html.replace(/\swidth="\d+"/,' width="100%"').replace(/\sheight="\d+"/,""))),d=i.$el.find(".media-widget-preview"),i=wp.template("wp-media-widget-video-preview"),d.html(i({model:{attachment_id:s,html:o,src:m,poster:e},is_oembed:a,error:n})),wp.mediaelement.initialize())},editMedia:function(){var t=this,e=t.mapModelToMediaFrameProps(t.model.toJSON()),d=new i({frame:"video",state:"video-details",metadata:e});(wp.media.frame=d).$el.addClass("media-widget"),e=function(e){t.selectedAttachment.set(e),t.model.set(_.extend(_.omit(t.model.defaults(),"title"),t.mapMediaToModelProps(e),{error:!1}))},d.state("video-details").on("update",e),d.state("replace-video").on("replace",e),d.on("close",function(){d.detach()}),d.open()}});t.controlConstructors.media_video=d,t.modelConstructors.media_video=e}(wp.mediaWidgets); media-image-widget.min.js 0000666 00000003750 15214027740 0011317 0 ustar 00 /*! This file is auto-generated */ !function(a,o){"use strict";var e=a.MediaWidgetModel.extend({}),t=a.MediaWidgetControl.extend({events:_.extend({},a.MediaWidgetControl.prototype.events,{"click .media-widget-preview.populated":"editMedia"}),renderPreview:function(){var e,t,i=this;(i.model.get("attachment_id")||i.model.get("url"))&&(t=i.$el.find(".media-widget-preview"),e=wp.template("wp-media-widget-image-preview"),t.html(e(i.previewTemplateProps.toJSON())),t.addClass("populated"),i.$el.find(".link").is(document.activeElement)||(e=i.$el.find(".media-widget-fields"),t=wp.template("wp-media-widget-image-fields"),e.html(t(i.previewTemplateProps.toJSON()))))},editMedia:function(){var i,e,a=this,t=a.mapModelToMediaFrameProps(a.model.toJSON());"none"===t.link&&(t.linkUrl=""),(i=wp.media({frame:"image",state:"image-details",metadata:t})).$el.addClass("media-widget"),t=function(){var e=i.state().attributes.image.toJSON(),t=e.link;e.link=e.linkUrl,a.selectedAttachment.set(e),a.displaySettings.set("link",t),a.model.set(_.extend(a.mapMediaToModelProps(e),{error:!1}))},i.state("image-details").on("update",t),i.state("replace-image").on("replace",t),e=wp.media.model.Attachment.prototype.sync,wp.media.model.Attachment.prototype.sync=function(){return o.Deferred().rejectWith(this).promise()},i.on("close",function(){i.detach(),wp.media.model.Attachment.prototype.sync=e}),i.open()},getEmbedResetProps:function(){return _.extend(a.MediaWidgetControl.prototype.getEmbedResetProps.call(this),{size:"full",width:0,height:0})},getModelPropsFromMediaFrame:function(e){return _.omit(a.MediaWidgetControl.prototype.getModelPropsFromMediaFrame.call(this,e),"image_title")},mapModelToPreviewTemplateProps:function(){var e=this,t=e.model.get("url"),i=a.MediaWidgetControl.prototype.mapModelToPreviewTemplateProps.call(e);return i.currentFilename=t?t.replace(/\?.*$/,"").replace(/^.+\//,""):"",i.link_url=e.model.get("link_url"),i}});a.controlConstructors.media_image=t,a.modelConstructors.media_image=e}(wp.mediaWidgets,jQuery); text-widgets.js 0000666 00000043251 15214027740 0007545 0 ustar 00 /** * @output wp-admin/js/widgets/text-widgets.js */ /* global tinymce, switchEditors */ /* eslint consistent-this: [ "error", "control" ] */ /** * @namespace wp.textWidgets */ wp.textWidgets = ( function( $ ) { 'use strict'; var component = { dismissedPointers: [], idBases: [ 'text' ] }; component.TextWidgetControl = Backbone.View.extend(/** @lends wp.textWidgets.TextWidgetControl.prototype */{ /** * View events. * * @type {Object} */ events: {}, /** * Text widget control. * * @constructs wp.textWidgets.TextWidgetControl * @augments Backbone.View * @abstract * * @param {Object} options - Options. * @param {jQuery} options.el - Control field container element. * @param {jQuery} options.syncContainer - Container element where fields are synced for the server. * * @return {void} */ initialize: function initialize( options ) { var control = this; if ( ! options.el ) { throw new Error( 'Missing options.el' ); } if ( ! options.syncContainer ) { throw new Error( 'Missing options.syncContainer' ); } Backbone.View.prototype.initialize.call( control, options ); control.syncContainer = options.syncContainer; control.$el.addClass( 'text-widget-fields' ); control.$el.html( wp.template( 'widget-text-control-fields' ) ); control.customHtmlWidgetPointer = control.$el.find( '.wp-pointer.custom-html-widget-pointer' ); if ( control.customHtmlWidgetPointer.length ) { control.customHtmlWidgetPointer.find( '.close' ).on( 'click', function( event ) { event.preventDefault(); control.customHtmlWidgetPointer.hide(); $( '#' + control.fields.text.attr( 'id' ) + '-html' ).focus(); control.dismissPointers( [ 'text_widget_custom_html' ] ); }); control.customHtmlWidgetPointer.find( '.add-widget' ).on( 'click', function( event ) { event.preventDefault(); control.customHtmlWidgetPointer.hide(); control.openAvailableWidgetsPanel(); }); } control.pasteHtmlPointer = control.$el.find( '.wp-pointer.paste-html-pointer' ); if ( control.pasteHtmlPointer.length ) { control.pasteHtmlPointer.find( '.close' ).on( 'click', function( event ) { event.preventDefault(); control.pasteHtmlPointer.hide(); control.editor.focus(); control.dismissPointers( [ 'text_widget_custom_html', 'text_widget_paste_html' ] ); }); } control.fields = { title: control.$el.find( '.title' ), text: control.$el.find( '.text' ) }; // Sync input fields to hidden sync fields which actually get sent to the server. _.each( control.fields, function( fieldInput, fieldName ) { fieldInput.on( 'input change', function updateSyncField() { var syncInput = control.syncContainer.find( '.sync-input.' + fieldName ); if ( syncInput.val() !== fieldInput.val() ) { syncInput.val( fieldInput.val() ); syncInput.trigger( 'change' ); } }); // Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event. fieldInput.val( control.syncContainer.find( '.sync-input.' + fieldName ).val() ); }); }, /** * Dismiss pointers for Custom HTML widget. * * @since 4.8.1 * * @param {Array} pointers Pointer IDs to dismiss. * @return {void} */ dismissPointers: function dismissPointers( pointers ) { _.each( pointers, function( pointer ) { wp.ajax.post( 'dismiss-wp-pointer', { pointer: pointer }); component.dismissedPointers.push( pointer ); }); }, /** * Open available widgets panel. * * @since 4.8.1 * @return {void} */ openAvailableWidgetsPanel: function openAvailableWidgetsPanel() { var sidebarControl; wp.customize.section.each( function( section ) { if ( section.extended( wp.customize.Widgets.SidebarSection ) && section.expanded() ) { sidebarControl = wp.customize.control( 'sidebars_widgets[' + section.params.sidebarId + ']' ); } }); if ( ! sidebarControl ) { return; } setTimeout( function() { // Timeout to prevent click event from causing panel to immediately collapse. wp.customize.Widgets.availableWidgetsPanel.open( sidebarControl ); wp.customize.Widgets.availableWidgetsPanel.$search.val( 'HTML' ).trigger( 'keyup' ); }); }, /** * Update input fields from the sync fields. * * This function is called at the widget-updated and widget-synced events. * A field will only be updated if it is not currently focused, to avoid * overwriting content that the user is entering. * * @return {void} */ updateFields: function updateFields() { var control = this, syncInput; if ( ! control.fields.title.is( document.activeElement ) ) { syncInput = control.syncContainer.find( '.sync-input.title' ); control.fields.title.val( syncInput.val() ); } syncInput = control.syncContainer.find( '.sync-input.text' ); if ( control.fields.text.is( ':visible' ) ) { if ( ! control.fields.text.is( document.activeElement ) ) { control.fields.text.val( syncInput.val() ); } } else if ( control.editor && ! control.editorFocused && syncInput.val() !== control.fields.text.val() ) { control.editor.setContent( wp.editor.autop( syncInput.val() ) ); } }, /** * Initialize editor. * * @return {void} */ initializeEditor: function initializeEditor() { var control = this, changeDebounceDelay = 1000, id, textarea, triggerChangeIfDirty, restoreTextMode = false, needsTextareaChangeTrigger = false, previousValue; textarea = control.fields.text; id = textarea.attr( 'id' ); previousValue = textarea.val(); /** * Trigger change if dirty. * * @return {void} */ triggerChangeIfDirty = function() { var updateWidgetBuffer = 300; // See wp.customize.Widgets.WidgetControl._setupUpdateUI() which uses 250ms for updateWidgetDebounced. if ( control.editor.isDirty() ) { /* * Account for race condition in customizer where user clicks Save & Publish while * focus was just previously given to the editor. Since updates to the editor * are debounced at 1 second and since widget input changes are only synced to * settings after 250ms, the customizer needs to be put into the processing * state during the time between the change event is triggered and updateWidget * logic starts. Note that the debounced update-widget request should be able * to be removed with the removal of the update-widget request entirely once * widgets are able to mutate their own instance props directly in JS without * having to make server round-trips to call the respective WP_Widget::update() * callbacks. See <https://core.trac.wordpress.org/ticket/33507>. */ if ( wp.customize && wp.customize.state ) { wp.customize.state( 'processing' ).set( wp.customize.state( 'processing' ).get() + 1 ); _.delay( function() { wp.customize.state( 'processing' ).set( wp.customize.state( 'processing' ).get() - 1 ); }, updateWidgetBuffer ); } if ( ! control.editor.isHidden() ) { control.editor.save(); } } // Trigger change on textarea when it has changed so the widget can enter a dirty state. if ( needsTextareaChangeTrigger && previousValue !== textarea.val() ) { textarea.trigger( 'change' ); needsTextareaChangeTrigger = false; previousValue = textarea.val(); } }; // Just-in-time force-update the hidden input fields. control.syncContainer.closest( '.widget' ).find( '[name=savewidget]:first' ).on( 'click', function onClickSaveButton() { triggerChangeIfDirty(); }); /** * Build (or re-build) the visual editor. * * @return {void} */ function buildEditor() { var editor, onInit, showPointerElement; // Abort building if the textarea is gone, likely due to the widget having been deleted entirely. if ( ! document.getElementById( id ) ) { return; } // The user has disabled TinyMCE. if ( typeof window.tinymce === 'undefined' ) { wp.editor.initialize( id, { quicktags: true, mediaButtons: true }); return; } // Destroy any existing editor so that it can be re-initialized after a widget-updated event. if ( tinymce.get( id ) ) { restoreTextMode = tinymce.get( id ).isHidden(); wp.editor.remove( id ); } // Add or enable the `wpview` plugin. $( document ).one( 'wp-before-tinymce-init.text-widget-init', function( event, init ) { // If somebody has removed all plugins, they must have a good reason. // Keep it that way. if ( ! init.plugins ) { return; } else if ( ! /\bwpview\b/.test( init.plugins ) ) { init.plugins += ',wpview'; } } ); wp.editor.initialize( id, { tinymce: { wpautop: true }, quicktags: true, mediaButtons: true }); /** * Show a pointer, focus on dismiss, and speak the contents for a11y. * * @param {jQuery} pointerElement Pointer element. * @return {void} */ showPointerElement = function( pointerElement ) { pointerElement.show(); pointerElement.find( '.close' ).focus(); wp.a11y.speak( pointerElement.find( 'h3, p' ).map( function() { return $( this ).text(); } ).get().join( '\n\n' ) ); }; editor = window.tinymce.get( id ); if ( ! editor ) { throw new Error( 'Failed to initialize editor' ); } onInit = function() { // When a widget is moved in the DOM the dynamically-created TinyMCE iframe will be destroyed and has to be re-built. $( editor.getWin() ).on( 'unload', function() { _.defer( buildEditor ); }); // If a prior mce instance was replaced, and it was in text mode, toggle to text mode. if ( restoreTextMode ) { switchEditors.go( id, 'html' ); } // Show the pointer. $( '#' + id + '-html' ).on( 'click', function() { control.pasteHtmlPointer.hide(); // Hide the HTML pasting pointer. if ( -1 !== component.dismissedPointers.indexOf( 'text_widget_custom_html' ) ) { return; } showPointerElement( control.customHtmlWidgetPointer ); }); // Hide the pointer when switching tabs. $( '#' + id + '-tmce' ).on( 'click', function() { control.customHtmlWidgetPointer.hide(); }); // Show pointer when pasting HTML. editor.on( 'pastepreprocess', function( event ) { var content = event.content; if ( -1 !== component.dismissedPointers.indexOf( 'text_widget_paste_html' ) || ! content || ! /<\w+.*?>/.test( content ) ) { return; } // Show the pointer after a slight delay so the user sees what they pasted. _.delay( function() { showPointerElement( control.pasteHtmlPointer ); }, 250 ); }); }; if ( editor.initialized ) { onInit(); } else { editor.on( 'init', onInit ); } control.editorFocused = false; editor.on( 'focus', function onEditorFocus() { control.editorFocused = true; }); editor.on( 'paste', function onEditorPaste() { editor.setDirty( true ); // Because pasting doesn't currently set the dirty state. triggerChangeIfDirty(); }); editor.on( 'NodeChange', function onNodeChange() { needsTextareaChangeTrigger = true; }); editor.on( 'NodeChange', _.debounce( triggerChangeIfDirty, changeDebounceDelay ) ); editor.on( 'blur hide', function onEditorBlur() { control.editorFocused = false; triggerChangeIfDirty(); }); control.editor = editor; } buildEditor(); } }); /** * Mapping of widget ID to instances of TextWidgetControl subclasses. * * @memberOf wp.textWidgets * * @type {Object.<string, wp.textWidgets.TextWidgetControl>} */ component.widgetControls = {}; /** * Handle widget being added or initialized for the first time at the widget-added event. * * @memberOf wp.textWidgets * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * * @return {void} */ component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone, fieldContainer, syncContainer; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. idBase = widgetForm.find( '> .id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } // Prevent initializing already-added widgets. widgetId = widgetForm.find( '.widget-id' ).val(); if ( component.widgetControls[ widgetId ] ) { return; } // Bypass using TinyMCE when widget is in legacy mode. if ( ! widgetForm.find( '.visual' ).val() ) { return; } /* * Create a container element for the widget control fields. * This is inserted into the DOM immediately before the .widget-content * element because the contents of this element are essentially "managed" * by PHP, where each widget update cause the entire element to be emptied * and replaced with the rendered output of WP_Widget::form() which is * sent back in Ajax request made to save/update the widget instance. * To prevent a "flash of replaced DOM elements and re-initialized JS * components", the JS template is rendered outside of the normal form * container. */ fieldContainer = $( '<div></div>' ); syncContainer = widgetContainer.find( '.widget-content:first' ); syncContainer.before( fieldContainer ); widgetControl = new component.TextWidgetControl({ el: fieldContainer, syncContainer: syncContainer }); component.widgetControls[ widgetId ] = widgetControl; /* * Render the widget once the widget parent's container finishes animating, * as the widget-added event fires with a slideDown of the container. * This ensures that the textarea is visible and an iframe can be embedded * with TinyMCE being able to set contenteditable on it. */ renderWhenAnimationDone = function() { if ( ! widgetContainer.hasClass( 'open' ) ) { setTimeout( renderWhenAnimationDone, animatedCheckDelay ); } else { widgetControl.initializeEditor(); } }; renderWhenAnimationDone(); }; /** * Setup widget in accessibility mode. * * @memberOf wp.textWidgets * * @return {void} */ component.setupAccessibleMode = function setupAccessibleMode() { var widgetForm, idBase, widgetControl, fieldContainer, syncContainer; widgetForm = $( '.editwidget > form' ); if ( 0 === widgetForm.length ) { return; } idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } // Bypass using TinyMCE when widget is in legacy mode. if ( ! widgetForm.find( '.visual' ).val() ) { return; } fieldContainer = $( '<div></div>' ); syncContainer = widgetForm.find( '> .widget-inside' ); syncContainer.before( fieldContainer ); widgetControl = new component.TextWidgetControl({ el: fieldContainer, syncContainer: syncContainer }); widgetControl.initializeEditor(); }; /** * Sync widget instance data sanitized from server back onto widget model. * * This gets called via the 'widget-updated' event when saving a widget from * the widgets admin screen and also via the 'widget-synced' event when making * a change to a widget in the customizer. * * @memberOf wp.textWidgets * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * @return {void} */ component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) { var widgetForm, widgetId, widgetControl, idBase; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); idBase = widgetForm.find( '> .id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } widgetId = widgetForm.find( '> .widget-id' ).val(); widgetControl = component.widgetControls[ widgetId ]; if ( ! widgetControl ) { return; } widgetControl.updateFields(); }; /** * Initialize functionality. * * This function exists to prevent the JS file from having to boot itself. * When WordPress enqueues this script, it should have an inline script * attached which calls wp.textWidgets.init(). * * @memberOf wp.textWidgets * * @return {void} */ component.init = function init() { var $document = $( document ); $document.on( 'widget-added', component.handleWidgetAdded ); $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated ); /* * Manually trigger widget-added events for media widgets on the admin * screen once they are expanded. The widget-added event is not triggered * for each pre-existing widget on the widgets admin screen like it is * on the customizer. Likewise, the customizer only triggers widget-added * when the widget is expanded to just-in-time construct the widget form * when it is actually going to be displayed. So the following implements * the same for the widgets admin screen, to invoke the widget-added * handler when a pre-existing media widget is expanded. */ $( function initializeExistingWidgetContainers() { var widgetContainers; if ( 'widgets' !== window.pagenow ) { return; } widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' ); widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() { var widgetContainer = $( this ); component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); }); // Accessibility mode. $( window ).on( 'load', function() { component.setupAccessibleMode(); }); }); }; return component; })( jQuery ); media-widgets.js 0000666 00000123342 15214027740 0007640 0 ustar 00 /** * @output wp-admin/js/widgets/media-widgets.js */ /* eslint consistent-this: [ "error", "control" ] */ /** * @namespace wp.mediaWidgets * @memberOf wp */ wp.mediaWidgets = ( function( $ ) { 'use strict'; var component = {}; /** * Widget control (view) constructors, mapping widget id_base to subclass of MediaWidgetControl. * * Media widgets register themselves by assigning subclasses of MediaWidgetControl onto this object by widget ID base. * * @memberOf wp.mediaWidgets * * @type {Object.<string, wp.mediaWidgets.MediaWidgetModel>} */ component.controlConstructors = {}; /** * Widget model constructors, mapping widget id_base to subclass of MediaWidgetModel. * * Media widgets register themselves by assigning subclasses of MediaWidgetControl onto this object by widget ID base. * * @memberOf wp.mediaWidgets * * @type {Object.<string, wp.mediaWidgets.MediaWidgetModel>} */ component.modelConstructors = {}; component.PersistentDisplaySettingsLibrary = wp.media.controller.Library.extend(/** @lends wp.mediaWidgets.PersistentDisplaySettingsLibrary.prototype */{ /** * Library which persists the customized display settings across selections. * * @constructs wp.mediaWidgets.PersistentDisplaySettingsLibrary * @augments wp.media.controller.Library * * @param {Object} options - Options. * * @return {void} */ initialize: function initialize( options ) { _.bindAll( this, 'handleDisplaySettingChange' ); wp.media.controller.Library.prototype.initialize.call( this, options ); }, /** * Sync changes to the current display settings back into the current customized. * * @param {Backbone.Model} displaySettings - Modified display settings. * @return {void} */ handleDisplaySettingChange: function handleDisplaySettingChange( displaySettings ) { this.get( 'selectedDisplaySettings' ).set( displaySettings.attributes ); }, /** * Get the display settings model. * * Model returned is updated with the current customized display settings, * and an event listener is added so that changes made to the settings * will sync back into the model storing the session's customized display * settings. * * @param {Backbone.Model} model - Display settings model. * @return {Backbone.Model} Display settings model. */ display: function getDisplaySettingsModel( model ) { var display, selectedDisplaySettings = this.get( 'selectedDisplaySettings' ); display = wp.media.controller.Library.prototype.display.call( this, model ); display.off( 'change', this.handleDisplaySettingChange ); // Prevent duplicated event handlers. display.set( selectedDisplaySettings.attributes ); if ( 'custom' === selectedDisplaySettings.get( 'link_type' ) ) { display.linkUrl = selectedDisplaySettings.get( 'link_url' ); } display.on( 'change', this.handleDisplaySettingChange ); return display; } }); /** * Extended view for managing the embed UI. * * @class wp.mediaWidgets.MediaEmbedView * @augments wp.media.view.Embed */ component.MediaEmbedView = wp.media.view.Embed.extend(/** @lends wp.mediaWidgets.MediaEmbedView.prototype */{ /** * Initialize. * * @since 4.9.0 * * @param {object} options - Options. * @return {void} */ initialize: function( options ) { var view = this, embedController; // eslint-disable-line consistent-this wp.media.view.Embed.prototype.initialize.call( view, options ); if ( 'image' !== view.controller.options.mimeType ) { embedController = view.controller.states.get( 'embed' ); embedController.off( 'scan', embedController.scanImage, embedController ); } }, /** * Refresh embed view. * * Forked override of {wp.media.view.Embed#refresh()} to suppress irrelevant "link text" field. * * @return {void} */ refresh: function refresh() { /** * @class wp.mediaWidgets~Constructor */ var Constructor; if ( 'image' === this.controller.options.mimeType ) { Constructor = wp.media.view.EmbedImage; } else { // This should be eliminated once #40450 lands of when this is merged into core. Constructor = wp.media.view.EmbedLink.extend(/** @lends wp.mediaWidgets~Constructor.prototype */{ /** * Set the disabled state on the Add to Widget button. * * @param {boolean} disabled - Disabled. * @return {void} */ setAddToWidgetButtonDisabled: function setAddToWidgetButtonDisabled( disabled ) { this.views.parent.views.parent.views.get( '.media-frame-toolbar' )[0].$el.find( '.media-button-select' ).prop( 'disabled', disabled ); }, /** * Set or clear an error notice. * * @param {string} notice - Notice. * @return {void} */ setErrorNotice: function setErrorNotice( notice ) { var embedLinkView = this, noticeContainer; // eslint-disable-line consistent-this noticeContainer = embedLinkView.views.parent.$el.find( '> .notice:first-child' ); if ( ! notice ) { if ( noticeContainer.length ) { noticeContainer.slideUp( 'fast' ); } } else { if ( ! noticeContainer.length ) { noticeContainer = $( '<div class="media-widget-embed-notice notice notice-error notice-alt"></div>' ); noticeContainer.hide(); embedLinkView.views.parent.$el.prepend( noticeContainer ); } noticeContainer.empty(); noticeContainer.append( $( '<p>', { html: notice })); noticeContainer.slideDown( 'fast' ); } }, /** * Update oEmbed. * * @since 4.9.0 * * @return {void} */ updateoEmbed: function() { var embedLinkView = this, url; // eslint-disable-line consistent-this url = embedLinkView.model.get( 'url' ); // Abort if the URL field was emptied out. if ( ! url ) { embedLinkView.setErrorNotice( '' ); embedLinkView.setAddToWidgetButtonDisabled( true ); return; } if ( ! url.match( /^(http|https):\/\/.+\// ) ) { embedLinkView.controller.$el.find( '#embed-url-field' ).addClass( 'invalid' ); embedLinkView.setAddToWidgetButtonDisabled( true ); } wp.media.view.EmbedLink.prototype.updateoEmbed.call( embedLinkView ); }, /** * Fetch media. * * @return {void} */ fetch: function() { var embedLinkView = this, fetchSuccess, matches, fileExt, urlParser, url, re, youTubeEmbedMatch; // eslint-disable-line consistent-this url = embedLinkView.model.get( 'url' ); if ( embedLinkView.dfd && 'pending' === embedLinkView.dfd.state() ) { embedLinkView.dfd.abort(); } fetchSuccess = function( response ) { embedLinkView.renderoEmbed({ data: { body: response } }); embedLinkView.controller.$el.find( '#embed-url-field' ).removeClass( 'invalid' ); embedLinkView.setErrorNotice( '' ); embedLinkView.setAddToWidgetButtonDisabled( false ); }; urlParser = document.createElement( 'a' ); urlParser.href = url; matches = urlParser.pathname.toLowerCase().match( /\.(\w+)$/ ); if ( matches ) { fileExt = matches[1]; if ( ! wp.media.view.settings.embedMimes[ fileExt ] ) { embedLinkView.renderFail(); } else if ( 0 !== wp.media.view.settings.embedMimes[ fileExt ].indexOf( embedLinkView.controller.options.mimeType ) ) { embedLinkView.renderFail(); } else { fetchSuccess( '<!--success-->' ); } return; } // Support YouTube embed links. re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/; youTubeEmbedMatch = re.exec( url ); if ( youTubeEmbedMatch ) { url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ]; // silently change url to proper oembed-able version. embedLinkView.model.attributes.url = url; } embedLinkView.dfd = wp.apiRequest({ url: wp.media.view.settings.oEmbedProxyUrl, data: { url: url, maxwidth: embedLinkView.model.get( 'width' ), maxheight: embedLinkView.model.get( 'height' ), discover: false }, type: 'GET', dataType: 'json', context: embedLinkView }); embedLinkView.dfd.done( function( response ) { if ( embedLinkView.controller.options.mimeType !== response.type ) { embedLinkView.renderFail(); return; } fetchSuccess( response.html ); }); embedLinkView.dfd.fail( _.bind( embedLinkView.renderFail, embedLinkView ) ); }, /** * Handle render failure. * * Overrides the {EmbedLink#renderFail()} method to prevent showing the "Link Text" field. * The element is getting display:none in the stylesheet, but the underlying method uses * uses {jQuery.fn.show()} which adds an inline style. This avoids the need for !important. * * @return {void} */ renderFail: function renderFail() { var embedLinkView = this; // eslint-disable-line consistent-this embedLinkView.controller.$el.find( '#embed-url-field' ).addClass( 'invalid' ); embedLinkView.setErrorNotice( embedLinkView.controller.options.invalidEmbedTypeError || 'ERROR' ); embedLinkView.setAddToWidgetButtonDisabled( true ); } }); } this.settings( new Constructor({ controller: this.controller, model: this.model.props, priority: 40 })); } }); /** * Custom media frame for selecting uploaded media or providing media by URL. * * @class wp.mediaWidgets.MediaFrameSelect * @augments wp.media.view.MediaFrame.Post */ component.MediaFrameSelect = wp.media.view.MediaFrame.Post.extend(/** @lends wp.mediaWidgets.MediaFrameSelect.prototype */{ /** * Create the default states. * * @return {void} */ createStates: function createStates() { var mime = this.options.mimeType, specificMimes = []; _.each( wp.media.view.settings.embedMimes, function( embedMime ) { if ( 0 === embedMime.indexOf( mime ) ) { specificMimes.push( embedMime ); } }); if ( specificMimes.length > 0 ) { mime = specificMimes; } this.states.add([ // Main states. new component.PersistentDisplaySettingsLibrary({ id: 'insert', title: this.options.title, selection: this.options.selection, priority: 20, toolbar: 'main-insert', filterable: 'dates', library: wp.media.query({ type: mime }), multiple: false, editable: true, selectedDisplaySettings: this.options.selectedDisplaySettings, displaySettings: _.isUndefined( this.options.showDisplaySettings ) ? true : this.options.showDisplaySettings, displayUserSettings: false // We use the display settings from the current/default widget instance props. }), new wp.media.controller.EditImage({ model: this.options.editImage }), // Embed states. new wp.media.controller.Embed({ metadata: this.options.metadata, type: 'image' === this.options.mimeType ? 'image' : 'link', invalidEmbedTypeError: this.options.invalidEmbedTypeError }) ]); }, /** * Main insert toolbar. * * Forked override of {wp.media.view.MediaFrame.Post#mainInsertToolbar()} to override text. * * @param {wp.Backbone.View} view - Toolbar view. * @this {wp.media.controller.Library} * @return {void} */ mainInsertToolbar: function mainInsertToolbar( view ) { var controller = this; // eslint-disable-line consistent-this view.set( 'insert', { style: 'primary', priority: 80, text: controller.options.text, // The whole reason for the fork. requires: { selection: true }, /** * Handle click. * * @ignore * * @fires wp.media.controller.State#insert() * @return {void} */ click: function onClick() { var state = controller.state(), selection = state.get( 'selection' ); controller.close(); state.trigger( 'insert', selection ).reset(); } }); }, /** * Main embed toolbar. * * Forked override of {wp.media.view.MediaFrame.Post#mainEmbedToolbar()} to override text. * * @param {wp.Backbone.View} toolbar - Toolbar view. * @this {wp.media.controller.Library} * @return {void} */ mainEmbedToolbar: function mainEmbedToolbar( toolbar ) { toolbar.view = new wp.media.view.Toolbar.Embed({ controller: this, text: this.options.text, event: 'insert' }); }, /** * Embed content. * * Forked override of {wp.media.view.MediaFrame.Post#embedContent()} to suppress irrelevant "link text" field. * * @return {void} */ embedContent: function embedContent() { var view = new component.MediaEmbedView({ controller: this, model: this.state() }).render(); this.content.set( view ); } }); component.MediaWidgetControl = Backbone.View.extend(/** @lends wp.mediaWidgets.MediaWidgetControl.prototype */{ /** * Translation strings. * * The mapping of translation strings is handled by media widget subclasses, * exported from PHP to JS such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). * * @type {Object} */ l10n: { add_to_widget: '{{add_to_widget}}', add_media: '{{add_media}}' }, /** * Widget ID base. * * This may be defined by the subclass. It may be exported from PHP to JS * such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). If not, * it will attempt to be discovered by looking to see if this control * instance extends each member of component.controlConstructors, and if * it does extend one, will use the key as the id_base. * * @type {string} */ id_base: '', /** * Mime type. * * This must be defined by the subclass. It may be exported from PHP to JS * such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). * * @type {string} */ mime_type: '', /** * View events. * * @type {Object} */ events: { 'click .notice-missing-attachment a': 'handleMediaLibraryLinkClick', 'click .select-media': 'selectMedia', 'click .placeholder': 'selectMedia', 'click .edit-media': 'editMedia' }, /** * Show display settings. * * @type {boolean} */ showDisplaySettings: true, /** * Media Widget Control. * * @constructs wp.mediaWidgets.MediaWidgetControl * @augments Backbone.View * @abstract * * @param {Object} options - Options. * @param {Backbone.Model} options.model - Model. * @param {jQuery} options.el - Control field container element. * @param {jQuery} options.syncContainer - Container element where fields are synced for the server. * * @return {void} */ initialize: function initialize( options ) { var control = this; Backbone.View.prototype.initialize.call( control, options ); if ( ! ( control.model instanceof component.MediaWidgetModel ) ) { throw new Error( 'Missing options.model' ); } if ( ! options.el ) { throw new Error( 'Missing options.el' ); } if ( ! options.syncContainer ) { throw new Error( 'Missing options.syncContainer' ); } control.syncContainer = options.syncContainer; control.$el.addClass( 'media-widget-control' ); // Allow methods to be passed in with control context preserved. _.bindAll( control, 'syncModelToInputs', 'render', 'updateSelectedAttachment', 'renderPreview' ); if ( ! control.id_base ) { _.find( component.controlConstructors, function( Constructor, idBase ) { if ( control instanceof Constructor ) { control.id_base = idBase; return true; } return false; }); if ( ! control.id_base ) { throw new Error( 'Missing id_base.' ); } } // Track attributes needed to renderPreview in it's own model. control.previewTemplateProps = new Backbone.Model( control.mapModelToPreviewTemplateProps() ); // Re-render the preview when the attachment changes. control.selectedAttachment = new wp.media.model.Attachment(); control.renderPreview = _.debounce( control.renderPreview ); control.listenTo( control.previewTemplateProps, 'change', control.renderPreview ); // Make sure a copy of the selected attachment is always fetched. control.model.on( 'change:attachment_id', control.updateSelectedAttachment ); control.model.on( 'change:url', control.updateSelectedAttachment ); control.updateSelectedAttachment(); /* * Sync the widget instance model attributes onto the hidden inputs that widgets currently use to store the state. * In the future, when widgets are JS-driven, the underlying widget instance data should be exposed as a model * from the start, without having to sync with hidden fields. See <https://core.trac.wordpress.org/ticket/33507>. */ control.listenTo( control.model, 'change', control.syncModelToInputs ); control.listenTo( control.model, 'change', control.syncModelToPreviewProps ); control.listenTo( control.model, 'change', control.render ); // Update the title. control.$el.on( 'input change', '.title', function updateTitle() { control.model.set({ title: $.trim( $( this ).val() ) }); }); // Update link_url attribute. control.$el.on( 'input change', '.link', function updateLinkUrl() { var linkUrl = $.trim( $( this ).val() ), linkType = 'custom'; if ( control.selectedAttachment.get( 'linkUrl' ) === linkUrl || control.selectedAttachment.get( 'link' ) === linkUrl ) { linkType = 'post'; } else if ( control.selectedAttachment.get( 'url' ) === linkUrl ) { linkType = 'file'; } control.model.set( { link_url: linkUrl, link_type: linkType }); // Update display settings for the next time the user opens to select from the media library. control.displaySettings.set( { link: linkType, linkUrl: linkUrl }); }); /* * Copy current display settings from the widget model to serve as basis * of customized display settings for the current media frame session. * Changes to display settings will be synced into this model, and * when a new selection is made, the settings from this will be synced * into that AttachmentDisplay's model to persist the setting changes. */ control.displaySettings = new Backbone.Model( _.pick( control.mapModelToMediaFrameProps( _.extend( control.model.defaults(), control.model.toJSON() ) ), _.keys( wp.media.view.settings.defaultProps ) ) ); }, /** * Update the selected attachment if necessary. * * @return {void} */ updateSelectedAttachment: function updateSelectedAttachment() { var control = this, attachment; if ( 0 === control.model.get( 'attachment_id' ) ) { control.selectedAttachment.clear(); control.model.set( 'error', false ); } else if ( control.model.get( 'attachment_id' ) !== control.selectedAttachment.get( 'id' ) ) { attachment = new wp.media.model.Attachment({ id: control.model.get( 'attachment_id' ) }); attachment.fetch() .done( function done() { control.model.set( 'error', false ); control.selectedAttachment.set( attachment.toJSON() ); }) .fail( function fail() { control.model.set( 'error', 'missing_attachment' ); }); } }, /** * Sync the model attributes to the hidden inputs, and update previewTemplateProps. * * @return {void} */ syncModelToPreviewProps: function syncModelToPreviewProps() { var control = this; control.previewTemplateProps.set( control.mapModelToPreviewTemplateProps() ); }, /** * Sync the model attributes to the hidden inputs, and update previewTemplateProps. * * @return {void} */ syncModelToInputs: function syncModelToInputs() { var control = this; control.syncContainer.find( '.media-widget-instance-property' ).each( function() { var input = $( this ), value, propertyName; propertyName = input.data( 'property' ); value = control.model.get( propertyName ); if ( _.isUndefined( value ) ) { return; } if ( 'array' === control.model.schema[ propertyName ].type && _.isArray( value ) ) { value = value.join( ',' ); } else if ( 'boolean' === control.model.schema[ propertyName ].type ) { value = value ? '1' : ''; // Because in PHP, strval( true ) === '1' && strval( false ) === ''. } else { value = String( value ); } if ( input.val() !== value ) { input.val( value ); input.trigger( 'change' ); } }); }, /** * Get template. * * @return {Function} Template. */ template: function template() { var control = this; if ( ! $( '#tmpl-widget-media-' + control.id_base + '-control' ).length ) { throw new Error( 'Missing widget control template for ' + control.id_base ); } return wp.template( 'widget-media-' + control.id_base + '-control' ); }, /** * Render template. * * @return {void} */ render: function render() { var control = this, titleInput; if ( ! control.templateRendered ) { control.$el.html( control.template()( control.model.toJSON() ) ); control.renderPreview(); // Hereafter it will re-render when control.selectedAttachment changes. control.templateRendered = true; } titleInput = control.$el.find( '.title' ); if ( ! titleInput.is( document.activeElement ) ) { titleInput.val( control.model.get( 'title' ) ); } control.$el.toggleClass( 'selected', control.isSelected() ); }, /** * Render media preview. * * @abstract * @return {void} */ renderPreview: function renderPreview() { throw new Error( 'renderPreview must be implemented' ); }, /** * Whether a media item is selected. * * @return {boolean} Whether selected and no error. */ isSelected: function isSelected() { var control = this; if ( control.model.get( 'error' ) ) { return false; } return Boolean( control.model.get( 'attachment_id' ) || control.model.get( 'url' ) ); }, /** * Handle click on link to Media Library to open modal, such as the link that appears when in the missing attachment error notice. * * @param {jQuery.Event} event - Event. * @return {void} */ handleMediaLibraryLinkClick: function handleMediaLibraryLinkClick( event ) { var control = this; event.preventDefault(); control.selectMedia(); }, /** * Open the media select frame to chose an item. * * @return {void} */ selectMedia: function selectMedia() { var control = this, selection, mediaFrame, defaultSync, mediaFrameProps, selectionModels = []; if ( control.isSelected() && 0 !== control.model.get( 'attachment_id' ) ) { selectionModels.push( control.selectedAttachment ); } selection = new wp.media.model.Selection( selectionModels, { multiple: false } ); mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() ); if ( mediaFrameProps.size ) { control.displaySettings.set( 'size', mediaFrameProps.size ); } mediaFrame = new component.MediaFrameSelect({ title: control.l10n.add_media, frame: 'post', text: control.l10n.add_to_widget, selection: selection, mimeType: control.mime_type, selectedDisplaySettings: control.displaySettings, showDisplaySettings: control.showDisplaySettings, metadata: mediaFrameProps, state: control.isSelected() && 0 === control.model.get( 'attachment_id' ) ? 'embed' : 'insert', invalidEmbedTypeError: control.l10n.unsupported_file_type }); wp.media.frame = mediaFrame; // See wp.media(). // Handle selection of a media item. mediaFrame.on( 'insert', function onInsert() { var attachment = {}, state = mediaFrame.state(); // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview. if ( 'embed' === state.get( 'id' ) ) { _.extend( attachment, { id: 0 }, state.props.toJSON() ); } else { _.extend( attachment, state.get( 'selection' ).first().toJSON() ); } control.selectedAttachment.set( attachment ); control.model.set( 'error', false ); // Update widget instance. control.model.set( control.getModelPropsFromMediaFrame( mediaFrame ) ); }); // Disable syncing of attachment changes back to server (except for deletions). See <https://core.trac.wordpress.org/ticket/40403>. defaultSync = wp.media.model.Attachment.prototype.sync; wp.media.model.Attachment.prototype.sync = function( method ) { if ( 'delete' === method ) { return defaultSync.apply( this, arguments ); } else { return $.Deferred().rejectWith( this ).promise(); } }; mediaFrame.on( 'close', function onClose() { wp.media.model.Attachment.prototype.sync = defaultSync; }); mediaFrame.$el.addClass( 'media-widget' ); mediaFrame.open(); // Clear the selected attachment when it is deleted in the media select frame. if ( selection ) { selection.on( 'destroy', function onDestroy( attachment ) { if ( control.model.get( 'attachment_id' ) === attachment.get( 'id' ) ) { control.model.set({ attachment_id: 0, url: '' }); } }); } /* * Make sure focus is set inside of modal so that hitting Esc will close * the modal and not inadvertently cause the widget to collapse in the customizer. */ mediaFrame.$el.find( '.media-frame-menu .media-menu-item.active' ).focus(); }, /** * Get the instance props from the media selection frame. * * @param {wp.media.view.MediaFrame.Select} mediaFrame - Select frame. * @return {Object} Props. */ getModelPropsFromMediaFrame: function getModelPropsFromMediaFrame( mediaFrame ) { var control = this, state, mediaFrameProps, modelProps; state = mediaFrame.state(); if ( 'insert' === state.get( 'id' ) ) { mediaFrameProps = state.get( 'selection' ).first().toJSON(); mediaFrameProps.postUrl = mediaFrameProps.link; if ( control.showDisplaySettings ) { _.extend( mediaFrameProps, mediaFrame.content.get( '.attachments-browser' ).sidebar.get( 'display' ).model.toJSON() ); } if ( mediaFrameProps.sizes && mediaFrameProps.size && mediaFrameProps.sizes[ mediaFrameProps.size ] ) { mediaFrameProps.url = mediaFrameProps.sizes[ mediaFrameProps.size ].url; } } else if ( 'embed' === state.get( 'id' ) ) { mediaFrameProps = _.extend( state.props.toJSON(), { attachment_id: 0 }, // Because some media frames use `attachment_id` not `id`. control.model.getEmbedResetProps() ); } else { throw new Error( 'Unexpected state: ' + state.get( 'id' ) ); } if ( mediaFrameProps.id ) { mediaFrameProps.attachment_id = mediaFrameProps.id; } modelProps = control.mapMediaToModelProps( mediaFrameProps ); // Clear the extension prop so sources will be reset for video and audio media. _.each( wp.media.view.settings.embedExts, function( ext ) { if ( ext in control.model.schema && modelProps.url !== modelProps[ ext ] ) { modelProps[ ext ] = ''; } }); return modelProps; }, /** * Map media frame props to model props. * * @param {Object} mediaFrameProps - Media frame props. * @return {Object} Model props. */ mapMediaToModelProps: function mapMediaToModelProps( mediaFrameProps ) { var control = this, mediaFramePropToModelPropMap = {}, modelProps = {}, extension; _.each( control.model.schema, function( fieldSchema, modelProp ) { // Ignore widget title attribute. if ( 'title' === modelProp ) { return; } mediaFramePropToModelPropMap[ fieldSchema.media_prop || modelProp ] = modelProp; }); _.each( mediaFrameProps, function( value, mediaProp ) { var propName = mediaFramePropToModelPropMap[ mediaProp ] || mediaProp; if ( control.model.schema[ propName ] ) { modelProps[ propName ] = value; } }); if ( 'custom' === mediaFrameProps.size ) { modelProps.width = mediaFrameProps.customWidth; modelProps.height = mediaFrameProps.customHeight; } if ( 'post' === mediaFrameProps.link ) { modelProps.link_url = mediaFrameProps.postUrl || mediaFrameProps.linkUrl; } else if ( 'file' === mediaFrameProps.link ) { modelProps.link_url = mediaFrameProps.url; } // Because some media frames use `id` instead of `attachment_id`. if ( ! mediaFrameProps.attachment_id && mediaFrameProps.id ) { modelProps.attachment_id = mediaFrameProps.id; } if ( mediaFrameProps.url ) { extension = mediaFrameProps.url.replace( /#.*$/, '' ).replace( /\?.*$/, '' ).split( '.' ).pop().toLowerCase(); if ( extension in control.model.schema ) { modelProps[ extension ] = mediaFrameProps.url; } } // Always omit the titles derived from mediaFrameProps. return _.omit( modelProps, 'title' ); }, /** * Map model props to media frame props. * * @param {Object} modelProps - Model props. * @return {Object} Media frame props. */ mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) { var control = this, mediaFrameProps = {}; _.each( modelProps, function( value, modelProp ) { var fieldSchema = control.model.schema[ modelProp ] || {}; mediaFrameProps[ fieldSchema.media_prop || modelProp ] = value; }); // Some media frames use attachment_id. mediaFrameProps.attachment_id = mediaFrameProps.id; if ( 'custom' === mediaFrameProps.size ) { mediaFrameProps.customWidth = control.model.get( 'width' ); mediaFrameProps.customHeight = control.model.get( 'height' ); } return mediaFrameProps; }, /** * Map model props to previewTemplateProps. * * @return {Object} Preview Template Props. */ mapModelToPreviewTemplateProps: function mapModelToPreviewTemplateProps() { var control = this, previewTemplateProps = {}; _.each( control.model.schema, function( value, prop ) { if ( ! value.hasOwnProperty( 'should_preview_update' ) || value.should_preview_update ) { previewTemplateProps[ prop ] = control.model.get( prop ); } }); // Templates need to be aware of the error. previewTemplateProps.error = control.model.get( 'error' ); return previewTemplateProps; }, /** * Open the media frame to modify the selected item. * * @abstract * @return {void} */ editMedia: function editMedia() { throw new Error( 'editMedia not implemented' ); } }); /** * Media widget model. * * @class wp.mediaWidgets.MediaWidgetModel * @augments Backbone.Model */ component.MediaWidgetModel = Backbone.Model.extend(/** @lends wp.mediaWidgets.MediaWidgetModel.prototype */{ /** * Id attribute. * * @type {string} */ idAttribute: 'widget_id', /** * Instance schema. * * This adheres to JSON Schema and subclasses should have their schema * exported from PHP to JS such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). * * @type {Object.<string, Object>} */ schema: { title: { type: 'string', 'default': '' }, attachment_id: { type: 'integer', 'default': 0 }, url: { type: 'string', 'default': '' } }, /** * Get default attribute values. * * @return {Object} Mapping of property names to their default values. */ defaults: function() { var defaults = {}; _.each( this.schema, function( fieldSchema, field ) { defaults[ field ] = fieldSchema['default']; }); return defaults; }, /** * Set attribute value(s). * * This is a wrapped version of Backbone.Model#set() which allows us to * cast the attribute values from the hidden inputs' string values into * the appropriate data types (integers or booleans). * * @param {string|Object} key - Attribute name or attribute pairs. * @param {mixed|Object} [val] - Attribute value or options object. * @param {Object} [options] - Options when attribute name and value are passed separately. * @return {wp.mediaWidgets.MediaWidgetModel} This model. */ set: function set( key, val, options ) { var model = this, attrs, opts, castedAttrs; // eslint-disable-line consistent-this if ( null === key ) { return model; } if ( 'object' === typeof key ) { attrs = key; opts = val; } else { attrs = {}; attrs[ key ] = val; opts = options; } castedAttrs = {}; _.each( attrs, function( value, name ) { var type; if ( ! model.schema[ name ] ) { castedAttrs[ name ] = value; return; } type = model.schema[ name ].type; if ( 'array' === type ) { castedAttrs[ name ] = value; if ( ! _.isArray( castedAttrs[ name ] ) ) { castedAttrs[ name ] = castedAttrs[ name ].split( /,/ ); // Good enough for parsing an ID list. } if ( model.schema[ name ].items && 'integer' === model.schema[ name ].items.type ) { castedAttrs[ name ] = _.filter( _.map( castedAttrs[ name ], function( id ) { return parseInt( id, 10 ); }, function( id ) { return 'number' === typeof id; } ) ); } } else if ( 'integer' === type ) { castedAttrs[ name ] = parseInt( value, 10 ); } else if ( 'boolean' === type ) { castedAttrs[ name ] = ! ( ! value || '0' === value || 'false' === value ); } else { castedAttrs[ name ] = value; } }); return Backbone.Model.prototype.set.call( this, castedAttrs, opts ); }, /** * Get props which are merged on top of the model when an embed is chosen (as opposed to an attachment). * * @return {Object} Reset/override props. */ getEmbedResetProps: function getEmbedResetProps() { return { id: 0 }; } }); /** * Collection of all widget model instances. * * @memberOf wp.mediaWidgets * * @type {Backbone.Collection} */ component.modelCollection = new ( Backbone.Collection.extend( { model: component.MediaWidgetModel }) )(); /** * Mapping of widget ID to instances of MediaWidgetControl subclasses. * * @memberOf wp.mediaWidgets * * @type {Object.<string, wp.mediaWidgets.MediaWidgetControl>} */ component.widgetControls = {}; /** * Handle widget being added or initialized for the first time at the widget-added event. * * @memberOf wp.mediaWidgets * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * * @return {void} */ component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { var fieldContainer, syncContainer, widgetForm, idBase, ControlConstructor, ModelConstructor, modelAttributes, widgetControl, widgetModel, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. idBase = widgetForm.find( '> .id_base' ).val(); widgetId = widgetForm.find( '> .widget-id' ).val(); // Prevent initializing already-added widgets. if ( component.widgetControls[ widgetId ] ) { return; } ControlConstructor = component.controlConstructors[ idBase ]; if ( ! ControlConstructor ) { return; } ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel; /* * Create a container element for the widget control (Backbone.View). * This is inserted into the DOM immediately before the .widget-content * element because the contents of this element are essentially "managed" * by PHP, where each widget update cause the entire element to be emptied * and replaced with the rendered output of WP_Widget::form() which is * sent back in Ajax request made to save/update the widget instance. * To prevent a "flash of replaced DOM elements and re-initialized JS * components", the JS template is rendered outside of the normal form * container. */ fieldContainer = $( '<div></div>' ); syncContainer = widgetContainer.find( '.widget-content:first' ); syncContainer.before( fieldContainer ); /* * Sync the widget instance model attributes onto the hidden inputs that widgets currently use to store the state. * In the future, when widgets are JS-driven, the underlying widget instance data should be exposed as a model * from the start, without having to sync with hidden fields. See <https://core.trac.wordpress.org/ticket/33507>. */ modelAttributes = {}; syncContainer.find( '.media-widget-instance-property' ).each( function() { var input = $( this ); modelAttributes[ input.data( 'property' ) ] = input.val(); }); modelAttributes.widget_id = widgetId; widgetModel = new ModelConstructor( modelAttributes ); widgetControl = new ControlConstructor({ el: fieldContainer, syncContainer: syncContainer, model: widgetModel }); /* * Render the widget once the widget parent's container finishes animating, * as the widget-added event fires with a slideDown of the container. * This ensures that the container's dimensions are fixed so that ME.js * can initialize with the proper dimensions. */ renderWhenAnimationDone = function() { if ( ! widgetContainer.hasClass( 'open' ) ) { setTimeout( renderWhenAnimationDone, animatedCheckDelay ); } else { widgetControl.render(); } }; renderWhenAnimationDone(); /* * Note that the model and control currently won't ever get garbage-collected * when a widget gets removed/deleted because there is no widget-removed event. */ component.modelCollection.add( [ widgetModel ] ); component.widgetControls[ widgetModel.get( 'widget_id' ) ] = widgetControl; }; /** * Setup widget in accessibility mode. * * @memberOf wp.mediaWidgets * * @return {void} */ component.setupAccessibleMode = function setupAccessibleMode() { var widgetForm, widgetId, idBase, widgetControl, ControlConstructor, ModelConstructor, modelAttributes, fieldContainer, syncContainer; widgetForm = $( '.editwidget > form' ); if ( 0 === widgetForm.length ) { return; } idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val(); ControlConstructor = component.controlConstructors[ idBase ]; if ( ! ControlConstructor ) { return; } widgetId = widgetForm.find( '> .widget-control-actions > .widget-id' ).val(); ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel; fieldContainer = $( '<div></div>' ); syncContainer = widgetForm.find( '> .widget-inside' ); syncContainer.before( fieldContainer ); modelAttributes = {}; syncContainer.find( '.media-widget-instance-property' ).each( function() { var input = $( this ); modelAttributes[ input.data( 'property' ) ] = input.val(); }); modelAttributes.widget_id = widgetId; widgetControl = new ControlConstructor({ el: fieldContainer, syncContainer: syncContainer, model: new ModelConstructor( modelAttributes ) }); component.modelCollection.add( [ widgetControl.model ] ); component.widgetControls[ widgetControl.model.get( 'widget_id' ) ] = widgetControl; widgetControl.render(); }; /** * Sync widget instance data sanitized from server back onto widget model. * * This gets called via the 'widget-updated' event when saving a widget from * the widgets admin screen and also via the 'widget-synced' event when making * a change to a widget in the customizer. * * @memberOf wp.mediaWidgets * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * * @return {void} */ component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) { var widgetForm, widgetContent, widgetId, widgetControl, attributes = {}; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); widgetId = widgetForm.find( '> .widget-id' ).val(); widgetControl = component.widgetControls[ widgetId ]; if ( ! widgetControl ) { return; } // Make sure the server-sanitized values get synced back into the model. widgetContent = widgetForm.find( '> .widget-content' ); widgetContent.find( '.media-widget-instance-property' ).each( function() { var property = $( this ).data( 'property' ); attributes[ property ] = $( this ).val(); }); // Suspend syncing model back to inputs when syncing from inputs to model, preventing infinite loop. widgetControl.stopListening( widgetControl.model, 'change', widgetControl.syncModelToInputs ); widgetControl.model.set( attributes ); widgetControl.listenTo( widgetControl.model, 'change', widgetControl.syncModelToInputs ); }; /** * Initialize functionality. * * This function exists to prevent the JS file from having to boot itself. * When WordPress enqueues this script, it should have an inline script * attached which calls wp.mediaWidgets.init(). * * @memberOf wp.mediaWidgets * * @return {void} */ component.init = function init() { var $document = $( document ); $document.on( 'widget-added', component.handleWidgetAdded ); $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated ); /* * Manually trigger widget-added events for media widgets on the admin * screen once they are expanded. The widget-added event is not triggered * for each pre-existing widget on the widgets admin screen like it is * on the customizer. Likewise, the customizer only triggers widget-added * when the widget is expanded to just-in-time construct the widget form * when it is actually going to be displayed. So the following implements * the same for the widgets admin screen, to invoke the widget-added * handler when a pre-existing media widget is expanded. */ $( function initializeExistingWidgetContainers() { var widgetContainers; if ( 'widgets' !== window.pagenow ) { return; } widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' ); widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() { var widgetContainer = $( this ); component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); }); // Accessibility mode. $( window ).on( 'load', function() { component.setupAccessibleMode(); }); }); }; return component; })( jQuery ); media-gallery-widget.js 0000666 00000024162 15214027740 0011112 0 ustar 00 /** * @output wp-admin/js/widgets/media-gallery-widget.js */ /* eslint consistent-this: [ "error", "control" ] */ (function( component ) { 'use strict'; var GalleryWidgetModel, GalleryWidgetControl, GalleryDetailsMediaFrame; /** * Custom gallery details frame. * * @since 4.9.0 * @class wp.mediaWidgets~GalleryDetailsMediaFrame * @augments wp.media.view.MediaFrame.Post */ GalleryDetailsMediaFrame = wp.media.view.MediaFrame.Post.extend(/** @lends wp.mediaWidgets~GalleryDetailsMediaFrame.prototype */{ /** * Create the default states. * * @since 4.9.0 * @return {void} */ createStates: function createStates() { this.states.add([ new wp.media.controller.Library({ id: 'gallery', title: wp.media.view.l10n.createGalleryTitle, priority: 40, toolbar: 'main-gallery', filterable: 'uploaded', multiple: 'add', editable: true, library: wp.media.query( _.defaults({ type: 'image' }, this.options.library ) ) }), // Gallery states. new wp.media.controller.GalleryEdit({ library: this.options.selection, editing: this.options.editing, menu: 'gallery' }), new wp.media.controller.GalleryAdd() ]); } } ); /** * Gallery widget model. * * See WP_Widget_Gallery::enqueue_admin_scripts() for amending prototype from PHP exports. * * @since 4.9.0 * * @class wp.mediaWidgets.modelConstructors.media_gallery * @augments wp.mediaWidgets.MediaWidgetModel */ GalleryWidgetModel = component.MediaWidgetModel.extend(/** @lends wp.mediaWidgets.modelConstructors.media_gallery.prototype */{} ); GalleryWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_gallery.prototype */{ /** * View events. * * @since 4.9.0 * @type {object} */ events: _.extend( {}, component.MediaWidgetControl.prototype.events, { 'click .media-widget-gallery-preview': 'editMedia' } ), /** * Gallery widget control. * * See WP_Widget_Gallery::enqueue_admin_scripts() for amending prototype from PHP exports. * * @constructs wp.mediaWidgets.controlConstructors.media_gallery * @augments wp.mediaWidgets.MediaWidgetControl * * @since 4.9.0 * @param {Object} options - Options. * @param {Backbone.Model} options.model - Model. * @param {jQuery} options.el - Control field container element. * @param {jQuery} options.syncContainer - Container element where fields are synced for the server. * @return {void} */ initialize: function initialize( options ) { var control = this; component.MediaWidgetControl.prototype.initialize.call( control, options ); _.bindAll( control, 'updateSelectedAttachments', 'handleAttachmentDestroy' ); control.selectedAttachments = new wp.media.model.Attachments(); control.model.on( 'change:ids', control.updateSelectedAttachments ); control.selectedAttachments.on( 'change', control.renderPreview ); control.selectedAttachments.on( 'reset', control.renderPreview ); control.updateSelectedAttachments(); /* * Refresh a Gallery widget partial when the user modifies one of the selected attachments. * This ensures that when an attachment's caption is updated in the media modal the Gallery * widget in the preview will then be refreshed to show the change. Normally doing this * would not be necessary because all of the state should be contained inside the changeset, * as everything done in the Customizer should not make a change to the site unless the * changeset itself is published. Attachments are a current exception to this rule. * For a proposal to include attachments in the customized state, see #37887. */ if ( wp.customize && wp.customize.previewer ) { control.selectedAttachments.on( 'change', function() { wp.customize.previewer.send( 'refresh-widget-partial', control.model.get( 'widget_id' ) ); } ); } }, /** * Update the selected attachments if necessary. * * @since 4.9.0 * @return {void} */ updateSelectedAttachments: function updateSelectedAttachments() { var control = this, newIds, oldIds, removedIds, addedIds, addedQuery; newIds = control.model.get( 'ids' ); oldIds = _.pluck( control.selectedAttachments.models, 'id' ); removedIds = _.difference( oldIds, newIds ); _.each( removedIds, function( removedId ) { control.selectedAttachments.remove( control.selectedAttachments.get( removedId ) ); }); addedIds = _.difference( newIds, oldIds ); if ( addedIds.length ) { addedQuery = wp.media.query({ order: 'ASC', orderby: 'post__in', perPage: -1, post__in: newIds, query: true, type: 'image' }); addedQuery.more().done( function() { control.selectedAttachments.reset( addedQuery.models ); }); } }, /** * Render preview. * * @since 4.9.0 * @return {void} */ renderPreview: function renderPreview() { var control = this, previewContainer, previewTemplate, data; previewContainer = control.$el.find( '.media-widget-preview' ); previewTemplate = wp.template( 'wp-media-widget-gallery-preview' ); data = control.previewTemplateProps.toJSON(); data.attachments = {}; control.selectedAttachments.each( function( attachment ) { data.attachments[ attachment.id ] = attachment.toJSON(); } ); previewContainer.html( previewTemplate( data ) ); }, /** * Determine whether there are selected attachments. * * @since 4.9.0 * @return {boolean} Selected. */ isSelected: function isSelected() { var control = this; if ( control.model.get( 'error' ) ) { return false; } return control.model.get( 'ids' ).length > 0; }, /** * Open the media select frame to edit images. * * @since 4.9.0 * @return {void} */ editMedia: function editMedia() { var control = this, selection, mediaFrame, mediaFrameProps; selection = new wp.media.model.Selection( control.selectedAttachments.models, { multiple: true }); mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() ); selection.gallery = new Backbone.Model( mediaFrameProps ); if ( mediaFrameProps.size ) { control.displaySettings.set( 'size', mediaFrameProps.size ); } mediaFrame = new GalleryDetailsMediaFrame({ frame: 'manage', text: control.l10n.add_to_widget, selection: selection, mimeType: control.mime_type, selectedDisplaySettings: control.displaySettings, showDisplaySettings: control.showDisplaySettings, metadata: mediaFrameProps, editing: true, multiple: true, state: 'gallery-edit' }); wp.media.frame = mediaFrame; // See wp.media(). // Handle selection of a media item. mediaFrame.on( 'update', function onUpdate( newSelection ) { var state = mediaFrame.state(), resultSelection; resultSelection = newSelection || state.get( 'selection' ); if ( ! resultSelection ) { return; } // Copy orderby_random from gallery state. if ( resultSelection.gallery ) { control.model.set( control.mapMediaToModelProps( resultSelection.gallery.toJSON() ) ); } // Directly update selectedAttachments to prevent needing to do additional request. control.selectedAttachments.reset( resultSelection.models ); // Update models in the widget instance. control.model.set( { ids: _.pluck( resultSelection.models, 'id' ) } ); } ); mediaFrame.$el.addClass( 'media-widget' ); mediaFrame.open(); if ( selection ) { selection.on( 'destroy', control.handleAttachmentDestroy ); } }, /** * Open the media select frame to chose an item. * * @since 4.9.0 * @return {void} */ selectMedia: function selectMedia() { var control = this, selection, mediaFrame, mediaFrameProps; selection = new wp.media.model.Selection( control.selectedAttachments.models, { multiple: true }); mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() ); if ( mediaFrameProps.size ) { control.displaySettings.set( 'size', mediaFrameProps.size ); } mediaFrame = new GalleryDetailsMediaFrame({ frame: 'select', text: control.l10n.add_to_widget, selection: selection, mimeType: control.mime_type, selectedDisplaySettings: control.displaySettings, showDisplaySettings: control.showDisplaySettings, metadata: mediaFrameProps, state: 'gallery' }); wp.media.frame = mediaFrame; // See wp.media(). // Handle selection of a media item. mediaFrame.on( 'update', function onUpdate( newSelection ) { var state = mediaFrame.state(), resultSelection; resultSelection = newSelection || state.get( 'selection' ); if ( ! resultSelection ) { return; } // Copy orderby_random from gallery state. if ( resultSelection.gallery ) { control.model.set( control.mapMediaToModelProps( resultSelection.gallery.toJSON() ) ); } // Directly update selectedAttachments to prevent needing to do additional request. control.selectedAttachments.reset( resultSelection.models ); // Update widget instance. control.model.set( { ids: _.pluck( resultSelection.models, 'id' ) } ); } ); mediaFrame.$el.addClass( 'media-widget' ); mediaFrame.open(); if ( selection ) { selection.on( 'destroy', control.handleAttachmentDestroy ); } /* * Make sure focus is set inside of modal so that hitting Esc will close * the modal and not inadvertently cause the widget to collapse in the customizer. */ mediaFrame.$el.find( ':focusable:first' ).focus(); }, /** * Clear the selected attachment when it is deleted in the media select frame. * * @since 4.9.0 * @param {wp.media.models.Attachment} attachment - Attachment. * @return {void} */ handleAttachmentDestroy: function handleAttachmentDestroy( attachment ) { var control = this; control.model.set( { ids: _.difference( control.model.get( 'ids' ), [ attachment.id ] ) } ); } } ); // Exports. component.controlConstructors.media_gallery = GalleryWidgetControl; component.modelConstructors.media_gallery = GalleryWidgetModel; })( wp.mediaWidgets ); media-audio-widget.js 0000666 00000010274 15214027740 0010553 0 ustar 00 /** * @output wp-admin/js/widgets/media-audio-widget.js */ /* eslint consistent-this: [ "error", "control" ] */ (function( component ) { 'use strict'; var AudioWidgetModel, AudioWidgetControl, AudioDetailsMediaFrame; /** * Custom audio details frame that removes the replace-audio state. * * @class wp.mediaWidgets.controlConstructors~AudioDetailsMediaFrame * @augments wp.media.view.MediaFrame.AudioDetails */ AudioDetailsMediaFrame = wp.media.view.MediaFrame.AudioDetails.extend(/** @lends wp.mediaWidgets.controlConstructors~AudioDetailsMediaFrame.prototype */{ /** * Create the default states. * * @return {void} */ createStates: function createStates() { this.states.add([ new wp.media.controller.AudioDetails({ media: this.media }), new wp.media.controller.MediaLibrary({ type: 'audio', id: 'add-audio-source', title: wp.media.view.l10n.audioAddSourceTitle, toolbar: 'add-audio-source', media: this.media, menu: false }) ]); } }); /** * Audio widget model. * * See WP_Widget_Audio::enqueue_admin_scripts() for amending prototype from PHP exports. * * @class wp.mediaWidgets.modelConstructors.media_audio * @augments wp.mediaWidgets.MediaWidgetModel */ AudioWidgetModel = component.MediaWidgetModel.extend({}); /** * Audio widget control. * * See WP_Widget_Audio::enqueue_admin_scripts() for amending prototype from PHP exports. * * @class wp.mediaWidgets.controlConstructors.media_audio * @augments wp.mediaWidgets.MediaWidgetControl */ AudioWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_audio.prototype */{ /** * Show display settings. * * @type {boolean} */ showDisplaySettings: false, /** * Map model props to media frame props. * * @param {Object} modelProps - Model props. * @return {Object} Media frame props. */ mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) { var control = this, mediaFrameProps; mediaFrameProps = component.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call( control, modelProps ); mediaFrameProps.link = 'embed'; return mediaFrameProps; }, /** * Render preview. * * @return {void} */ renderPreview: function renderPreview() { var control = this, previewContainer, previewTemplate, attachmentId, attachmentUrl; attachmentId = control.model.get( 'attachment_id' ); attachmentUrl = control.model.get( 'url' ); if ( ! attachmentId && ! attachmentUrl ) { return; } previewContainer = control.$el.find( '.media-widget-preview' ); previewTemplate = wp.template( 'wp-media-widget-audio-preview' ); previewContainer.html( previewTemplate({ model: { attachment_id: control.model.get( 'attachment_id' ), src: attachmentUrl }, error: control.model.get( 'error' ) })); wp.mediaelement.initialize(); }, /** * Open the media audio-edit frame to modify the selected item. * * @return {void} */ editMedia: function editMedia() { var control = this, mediaFrame, metadata, updateCallback; metadata = control.mapModelToMediaFrameProps( control.model.toJSON() ); // Set up the media frame. mediaFrame = new AudioDetailsMediaFrame({ frame: 'audio', state: 'audio-details', metadata: metadata }); wp.media.frame = mediaFrame; mediaFrame.$el.addClass( 'media-widget' ); updateCallback = function( mediaFrameProps ) { // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview. control.selectedAttachment.set( mediaFrameProps ); control.model.set( _.extend( control.model.defaults(), control.mapMediaToModelProps( mediaFrameProps ), { error: false } ) ); }; mediaFrame.state( 'audio-details' ).on( 'update', updateCallback ); mediaFrame.state( 'replace-audio' ).on( 'replace', updateCallback ); mediaFrame.on( 'close', function() { mediaFrame.detach(); }); mediaFrame.open(); } }); // Exports. component.controlConstructors.media_audio = AudioWidgetControl; component.modelConstructors.media_audio = AudioWidgetModel; })( wp.mediaWidgets ); custom-html-widgets.js 0000666 00000036416 15214027740 0011042 0 ustar 00 /** * @output wp-admin/js/widgets/custom-html-widgets.js */ /* global wp */ /* eslint consistent-this: [ "error", "control" ] */ /* eslint no-magic-numbers: ["error", { "ignore": [0,1,-1] }] */ /** * @namespace wp.customHtmlWidget * @memberOf wp */ wp.customHtmlWidgets = ( function( $ ) { 'use strict'; var component = { idBases: [ 'custom_html' ], codeEditorSettings: {}, l10n: { errorNotice: { singular: '', plural: '' } } }; component.CustomHtmlWidgetControl = Backbone.View.extend(/** @lends wp.customHtmlWidgets.CustomHtmlWidgetControl.prototype */{ /** * View events. * * @type {Object} */ events: {}, /** * Text widget control. * * @constructs wp.customHtmlWidgets.CustomHtmlWidgetControl * @augments Backbone.View * @abstract * * @param {Object} options - Options. * @param {jQuery} options.el - Control field container element. * @param {jQuery} options.syncContainer - Container element where fields are synced for the server. * * @return {void} */ initialize: function initialize( options ) { var control = this; if ( ! options.el ) { throw new Error( 'Missing options.el' ); } if ( ! options.syncContainer ) { throw new Error( 'Missing options.syncContainer' ); } Backbone.View.prototype.initialize.call( control, options ); control.syncContainer = options.syncContainer; control.widgetIdBase = control.syncContainer.parent().find( '.id_base' ).val(); control.widgetNumber = control.syncContainer.parent().find( '.widget_number' ).val(); control.customizeSettingId = 'widget_' + control.widgetIdBase + '[' + String( control.widgetNumber ) + ']'; control.$el.addClass( 'custom-html-widget-fields' ); control.$el.html( wp.template( 'widget-custom-html-control-fields' )( { codeEditorDisabled: component.codeEditorSettings.disabled } ) ); control.errorNoticeContainer = control.$el.find( '.code-editor-error-container' ); control.currentErrorAnnotations = []; control.saveButton = control.syncContainer.add( control.syncContainer.parent().find( '.widget-control-actions' ) ).find( '.widget-control-save, #savewidget' ); control.saveButton.addClass( 'custom-html-widget-save-button' ); // To facilitate style targeting. control.fields = { title: control.$el.find( '.title' ), content: control.$el.find( '.content' ) }; // Sync input fields to hidden sync fields which actually get sent to the server. _.each( control.fields, function( fieldInput, fieldName ) { fieldInput.on( 'input change', function updateSyncField() { var syncInput = control.syncContainer.find( '.sync-input.' + fieldName ); if ( syncInput.val() !== fieldInput.val() ) { syncInput.val( fieldInput.val() ); syncInput.trigger( 'change' ); } }); // Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event. fieldInput.val( control.syncContainer.find( '.sync-input.' + fieldName ).val() ); }); }, /** * Update input fields from the sync fields. * * This function is called at the widget-updated and widget-synced events. * A field will only be updated if it is not currently focused, to avoid * overwriting content that the user is entering. * * @return {void} */ updateFields: function updateFields() { var control = this, syncInput; if ( ! control.fields.title.is( document.activeElement ) ) { syncInput = control.syncContainer.find( '.sync-input.title' ); control.fields.title.val( syncInput.val() ); } /* * Prevent updating content when the editor is focused or if there are current error annotations, * to prevent the editor's contents from getting sanitized as soon as a user removes focus from * the editor. This is particularly important for users who cannot unfiltered_html. */ control.contentUpdateBypassed = control.fields.content.is( document.activeElement ) || control.editor && control.editor.codemirror.state.focused || 0 !== control.currentErrorAnnotations.length; if ( ! control.contentUpdateBypassed ) { syncInput = control.syncContainer.find( '.sync-input.content' ); control.fields.content.val( syncInput.val() ); } }, /** * Show linting error notice. * * @param {Array} errorAnnotations - Error annotations. * @return {void} */ updateErrorNotice: function( errorAnnotations ) { var control = this, errorNotice, message = '', customizeSetting; if ( 1 === errorAnnotations.length ) { message = component.l10n.errorNotice.singular.replace( '%d', '1' ); } else if ( errorAnnotations.length > 1 ) { message = component.l10n.errorNotice.plural.replace( '%d', String( errorAnnotations.length ) ); } if ( control.fields.content[0].setCustomValidity ) { control.fields.content[0].setCustomValidity( message ); } if ( wp.customize && wp.customize.has( control.customizeSettingId ) ) { customizeSetting = wp.customize( control.customizeSettingId ); customizeSetting.notifications.remove( 'htmlhint_error' ); if ( 0 !== errorAnnotations.length ) { customizeSetting.notifications.add( 'htmlhint_error', new wp.customize.Notification( 'htmlhint_error', { message: message, type: 'error' } ) ); } } else if ( 0 !== errorAnnotations.length ) { errorNotice = $( '<div class="inline notice notice-error notice-alt"></div>' ); errorNotice.append( $( '<p></p>', { text: message } ) ); control.errorNoticeContainer.empty(); control.errorNoticeContainer.append( errorNotice ); control.errorNoticeContainer.slideDown( 'fast' ); wp.a11y.speak( message ); } else { control.errorNoticeContainer.slideUp( 'fast' ); } }, /** * Initialize editor. * * @return {void} */ initializeEditor: function initializeEditor() { var control = this, settings; if ( component.codeEditorSettings.disabled ) { return; } settings = _.extend( {}, component.codeEditorSettings, { /** * Handle tabbing to the field before the editor. * * @ignore * * @return {void} */ onTabPrevious: function onTabPrevious() { control.fields.title.focus(); }, /** * Handle tabbing to the field after the editor. * * @ignore * * @return {void} */ onTabNext: function onTabNext() { var tabbables = control.syncContainer.add( control.syncContainer.parent().find( '.widget-position, .widget-control-actions' ) ).find( ':tabbable' ); tabbables.first().focus(); }, /** * Disable save button and store linting errors for use in updateFields. * * @ignore * * @param {Array} errorAnnotations - Error notifications. * @return {void} */ onChangeLintingErrors: function onChangeLintingErrors( errorAnnotations ) { control.currentErrorAnnotations = errorAnnotations; }, /** * Update error notice. * * @ignore * * @param {Array} errorAnnotations - Error annotations. * @return {void} */ onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) { control.saveButton.toggleClass( 'validation-blocked disabled', errorAnnotations.length > 0 ); control.updateErrorNotice( errorAnnotations ); } }); control.editor = wp.codeEditor.initialize( control.fields.content, settings ); // Improve the editor accessibility. $( control.editor.codemirror.display.lineDiv ) .attr({ role: 'textbox', 'aria-multiline': 'true', 'aria-labelledby': control.fields.content[0].id + '-label', 'aria-describedby': 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4' }); // Focus the editor when clicking on its label. $( '#' + control.fields.content[0].id + '-label' ).on( 'click', function() { control.editor.codemirror.focus(); }); control.fields.content.on( 'change', function() { if ( this.value !== control.editor.codemirror.getValue() ) { control.editor.codemirror.setValue( this.value ); } }); control.editor.codemirror.on( 'change', function() { var value = control.editor.codemirror.getValue(); if ( value !== control.fields.content.val() ) { control.fields.content.val( value ).trigger( 'change' ); } }); // Make sure the editor gets updated if the content was updated on the server (sanitization) but not updated in the editor since it was focused. control.editor.codemirror.on( 'blur', function() { if ( control.contentUpdateBypassed ) { control.syncContainer.find( '.sync-input.content' ).trigger( 'change' ); } }); // Prevent hitting Esc from collapsing the widget control. if ( wp.customize ) { control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) { var escKeyCode = 27; if ( escKeyCode === event.keyCode ) { event.stopPropagation(); } }); } } }); /** * Mapping of widget ID to instances of CustomHtmlWidgetControl subclasses. * * @alias wp.customHtmlWidgets.widgetControls * * @type {Object.<string, wp.textWidgets.CustomHtmlWidgetControl>} */ component.widgetControls = {}; /** * Handle widget being added or initialized for the first time at the widget-added event. * * @alias wp.customHtmlWidgets.handleWidgetAdded * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * * @return {void} */ component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone, fieldContainer, syncContainer; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. idBase = widgetForm.find( '> .id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } // Prevent initializing already-added widgets. widgetId = widgetForm.find( '.widget-id' ).val(); if ( component.widgetControls[ widgetId ] ) { return; } /* * Create a container element for the widget control fields. * This is inserted into the DOM immediately before the the .widget-content * element because the contents of this element are essentially "managed" * by PHP, where each widget update cause the entire element to be emptied * and replaced with the rendered output of WP_Widget::form() which is * sent back in Ajax request made to save/update the widget instance. * To prevent a "flash of replaced DOM elements and re-initialized JS * components", the JS template is rendered outside of the normal form * container. */ fieldContainer = $( '<div></div>' ); syncContainer = widgetContainer.find( '.widget-content:first' ); syncContainer.before( fieldContainer ); widgetControl = new component.CustomHtmlWidgetControl({ el: fieldContainer, syncContainer: syncContainer }); component.widgetControls[ widgetId ] = widgetControl; /* * Render the widget once the widget parent's container finishes animating, * as the widget-added event fires with a slideDown of the container. * This ensures that the textarea is visible and the editor can be initialized. */ renderWhenAnimationDone = function() { if ( ! ( wp.customize ? widgetContainer.parent().hasClass( 'expanded' ) : widgetContainer.hasClass( 'open' ) ) ) { // Core merge: The wp.customize condition can be eliminated with this change being in core: https://github.com/xwp/wordpress-develop/pull/247/commits/5322387d setTimeout( renderWhenAnimationDone, animatedCheckDelay ); } else { widgetControl.initializeEditor(); } }; renderWhenAnimationDone(); }; /** * Setup widget in accessibility mode. * * @alias wp.customHtmlWidgets.setupAccessibleMode * * @return {void} */ component.setupAccessibleMode = function setupAccessibleMode() { var widgetForm, idBase, widgetControl, fieldContainer, syncContainer; widgetForm = $( '.editwidget > form' ); if ( 0 === widgetForm.length ) { return; } idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } fieldContainer = $( '<div></div>' ); syncContainer = widgetForm.find( '> .widget-inside' ); syncContainer.before( fieldContainer ); widgetControl = new component.CustomHtmlWidgetControl({ el: fieldContainer, syncContainer: syncContainer }); widgetControl.initializeEditor(); }; /** * Sync widget instance data sanitized from server back onto widget model. * * This gets called via the 'widget-updated' event when saving a widget from * the widgets admin screen and also via the 'widget-synced' event when making * a change to a widget in the customizer. * * @alias wp.customHtmlWidgets.handleWidgetUpdated * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * @return {void} */ component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) { var widgetForm, widgetId, widgetControl, idBase; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); idBase = widgetForm.find( '> .id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } widgetId = widgetForm.find( '> .widget-id' ).val(); widgetControl = component.widgetControls[ widgetId ]; if ( ! widgetControl ) { return; } widgetControl.updateFields(); }; /** * Initialize functionality. * * This function exists to prevent the JS file from having to boot itself. * When WordPress enqueues this script, it should have an inline script * attached which calls wp.textWidgets.init(). * * @alias wp.customHtmlWidgets.init * * @param {object} settings - Options for code editor, exported from PHP. * * @return {void} */ component.init = function init( settings ) { var $document = $( document ); _.extend( component.codeEditorSettings, settings ); $document.on( 'widget-added', component.handleWidgetAdded ); $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated ); /* * Manually trigger widget-added events for media widgets on the admin * screen once they are expanded. The widget-added event is not triggered * for each pre-existing widget on the widgets admin screen like it is * on the customizer. Likewise, the customizer only triggers widget-added * when the widget is expanded to just-in-time construct the widget form * when it is actually going to be displayed. So the following implements * the same for the widgets admin screen, to invoke the widget-added * handler when a pre-existing media widget is expanded. */ $( function initializeExistingWidgetContainers() { var widgetContainers; if ( 'widgets' !== window.pagenow ) { return; } widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' ); widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() { var widgetContainer = $( this ); component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); }); // Accessibility mode. $( window ).on( 'load', function() { component.setupAccessibleMode(); }); }); }; return component; })( jQuery ); media-video-widget.js 0000666 00000015554 15214027740 0010566 0 ustar 00 /** * @output wp-admin/js/widgets/media-video-widget.js */ /* eslint consistent-this: [ "error", "control" ] */ (function( component ) { 'use strict'; var VideoWidgetModel, VideoWidgetControl, VideoDetailsMediaFrame; /** * Custom video details frame that removes the replace-video state. * * @class wp.mediaWidgets.controlConstructors~VideoDetailsMediaFrame * @augments wp.media.view.MediaFrame.VideoDetails * * @private */ VideoDetailsMediaFrame = wp.media.view.MediaFrame.VideoDetails.extend(/** @lends wp.mediaWidgets.controlConstructors~VideoDetailsMediaFrame.prototype */{ /** * Create the default states. * * @return {void} */ createStates: function createStates() { this.states.add([ new wp.media.controller.VideoDetails({ media: this.media }), new wp.media.controller.MediaLibrary({ type: 'video', id: 'add-video-source', title: wp.media.view.l10n.videoAddSourceTitle, toolbar: 'add-video-source', media: this.media, menu: false }), new wp.media.controller.MediaLibrary({ type: 'text', id: 'add-track', title: wp.media.view.l10n.videoAddTrackTitle, toolbar: 'add-track', media: this.media, menu: 'video-details' }) ]); } }); /** * Video widget model. * * See WP_Widget_Video::enqueue_admin_scripts() for amending prototype from PHP exports. * * @class wp.mediaWidgets.modelConstructors.media_video * @augments wp.mediaWidgets.MediaWidgetModel */ VideoWidgetModel = component.MediaWidgetModel.extend({}); /** * Video widget control. * * See WP_Widget_Video::enqueue_admin_scripts() for amending prototype from PHP exports. * * @class wp.mediaWidgets.controlConstructors.media_video * @augments wp.mediaWidgets.MediaWidgetControl */ VideoWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_video.prototype */{ /** * Show display settings. * * @type {boolean} */ showDisplaySettings: false, /** * Cache of oembed responses. * * @type {Object} */ oembedResponses: {}, /** * Map model props to media frame props. * * @param {Object} modelProps - Model props. * @return {Object} Media frame props. */ mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) { var control = this, mediaFrameProps; mediaFrameProps = component.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call( control, modelProps ); mediaFrameProps.link = 'embed'; return mediaFrameProps; }, /** * Fetches embed data for external videos. * * @return {void} */ fetchEmbed: function fetchEmbed() { var control = this, url; url = control.model.get( 'url' ); // If we already have a local cache of the embed response, return. if ( control.oembedResponses[ url ] ) { return; } // If there is an in-flight embed request, abort it. if ( control.fetchEmbedDfd && 'pending' === control.fetchEmbedDfd.state() ) { control.fetchEmbedDfd.abort(); } control.fetchEmbedDfd = wp.apiRequest({ url: wp.media.view.settings.oEmbedProxyUrl, data: { url: control.model.get( 'url' ), maxwidth: control.model.get( 'width' ), maxheight: control.model.get( 'height' ), discover: false }, type: 'GET', dataType: 'json', context: control }); control.fetchEmbedDfd.done( function( response ) { control.oembedResponses[ url ] = response; control.renderPreview(); }); control.fetchEmbedDfd.fail( function() { control.oembedResponses[ url ] = null; }); }, /** * Whether a url is a supported external host. * * @deprecated since 4.9. * * @return {boolean} Whether url is a supported video host. */ isHostedVideo: function isHostedVideo() { return true; }, /** * Render preview. * * @return {void} */ renderPreview: function renderPreview() { var control = this, previewContainer, previewTemplate, attachmentId, attachmentUrl, poster, html = '', isOEmbed = false, mime, error, urlParser, matches; attachmentId = control.model.get( 'attachment_id' ); attachmentUrl = control.model.get( 'url' ); error = control.model.get( 'error' ); if ( ! attachmentId && ! attachmentUrl ) { return; } // Verify the selected attachment mime is supported. mime = control.selectedAttachment.get( 'mime' ); if ( mime && attachmentId ) { if ( ! _.contains( _.values( wp.media.view.settings.embedMimes ), mime ) ) { error = 'unsupported_file_type'; } } else if ( ! attachmentId ) { urlParser = document.createElement( 'a' ); urlParser.href = attachmentUrl; matches = urlParser.pathname.toLowerCase().match( /\.(\w+)$/ ); if ( matches ) { if ( ! _.contains( _.keys( wp.media.view.settings.embedMimes ), matches[1] ) ) { error = 'unsupported_file_type'; } } else { isOEmbed = true; } } if ( isOEmbed ) { control.fetchEmbed(); if ( control.oembedResponses[ attachmentUrl ] ) { poster = control.oembedResponses[ attachmentUrl ].thumbnail_url; html = control.oembedResponses[ attachmentUrl ].html.replace( /\swidth="\d+"/, ' width="100%"' ).replace( /\sheight="\d+"/, '' ); } } previewContainer = control.$el.find( '.media-widget-preview' ); previewTemplate = wp.template( 'wp-media-widget-video-preview' ); previewContainer.html( previewTemplate({ model: { attachment_id: attachmentId, html: html, src: attachmentUrl, poster: poster }, is_oembed: isOEmbed, error: error })); wp.mediaelement.initialize(); }, /** * Open the media image-edit frame to modify the selected item. * * @return {void} */ editMedia: function editMedia() { var control = this, mediaFrame, metadata, updateCallback; metadata = control.mapModelToMediaFrameProps( control.model.toJSON() ); // Set up the media frame. mediaFrame = new VideoDetailsMediaFrame({ frame: 'video', state: 'video-details', metadata: metadata }); wp.media.frame = mediaFrame; mediaFrame.$el.addClass( 'media-widget' ); updateCallback = function( mediaFrameProps ) { // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview. control.selectedAttachment.set( mediaFrameProps ); control.model.set( _.extend( _.omit( control.model.defaults(), 'title' ), control.mapMediaToModelProps( mediaFrameProps ), { error: false } ) ); }; mediaFrame.state( 'video-details' ).on( 'update', updateCallback ); mediaFrame.state( 'replace-video' ).on( 'replace', updateCallback ); mediaFrame.on( 'close', function() { mediaFrame.detach(); }); mediaFrame.open(); } }); // Exports. component.controlConstructors.media_video = VideoWidgetControl; component.modelConstructors.media_video = VideoWidgetModel; })( wp.mediaWidgets ); custom-html-widgets.min.js 0000666 00000012646 15214027740 0011623 0 ustar 00 /*! This file is auto-generated */ wp.customHtmlWidgets=function(a){"use strict";var s={idBases:["custom_html"],codeEditorSettings:{},l10n:{errorNotice:{singular:"",plural:""}}};return s.CustomHtmlWidgetControl=Backbone.View.extend({events:{},initialize:function(e){var n=this;if(!e.el)throw new Error("Missing options.el");if(!e.syncContainer)throw new Error("Missing options.syncContainer");Backbone.View.prototype.initialize.call(n,e),n.syncContainer=e.syncContainer,n.widgetIdBase=n.syncContainer.parent().find(".id_base").val(),n.widgetNumber=n.syncContainer.parent().find(".widget_number").val(),n.customizeSettingId="widget_"+n.widgetIdBase+"["+String(n.widgetNumber)+"]",n.$el.addClass("custom-html-widget-fields"),n.$el.html(wp.template("widget-custom-html-control-fields")({codeEditorDisabled:s.codeEditorSettings.disabled})),n.errorNoticeContainer=n.$el.find(".code-editor-error-container"),n.currentErrorAnnotations=[],n.saveButton=n.syncContainer.add(n.syncContainer.parent().find(".widget-control-actions")).find(".widget-control-save, #savewidget"),n.saveButton.addClass("custom-html-widget-save-button"),n.fields={title:n.$el.find(".title"),content:n.$el.find(".content")},_.each(n.fields,function(t,i){t.on("input change",function(){var e=n.syncContainer.find(".sync-input."+i);e.val()!==t.val()&&(e.val(t.val()),e.trigger("change"))}),t.val(n.syncContainer.find(".sync-input."+i).val())})},updateFields:function(){var e,t=this;t.fields.title.is(document.activeElement)||(e=t.syncContainer.find(".sync-input.title"),t.fields.title.val(e.val())),t.contentUpdateBypassed=t.fields.content.is(document.activeElement)||t.editor&&t.editor.codemirror.state.focused||0!==t.currentErrorAnnotations.length,t.contentUpdateBypassed||(e=t.syncContainer.find(".sync-input.content"),t.fields.content.val(e.val()))},updateErrorNotice:function(e){var t,i=this,n="";1===e.length?n=s.l10n.errorNotice.singular.replace("%d","1"):1<e.length&&(n=s.l10n.errorNotice.plural.replace("%d",String(e.length))),i.fields.content[0].setCustomValidity&&i.fields.content[0].setCustomValidity(n),wp.customize&&wp.customize.has(i.customizeSettingId)?((t=wp.customize(i.customizeSettingId)).notifications.remove("htmlhint_error"),0!==e.length&&t.notifications.add("htmlhint_error",new wp.customize.Notification("htmlhint_error",{message:n,type:"error"}))):0!==e.length?((e=a('<div class="inline notice notice-error notice-alt"></div>')).append(a("<p></p>",{text:n})),i.errorNoticeContainer.empty(),i.errorNoticeContainer.append(e),i.errorNoticeContainer.slideDown("fast"),wp.a11y.speak(n)):i.errorNoticeContainer.slideUp("fast")},initializeEditor:function(){var e,t=this;s.codeEditorSettings.disabled||(e=_.extend({},s.codeEditorSettings,{onTabPrevious:function(){t.fields.title.focus()},onTabNext:function(){t.syncContainer.add(t.syncContainer.parent().find(".widget-position, .widget-control-actions")).find(":tabbable").first().focus()},onChangeLintingErrors:function(e){t.currentErrorAnnotations=e},onUpdateErrorNotice:function(e){t.saveButton.toggleClass("validation-blocked disabled",0<e.length),t.updateErrorNotice(e)}}),t.editor=wp.codeEditor.initialize(t.fields.content,e),a(t.editor.codemirror.display.lineDiv).attr({role:"textbox","aria-multiline":"true","aria-labelledby":t.fields.content[0].id+"-label","aria-describedby":"editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4"}),a("#"+t.fields.content[0].id+"-label").on("click",function(){t.editor.codemirror.focus()}),t.fields.content.on("change",function(){this.value!==t.editor.codemirror.getValue()&&t.editor.codemirror.setValue(this.value)}),t.editor.codemirror.on("change",function(){var e=t.editor.codemirror.getValue();e!==t.fields.content.val()&&t.fields.content.val(e).trigger("change")}),t.editor.codemirror.on("blur",function(){t.contentUpdateBypassed&&t.syncContainer.find(".sync-input.content").trigger("change")}),wp.customize&&t.editor.codemirror.on("keydown",function(e,t){27===t.keyCode&&t.stopPropagation()}))}}),s.widgetControls={},s.handleWidgetAdded=function(e,t){var i,n,o,d=t.find("> .widget-inside > .form, > .widget-inside > form"),r=d.find("> .id_base").val();-1!==s.idBases.indexOf(r)&&(n=d.find(".widget-id").val(),s.widgetControls[n]||(r=a("<div></div>"),(d=t.find(".widget-content:first")).before(r),i=new s.CustomHtmlWidgetControl({el:r,syncContainer:d}),s.widgetControls[n]=i,(o=function(){(wp.customize?t.parent().hasClass("expanded"):t.hasClass("open"))?i.initializeEditor():setTimeout(o,50)})()))},s.setupAccessibleMode=function(){var e,t=a(".editwidget > form");0!==t.length&&(e=t.find("> .widget-control-actions > .id_base").val(),-1!==s.idBases.indexOf(e)&&(e=a("<div></div>"),(t=t.find("> .widget-inside")).before(e),new s.CustomHtmlWidgetControl({el:e,syncContainer:t}).initializeEditor()))},s.handleWidgetUpdated=function(e,t){var i=t.find("> .widget-inside > .form, > .widget-inside > form"),t=i.find("> .id_base").val();-1!==s.idBases.indexOf(t)&&(i=i.find("> .widget-id").val(),(i=s.widgetControls[i])&&i.updateFields())},s.init=function(e){var t=a(document);_.extend(s.codeEditorSettings,e),t.on("widget-added",s.handleWidgetAdded),t.on("widget-synced widget-updated",s.handleWidgetUpdated),a(function(){"widgets"===window.pagenow&&(a(".widgets-holder-wrap:not(#available-widgets)").find("div.widget").one("click.toggle-widget-expanded",function(){var e=a(this);s.handleWidgetAdded(new jQuery.Event("widget-added"),e)}),a(window).on("load",function(){s.setupAccessibleMode()}))})},s}(jQuery); media-gallery-widget.min.js 0000666 00000007266 15214027740 0011702 0 ustar 00 /*! This file is auto-generated */ !function(i){"use strict";var a=wp.media.view.MediaFrame.Post.extend({createStates:function(){this.states.add([new wp.media.controller.Library({id:"gallery",title:wp.media.view.l10n.createGalleryTitle,priority:40,toolbar:"main-gallery",filterable:"uploaded",multiple:"add",editable:!0,library:wp.media.query(_.defaults({type:"image"},this.options.library))}),new wp.media.controller.GalleryEdit({library:this.options.selection,editing:this.options.editing,menu:"gallery"}),new wp.media.controller.GalleryAdd])}}),e=i.MediaWidgetModel.extend({}),t=i.MediaWidgetControl.extend({events:_.extend({},i.MediaWidgetControl.prototype.events,{"click .media-widget-gallery-preview":"editMedia"}),initialize:function(e){var t=this;i.MediaWidgetControl.prototype.initialize.call(t,e),_.bindAll(t,"updateSelectedAttachments","handleAttachmentDestroy"),t.selectedAttachments=new wp.media.model.Attachments,t.model.on("change:ids",t.updateSelectedAttachments),t.selectedAttachments.on("change",t.renderPreview),t.selectedAttachments.on("reset",t.renderPreview),t.updateSelectedAttachments(),wp.customize&&wp.customize.previewer&&t.selectedAttachments.on("change",function(){wp.customize.previewer.send("refresh-widget-partial",t.model.get("widget_id"))})},updateSelectedAttachments:function(){var e,t=this,i=t.model.get("ids"),d=_.pluck(t.selectedAttachments.models,"id"),a=_.difference(d,i);_.each(a,function(e){t.selectedAttachments.remove(t.selectedAttachments.get(e))}),_.difference(i,d).length&&(e=wp.media.query({order:"ASC",orderby:"post__in",perPage:-1,post__in:i,query:!0,type:"image"})).more().done(function(){t.selectedAttachments.reset(e.models)})},renderPreview:function(){var e=this,t=e.$el.find(".media-widget-preview"),i=wp.template("wp-media-widget-gallery-preview"),d=e.previewTemplateProps.toJSON();d.attachments={},e.selectedAttachments.each(function(e){d.attachments[e.id]=e.toJSON()}),t.html(i(d))},isSelected:function(){return!this.model.get("error")&&0<this.model.get("ids").length},editMedia:function(){var i,d=this,e=new wp.media.model.Selection(d.selectedAttachments.models,{multiple:!0}),t=d.mapModelToMediaFrameProps(d.model.toJSON());e.gallery=new Backbone.Model(t),t.size&&d.displaySettings.set("size",t.size),i=new a({frame:"manage",text:d.l10n.add_to_widget,selection:e,mimeType:d.mime_type,selectedDisplaySettings:d.displaySettings,showDisplaySettings:d.showDisplaySettings,metadata:t,editing:!0,multiple:!0,state:"gallery-edit"}),(wp.media.frame=i).on("update",function(e){var t=i.state(),t=e||t.get("selection");t&&(t.gallery&&d.model.set(d.mapMediaToModelProps(t.gallery.toJSON())),d.selectedAttachments.reset(t.models),d.model.set({ids:_.pluck(t.models,"id")}))}),i.$el.addClass("media-widget"),i.open(),e&&e.on("destroy",d.handleAttachmentDestroy)},selectMedia:function(){var i,d=this,e=new wp.media.model.Selection(d.selectedAttachments.models,{multiple:!0}),t=d.mapModelToMediaFrameProps(d.model.toJSON());t.size&&d.displaySettings.set("size",t.size),i=new a({frame:"select",text:d.l10n.add_to_widget,selection:e,mimeType:d.mime_type,selectedDisplaySettings:d.displaySettings,showDisplaySettings:d.showDisplaySettings,metadata:t,state:"gallery"}),(wp.media.frame=i).on("update",function(e){var t=i.state(),t=e||t.get("selection");t&&(t.gallery&&d.model.set(d.mapMediaToModelProps(t.gallery.toJSON())),d.selectedAttachments.reset(t.models),d.model.set({ids:_.pluck(t.models,"id")}))}),i.$el.addClass("media-widget"),i.open(),e&&e.on("destroy",d.handleAttachmentDestroy),i.$el.find(":focusable:first").focus()},handleAttachmentDestroy:function(e){this.model.set({ids:_.difference(this.model.get("ids"),[e.id])})}});i.controlConstructors.media_gallery=t,i.modelConstructors.media_gallery=e}(wp.mediaWidgets); media-image-widget.js 0000666 00000012534 15214027740 0010535 0 ustar 00 /** * @output wp-admin/js/widgets/media-image-widget.js */ /* eslint consistent-this: [ "error", "control" ] */ (function( component, $ ) { 'use strict'; var ImageWidgetModel, ImageWidgetControl; /** * Image widget model. * * See WP_Widget_Media_Image::enqueue_admin_scripts() for amending prototype from PHP exports. * * @class wp.mediaWidgets.modelConstructors.media_image * @augments wp.mediaWidgets.MediaWidgetModel */ ImageWidgetModel = component.MediaWidgetModel.extend({}); /** * Image widget control. * * See WP_Widget_Media_Image::enqueue_admin_scripts() for amending prototype from PHP exports. * * @class wp.mediaWidgets.controlConstructors.media_audio * @augments wp.mediaWidgets.MediaWidgetControl */ ImageWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_image.prototype */{ /** * View events. * * @type {object} */ events: _.extend( {}, component.MediaWidgetControl.prototype.events, { 'click .media-widget-preview.populated': 'editMedia' } ), /** * Render preview. * * @return {void} */ renderPreview: function renderPreview() { var control = this, previewContainer, previewTemplate, fieldsContainer, fieldsTemplate, linkInput; if ( ! control.model.get( 'attachment_id' ) && ! control.model.get( 'url' ) ) { return; } previewContainer = control.$el.find( '.media-widget-preview' ); previewTemplate = wp.template( 'wp-media-widget-image-preview' ); previewContainer.html( previewTemplate( control.previewTemplateProps.toJSON() ) ); previewContainer.addClass( 'populated' ); linkInput = control.$el.find( '.link' ); if ( ! linkInput.is( document.activeElement ) ) { fieldsContainer = control.$el.find( '.media-widget-fields' ); fieldsTemplate = wp.template( 'wp-media-widget-image-fields' ); fieldsContainer.html( fieldsTemplate( control.previewTemplateProps.toJSON() ) ); } }, /** * Open the media image-edit frame to modify the selected item. * * @return {void} */ editMedia: function editMedia() { var control = this, mediaFrame, updateCallback, defaultSync, metadata; metadata = control.mapModelToMediaFrameProps( control.model.toJSON() ); // Needed or else none will not be selected if linkUrl is not also empty. if ( 'none' === metadata.link ) { metadata.linkUrl = ''; } // Set up the media frame. mediaFrame = wp.media({ frame: 'image', state: 'image-details', metadata: metadata }); mediaFrame.$el.addClass( 'media-widget' ); updateCallback = function() { var mediaProps, linkType; // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview. mediaProps = mediaFrame.state().attributes.image.toJSON(); linkType = mediaProps.link; mediaProps.link = mediaProps.linkUrl; control.selectedAttachment.set( mediaProps ); control.displaySettings.set( 'link', linkType ); control.model.set( _.extend( control.mapMediaToModelProps( mediaProps ), { error: false } ) ); }; mediaFrame.state( 'image-details' ).on( 'update', updateCallback ); mediaFrame.state( 'replace-image' ).on( 'replace', updateCallback ); // Disable syncing of attachment changes back to server. See <https://core.trac.wordpress.org/ticket/40403>. defaultSync = wp.media.model.Attachment.prototype.sync; wp.media.model.Attachment.prototype.sync = function rejectedSync() { return $.Deferred().rejectWith( this ).promise(); }; mediaFrame.on( 'close', function onClose() { mediaFrame.detach(); wp.media.model.Attachment.prototype.sync = defaultSync; }); mediaFrame.open(); }, /** * Get props which are merged on top of the model when an embed is chosen (as opposed to an attachment). * * @return {Object} Reset/override props. */ getEmbedResetProps: function getEmbedResetProps() { return _.extend( component.MediaWidgetControl.prototype.getEmbedResetProps.call( this ), { size: 'full', width: 0, height: 0 } ); }, /** * Get the instance props from the media selection frame. * * Prevent the image_title attribute from being initially set when adding an image from the media library. * * @param {wp.media.view.MediaFrame.Select} mediaFrame - Select frame. * @return {Object} Props. */ getModelPropsFromMediaFrame: function getModelPropsFromMediaFrame( mediaFrame ) { var control = this; return _.omit( component.MediaWidgetControl.prototype.getModelPropsFromMediaFrame.call( control, mediaFrame ), 'image_title' ); }, /** * Map model props to preview template props. * * @return {Object} Preview template props. */ mapModelToPreviewTemplateProps: function mapModelToPreviewTemplateProps() { var control = this, previewTemplateProps, url; url = control.model.get( 'url' ); previewTemplateProps = component.MediaWidgetControl.prototype.mapModelToPreviewTemplateProps.call( control ); previewTemplateProps.currentFilename = url ? url.replace( /\?.*$/, '' ).replace( /^.+\//, '' ) : ''; previewTemplateProps.link_url = control.model.get( 'link_url' ); return previewTemplateProps; } }); // Exports. component.controlConstructors.media_image = ImageWidgetControl; component.modelConstructors.media_image = ImageWidgetModel; })( wp.mediaWidgets, jQuery );
dvadf
dvadf
| ver. 1.4 |
Github
|
.
| PHP 7.0.33 | Generation time: 0 |
proxy
|
phpinfo
|
Settings