felixarntz/wpdlib

View on GitHub
inc/WPDLib/FieldTypes/Media.php

Summary

Maintainability
D
3 days
Test Coverage
<?php
/**
 * WPDLib\FieldTypes\Media class
 *
 * @package WPDLib
 * @subpackage FieldTypes
 * @author Felix Arntz <felix-arntz@leaves-and-love.net>
 * @since 0.5.0
 */

namespace WPDLib\FieldTypes;

use WPDLib\Components\Manager as ComponentManager;
use WPDLib\FieldTypes\Manager as FieldManager;
use WP_Error as WPError;

if ( ! defined( 'ABSPATH' ) ) {
    die();
}

if ( ! class_exists( 'WPDLib\FieldTypes\Media' ) ) {
    /**
     * Class for a media picker field.
     *
     * This implementation can either store an attachment ID (default) or URL.
     *
     * @since 0.5.0
     */
    class Media extends Base {

        /**
         * @since 0.5.0
         * @var integer|null Temporarily stores an attachment ID.
         */
        protected $temp_val = null;

        /**
         * Class constructor.
         *
         * For an overview of the supported arguments, please read the Field Types Reference.
         *
         * @since 0.5.0
         * @param string $type the field type
         * @param array $args array of field type arguments
         */
        public function __construct( $type, $args ) {
            $args = wp_parse_args( $args, array(
                'store'      => 'id',
                'mime_types' => 'all',
            ) );

            if ( 'url' !== $args['store'] ) {
                $args['store'] = 'id';
            }

            parent::__construct( $type, $args );
        }

        /**
         * Displays the input control for the field.
         *
         * @since 0.5.0
         * @param integer|string $val the current value of the field
         * @param bool $echo whether to echo the output (default is true)
         * @return string the HTML output of the field control
         */
        public function display( $val, $echo = true ) {
            $args = $this->args;
            unset( $args['placeholder'] );
            $args['value'] = $val;

            $args = array_merge( $args, $this->data_atts );

            $args['data-store'] = $args['store'];
            $mime_types = $this->verify_mime_types( $args['mime_types'] );
            if ( $mime_types ) {
                $args['data-query'] = json_encode( array(
                    'post_mime_type' => $mime_types,
                ) );
            }

            unset( $args['store'] );
            unset( $args['mime_types'] );

            $output = '<input type="text"' . FieldManager::make_html_attributes( $args, false, false ) . ' />';

            if ( $echo ) {
                echo $output;
            }

            return $output;
        }

        /**
         * Validates a value for the field.
         *
         * @since 0.5.0
         * @param mixed $val the current value of the field
         * @return integer|string|WP_Error the validated field value or an error object
         */
        public function validate( $val = null ) {
            if ( ! $val ) {
                if ( 'url' === $this->args['store'] ) {
                    return '';
                }
                return 0;
            }

            $orig_val = $val;
            if ( 'url' === $this->args['store'] ) {
                $orig_val = FieldManager::format( $orig_val, 'url', 'input' );
                $val = attachment_url_to_postid( $orig_val );
                if ( ! $val ) {
                    return new WPError( 'invalid_media_url', sprintf( __( 'The URL %s does not point to a WordPress media file.', 'wpdlib' ), $orig_val ) );
                }
            } else {
                $orig_val = absint( $orig_val );
                $val = $orig_val;
            }

            if ( 'attachment' !== get_post_type( $val ) ) {
                return new WPError( 'invalid_media_post_type', sprintf( __( 'The post with ID %s is not a valid WordPress media file.', 'wpdlib' ), $val ) );
            }

            if ( ! $this->check_filetype( $val, $this->args['mime_types'] ) ) {
                $valid_formats = is_array( $this->args['mime_types'] ) ? implode( ', ', $this->args['mime_types'] ) : $this->args['mime_types'];
                return new WPError( 'invalid_media_mime_type', sprintf( __( 'The media item with ID %1$s is neither of the valid formats (%2$s).', 'wpdlib' ), $val, $valid_formats ) );
            }

            return $orig_val;
        }

        /**
         * Checks whether a value for the field is considered empty.
         *
         * This function is needed to check whether a required field has been properly filled.
         *
         * @since 0.5.0
         * @param integer|string $val the current value of the field
         * @return bool whether the value is considered empty
         */
        public function is_empty( $val ) {
            if ( 'url' === $this->args['store'] ) {
                return empty( $val );
            }
            return absint( $val ) < 1;
        }

        /**
         * Parses a value for the field.
         *
         * @since 0.5.0
         * @param mixed $val the current value of the field
         * @param bool|array $formatted whether to also format the value (default is false)
         * @return integer|string the correctly parsed value (string if $formatted is true)
         */
        public function parse( $val, $formatted = false ) {
            if ( 'url' === $this->args['store'] ) {
                $val = FieldManager::format( $val, 'url', 'input' );
            } else {
                $val = absint( $val );
            }
            if ( $formatted ) {
                if ( ! is_array( $formatted ) ) {
                    $formatted = array();
                }
                $formatted = wp_parse_args( $formatted, array(
                    'mode'     => 'field',
                    'field'    => 'url',
                    'template' => '',
                ) );
                $attachment_id = $val;
                if ( 'url' === $this->args['store'] ) {
                    if ( 'field' === $formatted['mode'] && 'url' === $formatted['field'] ) {
                        return $val;
                    }
                    $attachment_id = attachment_url_to_postid( $val );
                }
                return $this->format_attachment( $attachment_id, $formatted );
            }
            return $val;
        }

        /**
         * Enqueues required assets for the field type.
         *
         * The function also generates script vars to be applied in `wp_localize_script()`.
         *
         * @since 0.5.0
         * @return array array which can (possibly) contain a 'dependencies' array and a 'script_vars' array
         */
        public function enqueue_assets() {
            global $post;

            if ( self::is_enqueued( __CLASS__ ) ) {
                return array();
            }

            $assets_url = ComponentManager::get_base_url() . '/assets';
            $version = ComponentManager::get_dependency_info( 'wp-media-picker', 'version' );

            if ( $post ) {
                wp_enqueue_media( array( 'post' => $post->ID ) );
            } else {
                wp_enqueue_media();
            }

            $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';

            wp_enqueue_style( 'wp-media-picker', $assets_url . '/vendor/wp-media-picker/wp-media-picker' . $min . '.css', array(), $version );
            wp_enqueue_script( 'wp-media-picker', $assets_url . '/vendor/wp-media-picker/wp-media-picker' . $min . '.js', array( 'jquery', 'jquery-ui-widget', 'media-editor' ), $version, true );

            return array(
                'dependencies'       => array( 'media-editor', 'wp-media-picker' ),
                'script_vars'        => array(
                    'media_i18n_add'     => __( 'Choose a File', 'wpdlib' ),
                    'media_i18n_replace' => __( 'Choose another File', 'wpdlib' ),
                    'media_i18n_remove'  => __( 'Remove', 'wpdlib' ),
                    'media_i18n_modal'   => __( 'Choose a File', 'wpdlib' ),
                    'media_i18n_button'  => __( 'Insert File', 'wpdlib' ),
                ),
            );
        }

        /**
         * Checks a filetype of an attachment.
         *
         * @since 0.5.0
         * @param integer $val the current field value (attachment ID)
         * @param string|array $accepted_types a string or an array of accepted types (default is 'all' to allow everything)
         * @return bool whether the file type is valid
         */
        protected function check_filetype( $val, $accepted_types = 'all' ) {
            $extension = $this->get_attachment_extension( $val );

            if ( $extension ) {
                return $this->check_extension( $extension, $accepted_types );
            }

            return false;
        }

        /**
         * Returns the file extension of an attachment.
         *
         * @since 0.5.0
         * @param integer $id the current field value (attachment ID)
         * @return string|false the file extension or false if it could not be detected
         */
        protected function get_attachment_extension( $id ) {
            $filename = get_attached_file( $id );

            if ( $filename ) {
                $extension = wp_check_filetype( $filename );
                $extension = $extension['ext'];
                if ( $extension ) {
                    return $extension;
                }
            }

            return false;
        }

        /**
         * Checks whether a file extension is among the accepted file types.
         *
         * @since 0.5.0
         * @param string $extension the file extension to check
         * @param string|array $accepted_types a string or an array of accepted types (default is 'all' to allow everything)
         * @return bool whether the file type is valid
         */
        protected function check_extension( $extension, $accepted_types = 'all' ) {
            if ( 'all' == $accepted_types || ! $accepted_types ) {
                return true;
            }

            if ( ! is_array( $accepted_types ) ) {
                $accepted_types = array( $accepted_types );
            }

            // check the file extension
            if ( in_array( strtolower( $extension ), $accepted_types ) ) {
                return true;
            }

            // check the file type (not MIME type!)
            $type = wp_ext2type( $extension );
            if ( $type !== null && in_array( $type, $accepted_types ) ) {
                return true;
            }

            // check the file MIME type (and first part of MIME type)
            $allowed_mime_types = $this->get_all_mime_types();
            if ( isset( $allowed_mime_types[ $extension ] ) ) {
                if ( in_array( $allowed_mime_types[ $extension ], $accepted_types ) ) {
                    return true;
                }

                $general_type = explode( '/', $allowed_mime_types[ $extension ] )[0];
                if ( in_array( $general_type, $accepted_types ) ) {
                    return true;
                }
            }

            return false;
        }

        /**
         * Verifies the MIME types whitelist.
         *
         * The function ensures that only valid MIME types (full or only general) are provided.
         * File extensions are parsed into their MIME types while invalid MIME types are stripped out.
         *
         * @since 0.5.3
         * @param string|array $accepted_types a string or an array of accepted types (providing 'all' will allow everything, returning an empty array)
         * @return array an array of valid MIME types
         */
        protected function verify_mime_types( $accepted_types ) {
            if ( 'all' === $accepted_types ) {
                return array();
            }

            $validated_mime_types = array();

            if ( ! is_array( $accepted_types ) ) {
                $accepted_types = array( $accepted_types );
            }

            $allowed_mime_types = $this->get_all_mime_types();

            foreach ( $accepted_types as $mime_type ) {
                if ( false === strpos( $mime_type, '/' ) ) {
                    switch ( $mime_type ) {
                        case 'document':
                        case 'spreadsheet':
                        case 'interactive':
                        case 'archive':
                            // documents, spreadsheets, interactive and archive are always MIME type application
                            $validated_mime_types[] = 'application';
                            break;
                        case 'code':
                            // code is always MIME type text
                            $validated_mime_types[] = 'text';
                            break;
                        case 'image':
                        case 'audio':
                        case 'video':
                        case 'text':
                        case 'application':
                            // a valid MIME type
                            $validated_mime_types[] = $mime_type;
                            break;
                        default:
                            if ( isset( $allowed_mime_types[ $mime_type ] ) ) {
                                // a MIME type for a file extension
                                $validated_mime_types[] = $allowed_mime_types[ $mime_type ];
                            }
                    }
                } elseif ( in_array( $mime_type, $allowed_mime_types ) ) {
                    // a fully qualified MIME type (with subtype)
                    $validated_mime_types[] = $mime_type;
                }
            }

            return array_unique( $validated_mime_types );
        }

        /**
         * Returns an array of all MIME types which are allowed by WordPress.
         *
         * The array has a file extension as key and its MIME type as value.
         * Note that therefore it may contain duplicate values.
         *
         * @since 0.5.3
         * @return array an array of generally allowed MIME types
         */
        protected function get_all_mime_types() {
            $allowed_mime_types = array();

            $_allowed_mime_types = get_allowed_mime_types();

            foreach ( $_allowed_mime_types as $_extensions => $_mime_type ) {
                $extensions = explode( '|', $_extensions );
                foreach ( $extensions as $extension ) {
                    $allowed_mime_types[ $extension ] = $_mime_type;
                }
            }

            return $allowed_mime_types;
        }

        /**
         * Formats an attachment for output.
         *
         * The 'mode' key in the $args array specifies in which format it should be returned:
         * - object (returns the attachment's post object)
         * - link (returns a link to the attachment)
         * - image (returns the attachment image, or a mime type icon image if the attachment is not an image)
         * - template (returns an HTML string where it replaces the template tags by actual attachment values)
         * - field (returns the value of a specific attachment field)
         *
         * If using 'template', you also need to specify a 'template' key in the array which holds the template as an HTML string.
         * Use attachment field names wrapped in percent signs as template tags (for example '%medium_url%').
         *
         * If using 'field', you also need to specify a 'field' key in the array.
         * Its value will specify which field to retrieve (for example 'medium_url').
         *
         * @since 0.5.0
         * @see WPDLib\FieldTypes\Media::template_replace()
         * @see WPDLib\FieldTypes\Media::get_attachment_field()
         * @param integer $id the current value of the field (attachment ID)
         * @param array $args arguments on how to format
         * @return integer|string the correctly parsed value (string if $formatted is true)
         */
        protected function format_attachment( $id, $args = array() ) {
            switch ( $args['mode'] ) {
                case 'object':
                    return get_post( $id );
                case 'link':
                    return wp_get_attachment_link( $id, 'thumbnail', false, true );
                case 'image':
                    return wp_get_attachment_image( $id, 'thumbnail', true );
                case 'template':
                    if ( ! empty( $args['template'] ) && $id ) {
                        $this->temp_val = $id;
                        $output = preg_replace_callback( '/%([A-Za-z0-9_\-]+)%/', array( $this, 'template_replace_callback' ), $args['template'] );
                        $this->temp_val = null;
                        return $output;
                    }
                    return '';
                case 'field':
                default:
                    return $this->get_attachment_field( $id, $args['field'] );
            }

            return $id;
        }

        /**
         * Returns the value of a specific attachment field.
         *
         * @param integer $id the current field value (attachment ID)
         * @param string $field the field to get the value for (default is 'url')
         * @return string the attachment field's value
         */
        protected function get_attachment_field( $id, $field = 'url' ) {
            switch ( $field ) {
                case 'id':
                case 'ID':
                    return $id;
                case 'title':
                    return get_the_title( $id );
                case 'alt':
                    return get_post_meta( $id, '_wp_attachment_image_alt', true );
                case 'caption':
                    return $this->get_post_field( $id, 'post_excerpt' );
                case 'description':
                    return $this->get_post_field( $id, 'post_content' );
                case 'mime':
                case 'mime_type':
                case 'type':
                case 'subtype':
                    return $this->get_attachment_mime_type( $id, $field );
                case 'mime_icon':
                case 'mime_type_icon':
                    return wp_mime_type_icon( $id );
                case 'filename':
                    return wp_basename( get_attached_file( $id ) );
                case 'link':
                    return get_attachment_link( $id );
                case 'url':
                case 'path':
                default:
                    if ( '_path' === substr( $field, -5 ) ) {
                        return $this->get_attachment_path( $id, 'path', substr( $field, 0, -5 ) );
                    } elseif ( '_url' === substr( $field, -4 ) ) {
                        return $this->get_attachment_path( $id, 'url', substr( $field, 0, -4 ) );
                    }
                    return $this->get_attachment_path( $id, $field );
            }
        }

        /**
         * Returns the value of a specific post field.
         *
         * @param integer $id the current field value (attachment ID)
         * @param string $field the field to get the value for
         * @return string the post field value (or an empty string if not found)
         */
        protected function get_post_field( $id, $field ) {
            $attachment = get_post( $id );
            if ( ! $attachment ) {
                return '';
            }

            if ( ! isset( $attachment->$field ) ) {
                return '';
            }

            return $attachment->$field;
        }

        /**
         * Returns the mime type of a specific attachment.
         *
         * The second parameter can be set to:
         * - all (returns for example 'image/jpeg')
         * - type (returns for example 'image')
         * - subtype (returns for example 'jpeg')
         *
         * @param integer $id the current field value (attachment ID)
         * @param string $field in which form to return the mime type (default is 'all')
         * @return string the attachment's mime type
         */
        protected function get_attachment_mime_type( $id, $mode = 'all' ) {
            $mime_type = get_post_mime_type( $id );

            if ( in_array( $mode, array( 'type', 'subtype' ) ) ) {
                list( $type, $subtype ) = explode( '/', $mime_type );
                if ( 'type' === $mode ) {
                    return $type;
                }
                return $subtype;
            }

            return $mime_type;
        }

        /**
         * Returns the directory path or the URL to a specific attachment file.
         *
         * @param integer $id the current field value (attachment ID)
         * @param string $field whether to return a 'path' or a 'url' (default is 'url')
         * @param string|false $size the image size to return (or false to simply return the original file path/URL)
         * @return string the path or URL to the desired attachment file
         */
        protected function get_attachment_path( $id, $mode = 'url', $size = false ) {
            $url = wp_get_attachment_url( $id );
            if ( $size ) {
                $src = wp_get_attachment_image_src( $id, $size, false );
                if ( is_array( $src ) ) {
                    $url = $src[0];
                }
            }

            if ( $url && 'path' === $mode ) {
                $upload_dir = wp_upload_dir();
                $path = '';
                if ( strpos( $url, $upload_dir['baseurl'] ) !== false ) {
                    $path = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $url );
                }
                return $path;
            }

            return $url;
        }

        /**
         * Callback for the `preg_replace_callback()` call in the `format_attachment()` method (with mode 'template').
         *
         * @since 0.5.0
         * @param array $matches the matches from the regular expression
         * @return string the replacement
         */
        protected function template_replace_callback( $matches ) {
            if ( null === $this->temp_val ) {
                return '';
            }

            if ( ! isset( $matches[1] ) ) {
                return '';
            }

            return $this->get_attachment_field( $this->temp_val, $matches[1] );
        }
    }

}