attachments ) && !empty( $instance->attachments ) )
{
foreach( $instance->attachments as $attachment )
{
// we need to give our Attachment a uid to carry through to all the fields
$attachment->uid = uniqid();
// we'll create the attachment
$this->create_attachment( $instance->name, $attachment );
}
}
?>
ATTACHMENTS_DIR . 'classes/fields/class.field.text.php',
'textarea' => ATTACHMENTS_DIR . 'classes/fields/class.field.textarea.php',
'select' => ATTACHMENTS_DIR . 'classes/fields/class.field.select.php',
'wysiwyg' => ATTACHMENTS_DIR . 'classes/fields/class.field.wysiwyg.php',
);
// support custom field types
// $field_types = apply_filters( 'attachments_fields', $field_types );
$field_index = 0;
foreach( $field_types as $type => $path )
{
// proceed with inclusion
if( file_exists( $path ) )
{
// include the file
include_once( $path );
// store the registered classes so we can single out what gets added
$existing_classes = get_declared_classes();
// we're going to use our Attachments class as a reference because
// during subsequent instantiations of Attachments (e.g. within template files)
// these field classes WILL NOT be added to the array again because
// we're using include_once() so that strategy is no longer useful
// determine it's class
$flag = array_search( 'Attachments_Field', $existing_classes );
// the field's class is next
$field_class = $existing_classes[$flag + $field_index + 1];
// create our link using our new field class
$field_types[$type] = $field_class;
$field_index++;
}
}
// send it back
return $field_types;
}
/**
* Registers a field type for use within an instance
*
* @since 3.0
*/
function register_field( $params = array() )
{
$defaults = array(
'name' => 'title',
'type' => 'text',
'label' => __( 'Title', 'attachments' ),
'meta' => array(),
);
$params = array_merge( $defaults, $params );
// ensure it's a valid type
if( !isset( $this->fields[$params['type']] ) )
return false;
// sanitize
if( isset( $params['name'] ) )
$params['name'] = str_replace( '-', '_', sanitize_title( $params['name'] ) );
if( isset( $params['label'] ) )
$params['label'] = __( esc_html( $params['label'] ) );
if( !isset( $params['meta'] ) || !is_array( $params['meta'] ) )
$params['meta'] = array();
// instantiate the class for this field and send it back
return new $this->fields[ $params['type'] ]( $params['name'], $params['label'], $params['meta'] );
}
/**
* Registers an Attachments instance
*
* @since 3.0
*/
function register( $name = 'attachments', $params = array() )
{
$defaults = array(
// title of the meta box (string)
'label' => __( 'Attachments', 'attachments' ),
// all post types to utilize (string|array)
'post_type' => array( 'post', 'page' ),
// meta box position (string) (normal, side or advanced)
'position' => 'normal',
// meta box priority (string) (high, default, low, core)
'priority' => 'high',
// maximum number of Attachments (int) (-1 is unlimited)
'limit' => -1,
// allowed file type(s) (array) (image|video|text|audio|application)
'filetype' => null, // no filetype limit
// include a note within the meta box (string)
'note' => null, // no note
// by default new Attachments will be appended to the list
// but you can have then prepend if you set this to false
'append' => true,
// text for 'Attach' button (string)
'button_text' => __( 'Attach', 'attachments' ),
// text for modal 'Attach' button (string)
'modal_text' => __( 'Attach', 'attachments' ),
// which tab should be the default in the modal (string) (browse|upload)
'router' => 'browse',
// fields for this instance (array)
'fields' => array(
array(
'name' => 'title', // unique field name
'type' => 'text', // registered field type
'label' => __( 'Title', 'attachments' ), // label to display
'default' => 'title', // default value upon selection
),
array(
'name' => 'caption', // unique field name
'type' => 'wysiwyg', // registered field type
'label' => __( 'Caption', 'attachments' ), // label to display
'default' => 'caption', // default value upon selection
),
),
);
$params = array_merge( $defaults, $params );
// sanitize
if( !is_array( $params['post_type'] ) )
$params['post_type'] = array( $params['post_type'] ); // we always want an array
if( !is_array( $params['filetype'] ) )
$params['filetype'] = array( $params['filetype'] ); // we always want an array
$params['label'] = esc_html( $params['label'] );
$params['limit'] = intval( $params['limit'] );
$params['note'] = esc_sql( $params['note'] );
$params['button_text'] = esc_attr( $params['button_text'] );
$params['modal_text'] = esc_attr( $params['modal_text'] );
// make sure we've got valid filetypes
if( is_array( $params['filetype'] ) )
{
foreach( $params['filetype'] as $key => $filetype )
{
if( !in_array( $filetype, $this->valid_filetypes ) )
{
unset( $params['filetype'][$key] );
}
}
}
// WordPress sanitizes post type names when registering, so we will too
foreach( $params['post_type'] as $key => $post_type )
$params['post_type'][$key] = sanitize_key( $post_type );
// make sure the instance name is proper
$instance = str_replace( '-', '_', sanitize_title( $name ) );
// register the fields
if( isset( $params['fields'] ) && is_array( $params['fields'] ) && count( $params['fields'] ) )
{
foreach( $params['fields'] as $field )
{
// register the field
$this->register_field( $field );
}
}
// set the instance
$this->instances[$instance] = $params;
// set the Attachments for this instance
$this->instances[$instance]['attachments'] = $this->get_attachments( $instance );
}
/**
* Gets the applicable Attachments instances for the current post type
*
* @since 3.0
*/
function get_instances_for_post_type( $post_type = null )
{
$post_type = ( !is_null( $post_type ) && post_type_exists( $post_type ) ) ? $post_type : $this->get_post_type();
$instances = array();
if( !empty( $this->instances ) )
{
foreach( $this->instances as $name => $params )
{
if( in_array( $post_type, $params['post_type'] ) )
{
$instances[] = $name;
}
}
}
return $instances;
}
/**
* Our own implementation of WordPress' get_post_type() as it's not
* functional when we need it
*
* @since 3.0
*/
function get_post_type()
{
global $post;
// TODO: Retrieving the post_type at this point is ugly to say the least. This needs major cleanup.
if( empty( $post->ID ) && isset( $_GET['post_type'] ) )
{
$post_type = str_replace( '-', '_', sanitize_title( $_GET['post_type'] ) ); // TODO: Better sanitization
}
elseif( !empty( $post->ID ) )
{
$post_type = get_post_type( $post->ID );
}
elseif( isset( $_GET['post'] ) )
{
$post_type = get_post_type( intval( $_GET['post'] ) );
}
else
{
$post_type = 'post';
}
return $post_type;
}
/**
* Sets the applicable Attachments instances for the current post type
*
* @since 3.0
*/
function set_instances_for_current_post_type()
{
// store the applicable instances for this post type
$this->instances_for_post_type = $this->get_instances_for_post_type( $this->get_post_type() );
}
/**
* Outputs HTML for a single Attachment within an instance
*
* @since 3.0
*/
function create_attachment_field( $instance, $field, $attachment = null )
{
// the $field at this point is just the user-declared array
// we need to make it a field object
$type = $field['type'];
if( isset( $this->fields[$type] ) )
{
$name = sanitize_title( $field['name'] );
$label = esc_html( $field['label'] );
$default = isset( $field['default'] ) ? $field['default'] : false; // validated in the class
$meta = isset( $field['meta'] ) ? $field['meta'] : array();
$value = isset( $attachment->fields->$name ) ? $attachment->fields->$name : null;
$field = new $this->fields[$type]( $name, $label, $value, $meta );
$field->value = $field->format_value_for_input( $field->value );
// does this field already have a unique ID?
$uid = ( isset( $attachment->uid ) ) ? $attachment->uid : null;
// TODO: make sure we've got a registered instance
$field->set_field_instance( $instance, $field );
$field->set_field_identifiers( $field, $uid );
$field->set_field_type( $type );
$field->set_field_default( $default );
?>
create_field( $instance, $field ); ?>
html( $field );
}
/**
* Outputs all the necessary markup for an Attachment
*
* @since 3.0
*/
function create_attachment( $instance, $attachment = null )
{
?>
uid ) ) ? $attachment->uid : '{{ attachments.attachment_uid }}'; ?>
id ) )
{
// we'll just use the full size since that's what Media in 3.5 uses
$attachment_meta = wp_get_attachment_metadata( $attachment->id );
// only images return the 'file' key
if( !isset( $attachment_meta['file'] ))
$attachment_meta['file'] = get_attached_file( $attachment->id );
$attachment->width = isset( $attachment_meta['width'] ) ? $attachment_meta['width'] : null;
$attachment->height = isset( $attachment_meta['height'] ) ? $attachment_meta['height'] : null;
$attachment->filename = end( explode( "/", $attachment_meta['file'] ) );
$attachment_mime = explode( '/', get_post_mime_type( $attachment->id ) );
$attachment->type = isset( $attachment_mime[0] ) ? $attachment_mime[0] : null;
$attachment->subtype = isset( $attachment_mime[1] ) ? $attachment_mime[1] : null;
}
?>
instances_for_post_type ) || empty( $post ) )
return;
// all metaboxes have been put in place, we can now determine which field assets need to be included
// first we'll get a list of the field types on screen
$fieldtypes = array();
foreach( $this->instances_for_post_type as $instance )
foreach( $this->instances[$instance]['fields'] as $field )
$fieldtypes[] = $field['type'];
// we only want to dump out assets once for each field type
$fieldtypes = array_unique( $fieldtypes );
// loop through and dump out all the assets
foreach( $fieldtypes as $fieldtype )
{
$field = new $this->fields[$fieldtype];
$field->assets();
}
}
/**
* Callback to fire the init() function for reach registered field
*
* @since 3.1
*/
function field_inits()
{
global $post;
// we only want to enqueue if we're on an edit screen and it's applicable
if( empty( $this->instances_for_post_type ) || empty( $post ) )
return;
// all metaboxes have been put in place, we can now determine which field assets need to be included
// first we'll get a list of the field types on screen
$fieldtypes = array();
foreach( $this->instances_for_post_type as $instance )
foreach( $this->instances[$instance]['fields'] as $field )
$fieldtypes[] = $field['type'];
// we only want to dump out assets once for each field type
$fieldtypes = array_unique( $fieldtypes );
// loop through and dump out all the assets
foreach( $fieldtypes as $fieldtype )
{
$field = new $this->fields[$fieldtype];
$field->init();
}
}
/**
* Outputs all necessary Backbone templates
* Each Backbone template includes each field present in an instance
*
* @since 3.0
*/
function admin_footer()
{
if( !empty( $this->instances_for_post_type ) )
{ ?>
instances_for_post_type as $instance ) : ?>
$instance_attachments )
{
// loop through each Attachment of this instance
foreach( $instance_attachments as $key => $attachment )
{
// since we're using JSON for storage in the database, we need
// to make sure that characters are encoded that would otherwise
// break the JSON
if( isset( $attachment['fields'] ) && is_array( $attachment['fields'] ) )
{
foreach( $attachment['fields'] as $key => $field_value )
{
// take care of our returns
$field_value = str_replace( "\r\n", "\n", $field_value );
$field_value = str_replace( "\r", "\n", $field_value );
// we dont want to strip out our newlines so we're going to flag them
$field_value = str_replace("\n", "%%ATTACHMENTS_NEWLINE%%", $field_value );
// slashes were already added so we're going to strip them
$field_value = stripslashes_deep( $field_value );
// put back our newlines
$field_value = str_replace("%%ATTACHMENTS_NEWLINE%%", "\\n", $field_value );
// encode the whole thing
$field_value = $this->encode_field_value( $field_value );
// encode things properly
$attachment['fields'][$key] = $field_value;
}
}
$attachments[$instance][] = $attachment;
}
}
if( !empty( $attachments ) )
{
// we're going to store JSON (JSON_UNESCAPED_UNICODE is PHP 5.4+)
$attachments = version_compare( PHP_VERSION, '5.4.0', '>=' ) ? json_encode( $attachments, JSON_UNESCAPED_UNICODE ) : json_encode( $attachments );
// we're going to wipe out any existing Attachments meta (because we'll put it back)
update_post_meta( $post_id, $this->meta_key, $attachments );
}
else
{
// there are no attachments so we'll clean up the record
delete_post_meta( $post_id, $this->meta_key );
}
return $post_id;
}
/**
* Recursive function to encode a field type before saving
* @param mixed $field_value The field value
* @return mixed The encoded field value
*
* @since 3.3
*/
function encode_field_value( $field_value = null )
{
if( is_null( $field_value ) )
return;
if( is_object( $field_value ) )
{
$input = get_object_vars( $field_value );
foreach( $input as $key => $val )
$field_value[$key] = $this->encode_field_value( $val );
$field_value = (object) $field_value;
}
elseif( is_array( $field_value ) )
{
foreach( $field_value as $key => $val )
$field_value[$key] = $this->encode_field_value( $val );
}
else
$field_value = htmlentities( $field_value, ENT_QUOTES, 'UTF-8' );
return $field_value;
}
/**
* Retrieves all Attachments for the submitted instance and post ID
*
* @since 3.0
*/
function get_attachments( $instance = null, $post_id = null )
{
$post_id = $this->determine_post_id( $post_id );
if( !$post_id )
return false;
$attachments = array();
$attachments_raw = $this->get_attachments_metadata( $post_id );
// we need to decode the fields (that were encoded during save) and run them through
// their format_value_for_input as defined in it's class
if( !is_null( $instance ) && is_string( $instance ) && isset( $attachments_raw->$instance ) )
{
foreach( $attachments_raw->$instance as $attachment )
$attachments[] = $this->process_attachment( $attachment, $instance );
}
elseif( is_null( $instance ) )
{
// return them all, regardless of instance
if( is_array( $attachments_raw ) && count( $attachments_raw ) )
foreach( $attachments_raw as $instance => $attachments_unprocessed )
foreach( $attachments_unprocessed as $unprocessed_attachment )
$attachments[] = $this->process_attachment( $unprocessed_attachment, $instance );
}
// tack on the post ID for each attachment
for( $i = 0; $i < count( $attachments ); $i++ )
$attachments[$i]->post_id = $post_id;
// we don't want the filter to run on the admin side of things
if( !is_admin() )
$attachments = apply_filters( "attachments_get_{$instance}", $attachments );
return $attachments;
}
/**
* Retrieves the post_meta record (saved as JSON) and processes it
* @param int $post_id The post ID
* @return object Attachments for the post
*
* @since 3.3
*/
function get_attachments_metadata( $post_id )
{
$post_id = intval( $post_id );
// grab our JSON and decode it
$attachments_json = get_post_meta( $post_id, $this->meta_key, true );
$attachments_raw = is_string( $attachments_json ) ? json_decode( $attachments_json ) : false;
return $attachments_raw;
}
/**
* Determines a proper post ID based on whether one was passed, the global $post object, or a GET variable
* @param int $post_id Desired post ID
* @return mixed The understood post ID
*/
function determine_post_id( $post_id = null )
{
global $post;
// if a post id was passed, we'll use it
if( !is_null( $post_id ) )
{
$post_id = intval( $post_id );
}
elseif( is_null( $post_id ) && is_object( $post ) && isset( $post->ID ) )
{
$post_id = $post->ID;
}
elseif( isset( $_GET['post'] ) )
{
$post_id = intval( $_GET['post'] );
}
else
{
// no post ID, nothing to do...
$post_id = false;
}
return $post_id;
}
/**
* Processes Attachment (including fields) in preparation for return
* @param object $attachment An Attachment object as it was stored as post metadata
* @return object Post-processed Attachment data
*
* @since 3.3
*/
function process_attachment( $attachment, $instance )
{
if( !is_object( $attachment ) || !is_string( $instance ) )
return $attachment;
if( is_object( $attachment->fields ) )
{
foreach( $attachment->fields as $key => $value )
{
// loop through the instance fields to get the type
if( isset( $this->instances[$instance]['fields'] ) )
{
$type = '';
foreach( $this->instances[$instance]['fields'] as $field )
{
if( isset( $field['name'] ) && $field['name'] == $key )
{
$type = isset( $field['type'] ) ? $field['type'] : false;
break;
}
}
if( isset( $this->fields[$type] ) )
{
// we need to decode the html entities that were encoded for the save
$attachment->fields->$key = $this->decode_field_value( $attachment->fields->$key );
}
else
{
// the type doesn't exist
$attachment->fields->$key = false;
}
}
else
{
// this was a theme file request, just grab it
$attachment->fields->$key = $this->decode_field_value( $attachment->fields->$key );
}
}
}
return $attachment;
}
/**
* Recursive function to decode a field value when retrieving it
* @param mixed $field_value The field value
* @return mixed The encoded field value
*
* @since 3.3
*/
function decode_field_value( $field_value = null )
{
if( is_null( $field_value ) )
return;
if( is_object( $field_value ) )
{
$input = get_object_vars( $field_value );
foreach( $input as $key => $val )
$field_value[$key] = $this->decode_field_value( $val );
$field_value = (object) $field_value;
}
elseif( is_array( $field_value ) )
{
foreach( $field_value as $key => $val )
$field_value[$key] = $this->decode_field_value( $val );
}
else
$field_value = html_entity_decode( $field_value, ENT_QUOTES, 'UTF-8' );
return $field_value;
}
/**
* Callback to implement our Settings page
*
* @since 3.0
*/
function admin_page()
{
add_options_page( 'Settings', __( 'Attachments', 'attachments' ), 'manage_options', 'attachments', array( $this, 'options_page' ) );
}
/**
* Callback to output our Settings page markup
*
* @since 3.0
*/
function options_page()
{
include_once( ATTACHMENTS_DIR . '/views/options.php' );
}
}
endif; // class_exists check
new Attachments();