File: /home/slyfwmm/foar/wp-content/plugins/attachments/classes/class.attachments.php
<?php
/**
* Attachments
*
* Attachments allows you to simply append any number of items from your WordPress
* Media Library to Posts, Pages, and Custom Post Types
*
* @package Attachments
* @subpackage Main
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Declare our class
if ( ! class_exists( 'Attachments' ) ) :
/**
* Main Attachments Class
*
* @since 3.0
*/
class Attachments {
private $version; // stores Attachments' version number
private $url; // stores Attachments' URL
private $dir; // stores Attachments' directory
private $instances; // all registered Attachments instances
private $instances_for_post_type; // instance names that apply to the current post type
private $fields; // stores all registered field types
private $attachments; // stores all of the Attachments for the given instance
private $image_sizes = array( 'full' ); // store all registered image sizes
private $attachments_ref = -1; // flags where a get() loop last did it's thing
private $meta_key = 'attachments'; // our meta key
private $valid_filetypes = array( // what WordPress considers to be valid file types
'image',
'video',
'text',
'audio',
'application'
);
/**
* Constructor
*
* @since 3.0
*/
function __construct( $instance = null, $post_id = null ) {
global $_wp_additional_image_sizes;
// establish our environment variables
$this->version = '3.5.5';
$this->url = ATTACHMENTS_URL;
$this->dir = ATTACHMENTS_DIR;
$plugin = 'attachments/index.php';
// includes
include_once( ATTACHMENTS_DIR . 'upgrade.php' );
include_once( ATTACHMENTS_DIR . '/classes/class.attachments.legacy.php' );
include_once( ATTACHMENTS_DIR . '/classes/class.attachments.search.php' );
include_once( ATTACHMENTS_DIR . '/classes/class.field.php' );
// include our fields
$this->fields = $this->get_field_types();
// set our image sizes
$this->image_sizes = array_merge( $this->image_sizes, get_intermediate_image_sizes() );
// add 'Extend' link
add_filter( "plugin_action_links_$plugin", array( $this, 'plugin_settings_link' ) );
// add update message(s)
// add_action( 'in_plugin_update_message-attachments/index.php', array( $this, 'update_message' ) );
// set up l10n
add_action( 'plugins_loaded', array( $this, 'l10n' ) );
// load extensions
add_action( 'plugins_loaded', array( $this, 'load_extensions' ) );
// hook into WP
add_action( 'admin_enqueue_scripts', array( $this, 'assets' ), 999, 1 );
// register our user-defined instances
add_action( 'init', array( $this, 'setup_instances' ) );
// determine which instances apply to the current post type
add_action( 'init', array( $this, 'set_instances_for_current_post_type' ) );
add_action( 'add_meta_boxes', array( $this, 'meta_box_init' ) );
add_action( 'admin_footer', array( $this, 'admin_footer' ) );
add_action( 'save_post', array( $this, 'save' ) );
// only show the Settings screen if it hasn't been explicitly disabled
add_action( 'admin_menu', array( $this, 'admin_page' ) );
add_action( 'admin_head', array( $this, 'field_inits' ) );
add_action( 'admin_print_footer_scripts', array( $this, 'field_assets' ) );
add_action( 'admin_init', array( $this, 'admin_init' ) );
// execution of actions varies depending on whether we're in the admin or not and an instance was passed
if( is_admin() ) {
add_action( 'after_setup_theme', array( $this, 'apply_init_filters' ), 999 );
$this->attachments = $this->get_attachments( $instance, $post_id );
} elseif ( ! is_null( $instance ) ) {
$this->apply_init_filters();
$this->attachments = $this->get_attachments( $instance, $post_id );
}
}
/**
* Add notification about available Attachments extensions
*/
function update_message() { ?>
<div style="margin-top:10px;padding-top:8px;border-top:1px solid #eaeaea;"><span style="color:#f00;"><?php _e( 'Attachments Extensions Available!', 'attachments' ); ?></span> <span style="font-weight:normal;"><?php _e( 'These utilities make working with Attachments even easier!', 'attachments' ); ?></span></div>
<div style="font-weight:normal;padding-top:8px;">
<p><strong><a href="https://mondaybynoon.com/members/plugins/attachments-ui/?utm_campaign=Attachments&utm_term=Upgrade%2bNotice">Attachments UI</a></strong> - <?php _e( 'Create Attachments Instances with an easy-to-use UI, easily limit meta box locations with fine grained control (e.g. Home page only), and more.', 'attachments' ); ?></p>
</div>
<?php }
/**
* Customize the links on the Plugins list page
*
* @param $links
* @return mixed
*/
function plugin_settings_link( $links ) {
$settings_link = '<a href="options-general.php?page=attachments">'. __( 'Settings', 'attachments' ) . '</a>';
array_unshift( $links, $settings_link );
$extend_link = '<a href="https://mondaybynoon.com/members/plugins/?utm_campaign=Attachments&utm_term=Plugins%2bExtend#attachments">'. __( 'Extend', 'attachments' ) . '</a>';
array_unshift( $links, $extend_link );
return $links;
}
/**
* Callback for WordPress' admin_init action
*
* @since 3.4.3
*/
function admin_init() {
if ( current_user_can( 'delete_posts' ) ) {
add_action( 'delete_post', array( $this, 'handle_wp_post_delete' ), 10 );
}
}
/**
* Getter for the existing fields
*
* @return array
* @since 3.5
*/
function get_fields() {
return $this->fields;
}
/**
* Callback for WordPress' delete_post action. Searches all saved Attachments
* data for any records using a deleted attachment. If found, the record is removed.
*
* @param int $pid Post ID
* @since 3.4.3
*/
function handle_wp_post_delete( $pid ) {
if ( ! current_user_can( 'delete_posts' ) ) {
return;
}
// check to make sure it was an attachment
if( 'attachment' != get_post_type( $pid ) ) {
return;
}
// if a user deletes an attachment from the Media library (but it's been used
// in Attachments somewhere else) we need to clean that up...
// we hook into delete_post because the only other option is to filter
// each Attachment when retrieving Attachments via get_attachments()
// which could potentially be a ton of database calls
// so we're going to use the search class to find all instances
// for any occurrence of the deleted attachment (which has an ID of $pid)
if ( is_array( $this->instances ) ) {
foreach( $this->instances as $instance => $details ) {
$search_args = array(
'instance' => $instance,
'attachment_id' => intval( $pid ),
);
$this->search( null, $search_args );
if( $this->exist() ) {
// we've got a hit (e.g. an existing post uses the deleted attachment)
while( $attachment = $this->get() ) {
$post_id = $attachment->post_id;
// we'll use the post ID to snag the details
$post_attachments = $this->get_attachments_metadata( $post_id );
if( is_object( $post_attachments ) ) {
foreach( $post_attachments as $existing_instance => $existing_instance_attachments ) {
foreach( $existing_instance_attachments as $existing_instance_attachment_key => $existing_instance_attachment ) {
if( $pid == intval( $existing_instance_attachment->id ) ) {
unset( $post_attachments->{$existing_instance}[$existing_instance_attachment_key] );
}
}
}
// saving routine assumes array from POST so we'll typecast it
$post_attachments = (array) $post_attachments;
// save the revised Attachments metadata
$this->save_metadata( $post_id, $post_attachments );
}
}
}
}
}
}
/**
* Getter for the current meta_key
*
* @since 3.4.2
*/
function get_meta_key() {
return $this->meta_key;
}
/**
* Getter for our URL
*
* @since 3.5
*/
function get_url() {
return $this->url;
}
/**
* Register our textdomain for l10n
*
* @since 3.4.2
*/
function l10n() {
load_plugin_textdomain( 'attachments', false, 'attachments/languages/' );
}
function load_extensions() {
do_action( 'attachments_extension', $this );
}
/**
* Various initialization filter triggers
*
* @since 3.4
*/
function apply_init_filters() {
// allows a different meta_key to be used
$this->meta_key = apply_filters( 'attachments_meta_key', $this->meta_key );
}
/**
* Facilitates searching for Attachments
*
* @since 3.3
*/
function search( $query = null, $params = array() ) {
$results = new AttachmentsSearch( $query, $params );
$this->attachments = $results->results;
}
/**
* Returns whether or not the current object has any Attachments
*
* @since 3.0
*/
function exist() {
return ! empty( $this->attachments );
}
/**
* Returns the number of Attachments
*
* @since 3.0.6
*/
function total() {
return count( $this->attachments );
}
/**
* Resets the attachment index
*
* @since 3.6
*/
function rewind() {
$this->attachments_ref = -1;
}
/**
* Returns the next Attachment for the current object and increments the index
*
* @since 3.0
*/
function get() {
$this->attachments_ref++;
if ( ! count( $this->attachments ) || $this->attachments_ref >= count( $this->attachments ) ) {
return false;
}
return $this->attachments[ $this->attachments_ref ];
}
/**
* Returns a specific Attachment
*
* @since 3.2
*/
function get_single( $index ) {
return isset( $this->attachments[ $index ] ) ? $this->attachments[ $index ] : false;
}
/**
* Returns the asset (array) for the current Attachment
*
* @since 3.0.6
*/
function asset( $size = 'thumbnail', $index = null ) {
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
// do we have our meta yet?
if ( ! isset( $this->attachments[ $index ]->meta ) ) {
$this->attachments[ $index ]->meta = wp_get_attachment_metadata( $this->attachments[ $index ]->id );
}
// is it an image?
if ( isset( $this->attachments[ $index ]->meta['sizes'] ) ) {
$asset = wp_get_attachment_image_src( $this->attachments[ $index ]->id, $size );
} else {
// either it's not an image or we don't have the proper size, so we'll use the icon
$asset = $this->icon( $index );
}
return $asset;
}
/**
* Returns the icon (array) for the current Attachment
*
* @since 3.0.6
*/
function icon( $index = null ) {
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
$asset = wp_get_attachment_image_src( $this->attachments[ $index ]->id, null, true );
return $asset;
}
/**
* Returns the date for the current Attachment
* @author Hasin Hayder
*
* @since 3.4.1
*/
function date( $d = "d/m/Y", $index = null ) {
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
$date = get_the_time( $d, $this->attachments[ $index ]->id );
return $date;
}
/**
* Returns an appropriate <img /> for the current Attachment if it's an image
*
* @since 3.0
*/
function image( $size = 'thumbnail', $index = null ) {
$asset = $this->asset( $size, $index );
$image_src = $asset[0];
$image_width = $asset[1];
$image_height = $asset[2];
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
$image_alt = get_post_meta( $this->attachments[ $index ]->id, '_wp_attachment_image_alt', true );
$image = '<img src="' . esc_url( $image_src ) . '" width="' . absint( $image_width ) . '" height="' . absint( $image_height ) . '" alt="' . esc_attr( $image_alt ) . '" />';
return $image;
}
/**
* Returns the URL for the current Attachment if it's an image
*
* @since 3.0
*/
function src( $size = 'thumbnail', $index = null ) {
$asset = $this->asset( $size, $index );
return esc_url( $asset[0] );
}
/**
* Returns the width of the current Attachment if it's an image
*
* @since 3.5
*/
function width( $size = 'thumbnail', $index = null ) {
$asset = $this->asset( $size, $index );
return absint( $asset[1] );
}
/**
* Returns the height of the current Attachment if it's an image
*
* @since 3.5
*/
function height( $size = 'thumbnail', $index = null ) {
$asset = $this->asset( $size, $index );
return absint( $asset[2] );
}
/**
* Returns the formatted filesize of the current Attachment
*
* @since 3.0
*/
function filesize( $index = null, $size = null ) {
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
if ( ! isset( $this->attachments[ $index ]->id ) ) {
return false;
}
// if an image size is passed along, use that
$url = is_string( $size ) ? $this->src( $size, $index ) : wp_get_attachment_url( $this->attachments[ $index ]->id );
$uploads = wp_upload_dir();
$file_path = str_replace( $uploads['baseurl'], $uploads['basedir'], $url );
$formatted = '0 bytes';
if ( file_exists( $file_path ) ) {
$formatted = size_format( @filesize( $file_path ) );
}
return $formatted;
}
/**
* Returns the type of the current Attachment
*
* @since 3.0
*/
function type( $index = null ) {
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
if ( ! isset( $this->attachments[$index]->id ) ) {
return false;
}
$attachment_mime = $this->get_mime_type( $this->attachments[$index]->id );
return $attachment_mime;
}
/**
* Retrieves the mime type for a WordPress $post ID
*
* @since 3.4.2
*/
function get_mime_type( $id = null ) {
$attachment_mime = explode( '/', get_post_mime_type( intval( $id ) ) );
return isset( $attachment_mime[0] ) ? $attachment_mime[0] : false;
}
/**
* Returns the subtype of the current Attachment
*
* @since 3.0
*/
function subtype( $index = null ) {
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
if ( ! isset( $this->attachments[$index]->id ) ) {
return false;
}
$attachment_mime = explode( '/', get_post_mime_type( $this->attachments[ $index ]->id ) );
return isset( $attachment_mime[1] ) ? $attachment_mime[1] : false;
}
/**
* Returns the id of the current Attachment
*
* @since 3.0
*/
function id( $index = null ) {
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
return isset( $this->attachments[ $index ]->id ) ? $this->attachments[ $index ]->id : false;
}
/**
* Returns the $post->ID of the current Attachment
*
* @since 3.3
*/
function post_id( $index = null ) {
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
return isset( $this->attachments[ $index ]->post_id ) ? $this->attachments[ $index ]->post_id : false;
}
/**
* Returns the URL for the current Attachment
*
* @since 3.0
*/
function url( $index = null ) {
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
return isset( $this->attachments[ $index ]->id ) ? wp_get_attachment_url( $this->attachments[ $index ]->id ) : false;
}
/**
* Returns the field value for the submitted field name
*
* @since 3.0
*/
function field( $name = 'title', $index = null ) {
$index = is_null( $index ) ? $this->attachments_ref : intval( $index );
return isset( $this->attachments[ $index ]->fields->$name ) ? $this->attachments[ $index ]->fields->$name : false;
}
/**
* Fires all of our actions
*
* @since 3.0
*/
function setup_instances() {
// implement our default instance if appropriate
$filtered = apply_filters( 'attachments_default_instance', true );
if ( $filtered && ( ! defined( 'ATTACHMENTS_DEFAULT_INSTANCE' ) || true == ATTACHMENTS_DEFAULT_INSTANCE ) ) {
$this->register();
}
// facilitate user-defined instance registration
do_action( 'attachments_register', $this );
}
/**
* Enqueues our necessary assets
*
* @since 3.0
*/
function assets( $hook ) {
global $post;
// we only want our assets on edit screens
if ( ! empty( $this->instances_for_post_type ) && 'edit.php' != $hook && 'post.php' != $hook && 'post-new.php' != $hook ) {
return;
}
// we only want to enqueue if appropriate
if ( empty( $this->instances_for_post_type ) ) {
return;
}
$post_id = isset( $post->ID ) ? $post->ID : null;
wp_enqueue_media( array( 'post' => $post_id ) );
wp_enqueue_style( 'attachments', trailingslashit( $this->url ) . 'css/attachments.css', null, $this->version, 'screen' );
wp_enqueue_script( 'attachments', trailingslashit( $this->url ) . 'js/attachments.js', array( 'jquery', 'jquery-ui-sortable' ), $this->version, true );
}
/**
* Registers meta box(es) for the current edit screen
*
* @since 3.0
*/
function meta_box_init() {
$nonce_sent = false;
if ( !empty ( $this->instances_for_post_type ) ) {
foreach( $this->instances_for_post_type as $instance ) {
// facilitate more fine-grained meta box positioning than post type
$applicable = apply_filters( "attachments_location_{$instance}", true, $instance );
// obtain other details about instance
$instance_name = $instance;
$instance = (object) $this->instances[$instance];
$instance->name = $instance_name;
$position = isset($instance->position) ? $instance->position : 'normal';
$priority = isset($instance->priority) ? $instance->priority : 'high';
if( $applicable ) {
add_meta_box(
'attachments-' . $instance_name,
__( esc_attr( $instance->label ) ),
array( $this, 'meta_box_markup' ),
$this->get_post_type(),
$position,
$priority,
array(
'instance' => $instance,
'setup_nonce' => !$nonce_sent
)
);
$nonce_sent = true;
}
}
}
}
/**
* Callback that outputs the meta box markup
*
* @since 3.0
*/
function meta_box_markup( $post, $metabox ) {
// single out our $instance
$instance = (object) $metabox['args']['instance'];
if( $metabox['args']['setup_nonce'] ) {
wp_nonce_field( 'attachments_save', 'attachments_nonce' );
}
?>
<div id="attachments-<?php echo esc_attr( $instance->name ); ?>" class="attachments-parent-container<?php if( $instance->append == false ) : ?> attachments-prepend<?php endif; ?>">
<?php if( ! empty( $instance->note ) ) : ?>
<div class="attachments-note"><?php echo wpautop( $instance->note ); ?></div>
<?php endif; ?>
<?php if( $instance->append == false ) : ?>
<div class="attachments-invoke-wrapper">
<a class="button attachments-invoke"><?php _e( esc_attr( $instance->button_text ), 'attachments' ); ?></a>
</div>
<?php endif; ?>
<div class="attachments-container attachments-<?php echo esc_attr( $instance->name ); ?>"><?php
if ( isset( $instance->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 = esc_attr( str_replace( '.', '', uniqid( '', true ) ) );
// we'll create the attachment
$this->create_attachment( $instance->name, $attachment );
}
}
?></div>
<?php if( $instance->append == true ) : ?>
<div class="attachments-invoke-wrapper">
<a class="button attachments-invoke"><?php _e( esc_attr( $instance->button_text ), 'attachments' ); ?></a>
</div>
<?php endif; ?>
</div>
<script type="text/javascript">
jQuery(document).ready(function($){
var $element = $('#attachments-<?php echo esc_attr( $instance->name ); ?>'),
title = '<?php echo __( esc_attr( $instance->label ) ); ?>',
button = '<?php echo __( esc_attr( $instance->modal_text ) ); ?>',
router = '<?php echo __( esc_attr( $instance->router ) ); ?>',
limit = <?php echo intval( $instance->limit ); ?>,
existing = <?php echo ( isset( $instance->attachments ) && ! empty( $instance->attachments ) ) ? count( $instance->attachments ): 0; ?>,
attachmentsframe,
editframe;
$element.on( 'click', '.attachments-invoke', function( event ) {
var attachment;
event.preventDefault();
existing = $element.find('.attachments-container > .attachments-attachment').length;
if( limit > -1 && existing >= limit ){
alert('<?php _e( "Currently limited to", "attachments" ); ?> ' + limit);
return false;
}
// if the frame already exists, open it
if ( attachmentsframe ) {
attachmentsframe.open();
attachmentsframe.content.mode(router);
return;
}
// set our settings
attachmentsframe = wp.media({
title: title,
<?php if( $instance->limit < 0 || $instance->limit > 1 ) : ?>
multiple: true,
<?php endif; ?>
library: {
type: '<?php echo esc_attr( implode( ",", $instance->filetype ) ); ?>'
},
// Customize the submit button.
button: {
// Set the text of the button.
text: button
},
// Enable filters
states: [
new wp.media.controller.Library({
<?php if( $instance->limit < 0 || $instance->limit > 1 ) : ?>
multiple: true,
<?php endif; ?>
filterable: 'all'
})
]
});
// set up our select handler
attachmentsframe.on( 'select', function(){
var selection = attachmentsframe.state().get('selection');
if ( ! selection )
return;
// compile our Underscore template using Mustache syntax
_.templateSettings = {
variable : 'attachments',
interpolate : /\{\{(.+?)\}\}/g
};
var template = _.template($('script#tmpl-attachments-<?php echo $instance->name; ?>').html());
// loop through the selected files
selection.each( function( attachment ) {
// make sure we respect the limit
if( limit > -1 && existing >= limit ){
return;
}
// set our attributes to the template
attachment.attributes.attachment_uid = attachments_uniqid( 'attachmentsjs' );
// by default use the generic icon
attachment.attributes.attachment_thumb = attachment.attributes.icon;
// only thumbnails have sizes which is what we're on the hunt for
// TODO: this is a mess
if(attachments_isset(attachment.attributes)){
if(attachments_isset(attachment.attributes.sizes)){
if(attachments_isset(attachment.attributes.sizes.thumbnail)){
if(attachments_isset(attachment.attributes.sizes.thumbnail.url)){
// use the thumbnail
attachment.attributes.attachment_thumb = attachment.attributes.sizes.thumbnail.url;
}
}
}
}
var templateData = attachment.attributes;
// append the template
$element.find('.attachments-container').<?php if( $instance->append ) : ?>append<?php else : ?>prepend<?php endif; ?>(template(templateData));
// update the number of existing Attachments
existing = $element.find('.attachments-container > .attachments-attachment').length;
// if we're in a sidebar we DO want to show the fields which are normally hidden on load via CSS
if($element.parents('#side-sortables')){
$element.find('.attachments-attachment:<?php if( $instance->append ) : ?>last<?php else : ?>first<?php endif; ?> .attachments-fields').show();
}
// see if we need to set a default
// TODO: can we tie this into other field types (select, radio, checkbox)?
$element.find('.attachments-attachment:<?php if( $instance->append ) : ?>last<?php else : ?>first<?php endif; ?> .attachments-fields input, .attachments-attachment:<?php if( $instance->append ) : ?>last<?php else : ?>first<?php endif; ?> .attachments-fields textarea').each(function(){
if($(this).data('default')){
var meta_for_default = $(this).data('default');
if(attachments_isset(attachment.attributes)){
if(attachments_isset(attachment.attributes[meta_for_default])){
$(this).val(attachment.attributes[meta_for_default]);
}
}
}
});
$('body').trigger('attachments/new');
// if it wasn't an image we need to ditch the dimensions
if(!attachments_isset(attachment.attributes.width)||!attachments_isset(attachment.attributes.height)){
$element.find('.attachments-attachment:<?php if( $instance->append ) : ?>last<?php else : ?>first<?php endif; ?> .dimensions').hide();
}
});
});
// open the frame
attachmentsframe.open();
attachmentsframe.content.mode(router);
});
var targetAttachment;
$element.on( 'click', '.edit-attachment-asset', function( event ) {
event.preventDefault();
targetAttachment = $(event.target).parents(".attachments-attachment");
if ( editframe ) {
editframe.open();
editframe.content.mode(router);
return;
}
editframe = wp.media({
title: title,
multiple: false,
library: {
type: '<?php echo esc_attr( implode( ",", $instance->filetype ) ); ?>'
},
button: {
text: '<?php _e( "Change", 'attachments' ); ?>'
},
states: [
new wp.media.controller.Library({
filterable: 'all'
})
]
});
editframe.on( 'select', function(){
var selection = editframe.state().get('selection');
if ( ! selection )
return;
selection.each( function( attachment ) {
// update the ID
targetAttachment.find('input.attachments-track-id').val(attachment.id);
// update the thumbnail
var updatedThumb = false;
if(attachments_isset(attachment.attributes)){
if(attachments_isset(attachment.attributes.sizes)){
if(attachments_isset(attachment.attributes.sizes.thumbnail)){
if(attachments_isset(attachment.attributes.sizes.thumbnail.url)){
updatedThumb = true;
targetAttachment.find('.attachment-thumbnail img').attr('src',attachment.attributes.sizes.thumbnail.url);
}
}
}
}
if( !updatedThumb ){
targetAttachment.find('.attachment-thumbnail img').attr('src','');
}
// update the name
targetAttachment.find('.attachment-details .filename').text(attachment.attributes.filename);
// update the dimensions
if(attachments_isset(attachment.attributes.width)&&attachments_isset(attachment.attributes.height)){
targetAttachment.find('.attachment-details .dimensions').html(attachment.attributes.width + ' × ' + attachment.attributes.height).show();
}
} );
});
editframe.open();
editframe.content.mode(router);
} );
$element.on( 'click', '.delete-attachment a', function( event ) {
event.preventDefault();
targetAttachment = $(this).parents('.attachments-attachment');
targetAttachment.find('.attachment-meta').fadeOut(125);
targetAttachment.css('min-height',0).animate(
{
padding: 0,
margin: 0,
height: 0
},
600,
function(){
targetAttachment.remove();
}
);
} );
});
</script>
<?php }
/**
* Support the inclusion of custom, user-defined field types
* Borrowed implementation from Custom Field Suite by Matt Gibbs
* https://uproot.us/docs/creating-custom-field-types/
*
* @since 3.0
**/
function get_field_types() {
$field_types = array(
'text' => 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'] = sanitize_text_field( $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_html( $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 = sanitize_text_field( $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 = sanitize_text_field( $_GET['post_type'] );
} 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 );
?>
<div class="attachments-attachment-field attachments-attachment-field-<?php echo esc_attr( $instance ); ?> attachments-attachment-field-<?php echo esc_attr( $field->type ); ?> attachment-field-<?php echo esc_attr( $field->name ); ?>">
<div class="attachment-label attachment-label-<?php echo esc_attr( $instance ); ?>">
<label for="<?php echo esc_attr( $field->field_id ); ?>"><?php echo esc_html( $field->label ); ?></label>
</div>
<div class="attachment-field attachment-field-<?php echo esc_attr( $instance ); ?>">
<?php echo $this->create_field( $instance, $field ); ?>
</div>
</div>
<?php } else {
$field = false;
}
return $field;
}
/**
* Outputs HTML for submitted field
*
* @since 3.0
*/
function create_field( $instance, $field ) {
$field = (object) $field;
// with all of our attributes properly set, we can output
$field->html( $field );
}
/**
* Outputs all the necessary markup for an Attachment
*
* @since 3.0
*/
function create_attachment( $instance, $attachment = null ) {
?>
<div class="attachments-attachment attachments-attachment-<?php echo esc_attr( $instance ); ?>">
<?php $array_flag = ( isset( $attachment->uid ) ) ? $attachment->uid : '{{ attachments.attachment_uid }}'; ?>
<input type="hidden" class="attachments-track-id" name="attachments[<?php echo esc_attr( $instance ); ?>][<?php echo esc_attr( $array_flag ); ?>][id]" value="<?php echo isset( $attachment->id ) ? esc_attr( $attachment->id ) : '{{ attachments.id }}' ; ?>" />
<?php
// since attributes can change over time (image gets replaced, cropped, etc.) we'll pull that info
if ( isset( $attachment->id ) ) {
// we'll just use the full size since that's what Media in 3.5 uses
$attachment_meta = (array)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 );
$filename = explode( "/", $attachment_meta['file'] );
}
$attachment->width = isset( $attachment_meta['width'] ) ? $attachment_meta['width'] : null;
$attachment->height = isset( $attachment_meta['height'] ) ? $attachment_meta['height'] : null;
$attachment->filename = basename( $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;
}
?>
<div class="attachment-meta media-sidebar">
<?php
$thumbnail = isset( $attachment->id ) ? wp_get_attachment_image_src( $attachment->id, 'thumbnail', true ) : false;
$image = $thumbnail ? $thumbnail[0] : '{{ attachments.attachment_thumb }}';
?>
<div class="attachment-thumbnail">
<img src="<?php echo $image; ?>" alt="Thumbnail" />
</div>
<div class="attachment-details attachment-info details">
<div class="filename"><?php echo isset( $attachment->filename ) ? esc_html( $attachment->filename ) : '{{ attachments.filename }}' ; ?></div>
<?php if( ( isset( $attachment->id ) && isset( $attachment->width ) ) || ! isset( $attachment->id ) ) : ?>
<div class="dimensions"><?php echo isset( $attachment->width ) ? esc_html( $attachment->width ) : '{{ attachments.width }}' ; ?> × <?php echo isset( $attachment->height ) ? esc_html( $attachment->height ) : '{{ attachments.height }}' ; ?></div>
<?php endif; ?>
<div class="edit-attachment-asset"><a href="#"><?php _e( 'Change', 'attachments' ); ?></a></div>
<div class="delete-attachment"><a href="#"><?php _e( 'Remove', 'attachments' ); ?></a></div>
<div class="attachments-attachment-fields-toggle"><a href="#"><?php _e( 'Toggle Fields', 'attachments' ); ?></a></div>
</div>
</div>
<div class="attachments-handle"><img src="<?php echo trailingslashit( $this->url ) . 'images/handle.gif'; ?>" alt="Handle" width="20" height="20" /></div>
<div class="attachments-fields">
<?php
foreach( $this->instances[$instance]['fields'] as $field ) {
$field_ref = $this->create_attachment_field( $instance, $field, $attachment );
}
?>
</div>
</div>
<?php
}
/**
* Callback to fire the assets() function for reach registered field
*
* @since 3.1
*/
function field_assets() {
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->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 ) ) { ?>
<script type="text/javascript">
var ATTACHMENTS_VIEWS = {};
</script>
<?php foreach ( $this->instances_for_post_type as $instance ) : ?>
<script type="text/template" id="tmpl-attachments-<?php echo esc_attr( $instance ); ?>">
<?php $this->create_attachment( $instance ); ?>
</script>
<?php endforeach;
}
}
/**
* Saves our Attachments metadata when the post is saved
*
* @since 3.0
*/
function save( $post_id ) {
// is the user logged in?
if ( ! is_user_logged_in() ) {
return $post_id;
}
// is the nonce set?
if ( ! isset( $_POST['attachments_nonce'] ) ) {
return $post_id;
}
// is the nonce valid?
if ( ! wp_verify_nonce( $_POST['attachments_nonce'], 'attachments_save' ) ) {
return $post_id;
}
// can this user edit this post?
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
// passed authentication, proceed with save
// if the user deleted all Attachments we won't have our key
$attachments_meta = isset( $_POST['attachments'] ) ? $_POST['attachments'] : array();
$this->save_metadata( $post_id, $attachments_meta );
return $post_id;
}
/**
* Processes submitted fields and saves Attachments' post metadata
*
* @param int $post_id The post ID
* @param array $attachments_meta Multidimenaional array containing Attachments data
* @return bool
* @since 3.4.3
*/
function save_metadata( $post_id = 0, $attachments_meta = null ) {
if ( ! is_array( $attachments_meta ) || ! is_int( $post_id ) || intval( $post_id ) < 1 ) {
return false;
}
// final data store
$attachments = array();
$attachments_meta = apply_filters( 'attachments_meta_before_save', $attachments_meta, $post_id );
// loop through each submitted instance
foreach ( $attachments_meta as $instance => $instance_attachments ) {
// loop through each Attachment of this instance
foreach( $instance_attachments as $key => $attachment ) {
// see if it was pulled as JSON from a delete cleanup
if ( is_object( $attachment ) ) {
$attachment = get_object_vars( $attachment );
if ( is_array( $attachment ) && !empty( $attachment ) ) {
if ( isset( $attachment['fields'] ) && is_object( $attachment['fields'] ) ) {
$attachment['fields'] = get_object_vars( $attachment['fields'] );
}
}
}
$attachment_exists = isset( $attachment['id'] ) ? get_post( absint( $attachment['id'] ) ) : false;
// make sure the attachment exists
if ( $attachment_exists ) {
// 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 don't 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;
}
}
// set the post parent if applicable
// need to first check to make sure we're not overwriting a native Attach
$attach_post_ref = $attachment_exists;
if ( $attach_post_ref->post_parent == 0 && ! empty( $this->instances[ $instance ]['post_parent'] ) ) {
// no current Attach, we can add ours
$attach_post = array(
'ID' => absint( $attachment['id'] ),
'post_parent' => $post_id,
);
wp_update_post( $attach_post );
}
$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 );
// fix potentially encoded Unicode
$attachments = str_replace( '\\', '\\\\', $attachments );
// we're going to wipe out any existing Attachments meta (because we'll put it back)
return update_post_meta( $post_id, $this->meta_key, $attachments );
} else {
// there are no attachments so we'll clean up the record
return delete_post_meta( $post_id, $this->meta_key );
}
}
/**
* 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 false;
}
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 ) ) || is_object( $attachments_raw ) ) {
// cast an object if necessary
if ( is_object( $attachments_raw ) ) $attachments_raw = (array) $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;
// convert field newline characters properly
if ( ! empty( $attachments_raw ) ) {
foreach ( $attachments_raw as $instanceKey => $instance ) {
foreach( $instance as $attachmentKey => $attachment ) {
if( isset( $attachment->fields ) ) {
foreach( $attachment->fields as $fieldKey => $fieldValue ) {
$attachment->fields->$fieldKey = str_replace( '\\n', "\n", $fieldValue );
}
}
}
}
}
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
* @param string $instance The applicable instance
* @return object Post-processed Attachment data
*
* @since 3.3
*/
function process_attachment( $attachment, $instance ) {
if ( ! is_object( $attachment ) || !is_string( $instance ) ) {
return $attachment;
}
if ( isset( $attachment->fields ) && 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 false;
}
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() {
if ( ! ( defined( 'ATTACHMENTS_SETTINGS_SCREEN' ) && ATTACHMENTS_SETTINGS_SCREEN === false ) ) {
if ( apply_filters( 'attachments_settings_screen', true ) ) {
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();