home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / widgets / class-wp-widget-media.php < prev    next >
Encoding:
PHP Script  |  2017-10-19  |  13.5 KB  |  445 lines

  1. <?php
  2. /**
  3.  * Widget API: WP_Media_Widget class
  4.  *
  5.  * @package WordPress
  6.  * @subpackage Widgets
  7.  * @since 4.8.0
  8.  */
  9.  
  10. /**
  11.  * Core class that implements a media widget.
  12.  *
  13.  * @since 4.8.0
  14.  *
  15.  * @see WP_Widget
  16.  */
  17. abstract class WP_Widget_Media extends WP_Widget {
  18.  
  19.     /**
  20.      * Translation labels.
  21.      *
  22.      * @since 4.8.0
  23.      * @var array
  24.      */
  25.     public $l10n = array(
  26.         'add_to_widget' => '',
  27.         'replace_media' => '',
  28.         'edit_media' => '',
  29.         'media_library_state_multi' => '',
  30.         'media_library_state_single' => '',
  31.         'missing_attachment' => '',
  32.         'no_media_selected' => '',
  33.         'add_media' => '',
  34.     );
  35.  
  36.     /**
  37.      * Whether or not the widget has been registered yet.
  38.      *
  39.      * @since 4.8.1
  40.      * @var bool
  41.      */
  42.     protected $registered = false;
  43.  
  44.     /**
  45.      * Constructor.
  46.      *
  47.      * @since 4.8.0
  48.      *
  49.      * @param string $id_base         Base ID for the widget, lowercase and unique.
  50.      * @param string $name            Name for the widget displayed on the configuration page.
  51.      * @param array  $widget_options  Optional. Widget options. See wp_register_sidebar_widget() for
  52.      *                                information on accepted arguments. Default empty array.
  53.      * @param array  $control_options Optional. Widget control options. See wp_register_widget_control()
  54.      *                                for information on accepted arguments. Default empty array.
  55.      */
  56.     public function __construct( $id_base, $name, $widget_options = array(), $control_options = array() ) {
  57.         $widget_opts = wp_parse_args( $widget_options, array(
  58.             'description' => __( 'A media item.' ),
  59.             'customize_selective_refresh' => true,
  60.             'mime_type' => '',
  61.         ) );
  62.  
  63.         $control_opts = wp_parse_args( $control_options, array() );
  64.  
  65.         $l10n_defaults = array(
  66.             'no_media_selected' => __( 'No media selected' ),
  67.             'add_media' => _x( 'Add Media', 'label for button in the media widget' ),
  68.             'replace_media' => _x( 'Replace Media', 'label for button in the media widget; should preferably not be longer than ~13 characters long' ),
  69.             'edit_media' => _x( 'Edit Media', 'label for button in the media widget; should preferably not be longer than ~13 characters long' ),
  70.             'add_to_widget' => __( 'Add to Widget' ),
  71.             'missing_attachment' => sprintf(
  72.                 /* translators: %s: URL to media library */
  73.                 __( 'We can’t find that file. Check your <a href="%s">media library</a> and make sure it wasn’t deleted.' ),
  74.                 esc_url( admin_url( 'upload.php' ) )
  75.             ),
  76.             /* translators: %d: widget count */
  77.             'media_library_state_multi' => _n_noop( 'Media Widget (%d)', 'Media Widget (%d)' ),
  78.             'media_library_state_single' => __( 'Media Widget' ),
  79.             'unsupported_file_type' => __( 'Looks like this isn’t the correct kind of file. Please link to an appropriate file instead.' ),
  80.         );
  81.         $this->l10n = array_merge( $l10n_defaults, array_filter( $this->l10n ) );
  82.  
  83.         parent::__construct(
  84.             $id_base,
  85.             $name,
  86.             $widget_opts,
  87.             $control_opts
  88.         );
  89.     }
  90.  
  91.     /**
  92.      * Add hooks while registering all widget instances of this widget class.
  93.      *
  94.      * @since 4.8.0
  95.      *
  96.      * @param integer $number Optional. The unique order number of this widget instance
  97.      *                        compared to other instances of the same class. Default -1.
  98.      */
  99.     public function _register_one( $number = -1 ) {
  100.         parent::_register_one( $number );
  101.         if ( $this->registered ) {
  102.             return;
  103.         }
  104.         $this->registered = true;
  105.  
  106.         // Note that the widgets component in the customizer will also do the 'admin_print_scripts-widgets.php' action in WP_Customize_Widgets::print_scripts().
  107.         add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) );
  108.  
  109.         if ( $this->is_preview() ) {
  110.             add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) );
  111.         }
  112.  
  113.         // Note that the widgets component in the customizer will also do the 'admin_footer-widgets.php' action in WP_Customize_Widgets::print_footer_scripts().
  114.         add_action( 'admin_footer-widgets.php', array( $this, 'render_control_template_scripts' ) );
  115.  
  116.         add_filter( 'display_media_states', array( $this, 'display_media_state' ), 10, 2 );
  117.     }
  118.  
  119.     /**
  120.      * Get schema for properties of a widget instance (item).
  121.      *
  122.      * @since  4.8.0
  123.      *
  124.      * @see WP_REST_Controller::get_item_schema()
  125.      * @see WP_REST_Controller::get_additional_fields()
  126.      * @link https://core.trac.wordpress.org/ticket/35574
  127.      * @return array Schema for properties.
  128.      */
  129.     public function get_instance_schema() {
  130.         $schema = array(
  131.             'attachment_id' => array(
  132.                 'type' => 'integer',
  133.                 'default' => 0,
  134.                 'minimum' => 0,
  135.                 'description' => __( 'Attachment post ID' ),
  136.                 'media_prop' => 'id',
  137.             ),
  138.             'url' => array(
  139.                 'type' => 'string',
  140.                 'default' => '',
  141.                 'format' => 'uri',
  142.                 'description' => __( 'URL to the media file' ),
  143.             ),
  144.             'title' => array(
  145.                 'type' => 'string',
  146.                 'default' => '',
  147.                 'sanitize_callback' => 'sanitize_text_field',
  148.                 'description' => __( 'Title for the widget' ),
  149.                 'should_preview_update' => false,
  150.             ),
  151.         );
  152.  
  153.         /**
  154.          * Filters the media widget instance schema to add additional properties.
  155.          *
  156.          * @since 4.9.0
  157.          *
  158.          * @param array           $schema Instance schema.
  159.          * @param WP_Widget_Media $this   Widget object.
  160.          */
  161.         $schema = apply_filters( "widget_{$this->id_base}_instance_schema", $schema, $this );
  162.  
  163.         return $schema;
  164.     }
  165.  
  166.     /**
  167.      * Determine if the supplied attachment is for a valid attachment post with the specified MIME type.
  168.      *
  169.      * @since 4.8.0
  170.      *
  171.      * @param int|WP_Post $attachment Attachment post ID or object.
  172.      * @param string      $mime_type  MIME type.
  173.      * @return bool Is matching MIME type.
  174.      */
  175.     public function is_attachment_with_mime_type( $attachment, $mime_type ) {
  176.         if ( empty( $attachment ) ) {
  177.             return false;
  178.         }
  179.         $attachment = get_post( $attachment );
  180.         if ( ! $attachment ) {
  181.             return false;
  182.         }
  183.         if ( 'attachment' !== $attachment->post_type ) {
  184.             return false;
  185.         }
  186.         return wp_attachment_is( $mime_type, $attachment );
  187.     }
  188.  
  189.     /**
  190.      * Sanitize a token list string, such as used in HTML rel and class attributes.
  191.      *
  192.      * @since 4.8.0
  193.      *
  194.      * @link http://w3c.github.io/html/infrastructure.html#space-separated-tokens
  195.      * @link https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList
  196.      * @param string|array $tokens List of tokens separated by spaces, or an array of tokens.
  197.      * @return string Sanitized token string list.
  198.      */
  199.     public function sanitize_token_list( $tokens ) {
  200.         if ( is_string( $tokens ) ) {
  201.             $tokens = preg_split( '/\s+/', trim( $tokens ) );
  202.         }
  203.         $tokens = array_map( 'sanitize_html_class', $tokens );
  204.         $tokens = array_filter( $tokens );
  205.         return join( ' ', $tokens );
  206.     }
  207.  
  208.     /**
  209.      * Displays the widget on the front-end.
  210.      *
  211.      * @since 4.8.0
  212.      *
  213.      * @see WP_Widget::widget()
  214.      *
  215.      * @param array $args     Display arguments including before_title, after_title, before_widget, and after_widget.
  216.      * @param array $instance Saved setting from the database.
  217.      */
  218.     public function widget( $args, $instance ) {
  219.         $instance = wp_parse_args( $instance, wp_list_pluck( $this->get_instance_schema(), 'default' ) );
  220.  
  221.         // Short-circuit if no media is selected.
  222.         if ( ! $this->has_content( $instance ) ) {
  223.             return;
  224.         }
  225.  
  226.         echo $args['before_widget'];
  227.  
  228.         /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
  229.         $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
  230.  
  231.         if ( $title ) {
  232.             echo $args['before_title'] . $title . $args['after_title'];
  233.         }
  234.  
  235.         /**
  236.          * Filters the media widget instance prior to rendering the media.
  237.          *
  238.          * @since 4.8.0
  239.          *
  240.          * @param array           $instance Instance data.
  241.          * @param array           $args     Widget args.
  242.          * @param WP_Widget_Media $this     Widget object.
  243.          */
  244.         $instance = apply_filters( "widget_{$this->id_base}_instance", $instance, $args, $this );
  245.  
  246.         $this->render_media( $instance );
  247.  
  248.         echo $args['after_widget'];
  249.     }
  250.  
  251.     /**
  252.      * Sanitizes the widget form values as they are saved.
  253.      *
  254.      * @since 4.8.0
  255.      *
  256.      * @see WP_Widget::update()
  257.      * @see WP_REST_Request::has_valid_params()
  258.      * @see WP_REST_Request::sanitize_params()
  259.      *
  260.      * @param array $new_instance Values just sent to be saved.
  261.      * @param array $instance     Previously saved values from database.
  262.      * @return array Updated safe values to be saved.
  263.      */
  264.     public function update( $new_instance, $instance ) {
  265.  
  266.         $schema = $this->get_instance_schema();
  267.         foreach ( $schema as $field => $field_schema ) {
  268.             if ( ! array_key_exists( $field, $new_instance ) ) {
  269.                 continue;
  270.             }
  271.             $value = $new_instance[ $field ];
  272.  
  273.             // Workaround for rest_validate_value_from_schema() due to the fact that rest_is_boolean( '' ) === false, while rest_is_boolean( '1' ) is true.
  274.             if ( 'boolean' === $field_schema['type'] && '' === $value ) {
  275.                 $value = false;
  276.             }
  277.  
  278.             if ( true !== rest_validate_value_from_schema( $value, $field_schema, $field ) ) {
  279.                 continue;
  280.             }
  281.  
  282.             $value = rest_sanitize_value_from_schema( $value, $field_schema );
  283.  
  284.             // @codeCoverageIgnoreStart
  285.             if ( is_wp_error( $value ) ) {
  286.                 continue; // Handle case when rest_sanitize_value_from_schema() ever returns WP_Error as its phpdoc @return tag indicates.
  287.             }
  288.  
  289.             // @codeCoverageIgnoreEnd
  290.             if ( isset( $field_schema['sanitize_callback'] ) ) {
  291.                 $value = call_user_func( $field_schema['sanitize_callback'], $value );
  292.             }
  293.             if ( is_wp_error( $value ) ) {
  294.                 continue;
  295.             }
  296.             $instance[ $field ] = $value;
  297.         }
  298.  
  299.         return $instance;
  300.     }
  301.  
  302.     /**
  303.      * Render the media on the frontend.
  304.      *
  305.      * @since 4.8.0
  306.      *
  307.      * @param array $instance Widget instance props.
  308.      * @return string
  309.      */
  310.     abstract public function render_media( $instance );
  311.  
  312.     /**
  313.      * Outputs the settings update form.
  314.      *
  315.      * Note that the widget UI itself is rendered with JavaScript via `MediaWidgetControl#render()`.
  316.      *
  317.      * @since 4.8.0
  318.      *
  319.      * @see \WP_Widget_Media::render_control_template_scripts() Where the JS template is located.
  320.      * @param array $instance Current settings.
  321.      * @return void
  322.      */
  323.     final public function form( $instance ) {
  324.         $instance_schema = $this->get_instance_schema();
  325.         $instance = wp_array_slice_assoc(
  326.             wp_parse_args( (array) $instance, wp_list_pluck( $instance_schema, 'default' ) ),
  327.             array_keys( $instance_schema )
  328.         );
  329.  
  330.         foreach ( $instance as $name => $value ) : ?>
  331.             <input
  332.                 type="hidden"
  333.                 data-property="<?php echo esc_attr( $name ); ?>"
  334.                 class="media-widget-instance-property"
  335.                 name="<?php echo esc_attr( $this->get_field_name( $name ) ); ?>"
  336.                 id="<?php echo esc_attr( $this->get_field_id( $name ) ); // Needed specifically by wpWidgets.appendTitle(). ?>"
  337.                 value="<?php echo esc_attr( is_array( $value ) ? join( ',', $value ) : strval( $value ) ); ?>"
  338.             />
  339.         <?php
  340.         endforeach;
  341.     }
  342.  
  343.     /**
  344.      * Filters the default media display states for items in the Media list table.
  345.      *
  346.      * @since 4.8.0
  347.      *
  348.      * @param array   $states An array of media states.
  349.      * @param WP_Post $post   The current attachment object.
  350.      * @return array
  351.      */
  352.     public function display_media_state( $states, $post = null ) {
  353.         if ( ! $post ) {
  354.             $post = get_post();
  355.         }
  356.  
  357.         // Count how many times this attachment is used in widgets.
  358.         $use_count = 0;
  359.         foreach ( $this->get_settings() as $instance ) {
  360.             if ( isset( $instance['attachment_id'] ) && $instance['attachment_id'] === $post->ID ) {
  361.                 $use_count++;
  362.             }
  363.         }
  364.  
  365.         if ( 1 === $use_count ) {
  366.             $states[] = $this->l10n['media_library_state_single'];
  367.         } elseif ( $use_count > 0 ) {
  368.             $states[] = sprintf( translate_nooped_plural( $this->l10n['media_library_state_multi'], $use_count ), number_format_i18n( $use_count ) );
  369.         }
  370.  
  371.         return $states;
  372.     }
  373.  
  374.     /**
  375.      * Enqueue preview scripts.
  376.      *
  377.      * These scripts normally are enqueued just-in-time when a widget is rendered.
  378.      * In the customizer, however, widgets can be dynamically added and rendered via
  379.      * selective refresh, and so it is important to unconditionally enqueue them in
  380.      * case a widget does get added.
  381.      *
  382.      * @since 4.8.0
  383.      */
  384.     public function enqueue_preview_scripts() {}
  385.  
  386.     /**
  387.      * Loads the required scripts and styles for the widget control.
  388.      *
  389.      * @since 4.8.0
  390.      */
  391.     public function enqueue_admin_scripts() {
  392.         wp_enqueue_media();
  393.         wp_enqueue_script( 'media-widgets' );
  394.     }
  395.  
  396.     /**
  397.      * Render form template scripts.
  398.      *
  399.      * @since 4.8.0
  400.      */
  401.     public function render_control_template_scripts() {
  402.         ?>
  403.         <script type="text/html" id="tmpl-widget-media-<?php echo esc_attr( $this->id_base ); ?>-control">
  404.             <# var elementIdPrefix = 'el' + String( Math.random() ) + '_' #>
  405.             <p>
  406.                 <label for="{{ elementIdPrefix }}title"><?php esc_html_e( 'Title:' ); ?></label>
  407.                 <input id="{{ elementIdPrefix }}title" type="text" class="widefat title">
  408.             </p>
  409.             <div class="media-widget-preview <?php echo esc_attr( $this->id_base ); ?>">
  410.                 <div class="attachment-media-view">
  411.                     <div class="placeholder"><?php echo esc_html( $this->l10n['no_media_selected'] ); ?></div>
  412.                 </div>
  413.             </div>
  414.             <p class="media-widget-buttons">
  415.                 <button type="button" class="button edit-media selected">
  416.                     <?php echo esc_html( $this->l10n['edit_media'] ); ?>
  417.                 </button>
  418.             <?php if ( ! empty( $this->l10n['replace_media'] ) ) : ?>
  419.                 <button type="button" class="button change-media select-media selected">
  420.                     <?php echo esc_html( $this->l10n['replace_media'] ); ?>
  421.                 </button>
  422.             <?php endif; ?>
  423.                 <button type="button" class="button select-media not-selected">
  424.                     <?php echo esc_html( $this->l10n['add_media'] ); ?>
  425.                 </button>
  426.             </p>
  427.             <div class="media-widget-fields">
  428.             </div>
  429.         </script>
  430.         <?php
  431.     }
  432.  
  433.     /**
  434.      * Whether the widget has content to show.
  435.      *
  436.      * @since 4.8.0
  437.      *
  438.      * @param array $instance Widget instance props.
  439.      * @return bool Whether widget has content.
  440.      */
  441.     protected function has_content( $instance ) {
  442.         return ( $instance['attachment_id'] && 'attachment' === get_post_type( $instance['attachment_id'] ) ) || $instance['url'];
  443.     }
  444. }
  445.