PageRenderTime 64ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/attachments/classes/class.attachments.php

https://github.com/zackseuberling/k-r
PHP | 1787 lines | 976 code | 380 blank | 431 comment | 149 complexity | 8a4f00db1fae836f6102f9a8d858a8fe MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Attachments
  4. *
  5. * Attachments allows you to simply append any number of items from your WordPress
  6. * Media Library to Posts, Pages, and Custom Post Types
  7. *
  8. * @package Attachments
  9. * @subpackage Main
  10. */
  11. // Exit if accessed directly
  12. if( !defined( 'ABSPATH' ) ) exit;
  13. // Declare our class
  14. if ( !class_exists( 'Attachments' ) ) :
  15. /**
  16. * Main Attachments Class
  17. *
  18. * @since 3.0
  19. */
  20. class Attachments {
  21. private $version; // stores Attachments' version number
  22. private $url; // stores Attachments' URL
  23. private $dir; // stores Attachments' directory
  24. private $instances; // all registered Attachments instances
  25. private $instances_for_post_type; // instance names that apply to the current post type
  26. private $fields; // stores all registered field types
  27. private $attachments; // stores all of the Attachments for the given instance
  28. private $legacy = false; // whether or not there is legacy Attachments data
  29. private $legacy_pro = false; // whether or not there is legacy Attachment Pro data
  30. private $image_sizes = array( 'full' ); // store all registered image sizes
  31. private $default_instance = true; // use the default instance?
  32. private $attachments_ref = -1; // flags where a get() loop last did it's thing
  33. private $meta_key = 'attachments'; // our meta key
  34. private $valid_filetypes = array( // what WordPress considers to be valid file types
  35. 'image',
  36. 'video',
  37. 'text',
  38. 'audio',
  39. 'application'
  40. );
  41. /**
  42. * Constructor
  43. *
  44. * @since 3.0
  45. */
  46. function __construct( $instance = null, $post_id = null )
  47. {
  48. global $_wp_additional_image_sizes;
  49. // establish our environment variables
  50. $this->version = '3.4';
  51. $this->url = ATTACHMENTS_URL;
  52. $this->dir = ATTACHMENTS_DIR;
  53. // includes
  54. include_once( ATTACHMENTS_DIR . 'upgrade.php' );
  55. include_once( ATTACHMENTS_DIR . '/classes/class.field.php' );
  56. // include our fields
  57. $this->fields = $this->get_field_types();
  58. // deal with our legacy issues if the user hasn't dismissed or migrated already
  59. $this->check_for_legacy_data();
  60. // set our image sizes
  61. $this->image_sizes = array_merge( $this->image_sizes, get_intermediate_image_sizes() );
  62. // hook into WP
  63. add_action( 'admin_enqueue_scripts', array( $this, 'assets' ), 999, 1 );
  64. add_action( 'admin_enqueue_scripts', array( $this, 'admin_pointer' ), 999 );
  65. // register our user-defined instances
  66. add_action( 'init', array( $this, 'setup_instances' ) );
  67. // determine which instances apply to the current post type
  68. add_action( 'init', array( $this, 'set_instances_for_current_post_type' ) );
  69. add_action( 'add_meta_boxes', array( $this, 'meta_box_init' ) );
  70. add_action( 'admin_footer', array( $this, 'admin_footer' ) );
  71. add_action( 'save_post', array( $this, 'save' ) );
  72. // only show the Settings screen if it hasn't been explicitly disabled
  73. if( !( defined( 'ATTACHMENTS_SETTINGS_SCREEN' ) && ATTACHMENTS_SETTINGS_SCREEN === false ) )
  74. add_action( 'admin_menu', array( $this, 'admin_page' ) );
  75. // with version 3 we'll be giving at least one admin notice
  76. add_action( 'admin_notices', array( $this, 'admin_notice' ) );
  77. add_action( 'admin_head', array( $this, 'field_inits' ) );
  78. add_action( 'admin_print_footer_scripts', array( $this, 'field_assets' ) );
  79. // execution of actions varies depending on whether we're in the admin or not and an instance was passed
  80. if( is_admin() )
  81. {
  82. add_action( 'after_setup_theme', array( $this, 'apply_init_filters' ) );
  83. $this->attachments = $this->get_attachments( $instance, $post_id );
  84. }
  85. elseif( !is_null( $instance ) )
  86. {
  87. $this->apply_init_filters();
  88. $this->attachments = $this->get_attachments( $instance, $post_id );
  89. }
  90. }
  91. /**
  92. * Various initialization filter triggers
  93. *
  94. * @since 3.4
  95. */
  96. function apply_init_filters()
  97. {
  98. // allows a different meta_key to be used
  99. $this->meta_key = apply_filters( 'attachments_meta_key', $this->meta_key );
  100. }
  101. /**
  102. * Stores whether or not this environment has active legacy Attachments/Pro data
  103. *
  104. * @since 3.1.3
  105. */
  106. function check_for_legacy_data()
  107. {
  108. // we'll get a warning issued if fired when Network Activated
  109. // since it's supremely unlikely we'd have legacy data at this point, we're going to short circuit
  110. if( is_multisite() )
  111. {
  112. $plugins = get_site_option( 'active_sitewide_plugins' );
  113. if ( isset($plugins['attachments/index.php']) )
  114. return;
  115. }
  116. // deal with our legacy issues if the user hasn't dismissed or migrated already
  117. if( false == get_option( 'attachments_migrated' ) && false == get_option( 'attachments_ignore_migration' ) )
  118. {
  119. $legacy_attachments_settings = get_option( 'attachments_settings' );
  120. if( $legacy_attachments_settings && is_array( $legacy_attachments_settings['post_types'] ) && count( $legacy_attachments_settings['post_types'] ) )
  121. {
  122. // we have legacy settings, so we're going to use the post types
  123. // that Attachments is currently utilizing
  124. // the keys are the actual CPT names, so we need those
  125. foreach( $legacy_attachments_settings['post_types'] as $post_type => $value )
  126. if( $value )
  127. $post_types[] = $post_type;
  128. // set up our WP_Query args to grab anything with legacy data
  129. $args = array(
  130. 'post_type' => isset( $post_types ) ? $post_types : array( 'post', 'page' ),
  131. 'post_status' => 'any',
  132. 'posts_per_page' => 1,
  133. 'meta_key' => '_attachments',
  134. 'suppress_filters' => true,
  135. );
  136. $legacy = new WP_Query( $args );
  137. $this->legacy = empty( $legacy->found_posts ) ? false : true;
  138. }
  139. }
  140. // deal with our legacy Pro issues if the user hasn't dismissed or migrated already
  141. if( false == get_option( 'attachments_pro_migrated' ) && false == get_option( 'attachments_pro_ignore_migration' ) )
  142. {
  143. $post_types = get_post_types();
  144. // set up our WP_Query args to grab anything (really anything) with legacy data
  145. $args = array(
  146. 'post_type' => !empty( $post_types ) ? $post_types : array( 'post', 'page' ),
  147. 'post_status' => 'any',
  148. 'posts_per_page' => 1,
  149. 'meta_key' => '_attachments_pro',
  150. 'suppress_filters' => true,
  151. );
  152. $legacy_pro = new WP_Query( $args );
  153. $this->legacy_pro = empty( $legacy_pro->found_posts ) ? false : true;
  154. }
  155. }
  156. /**
  157. * Facilitates searching for Attachments
  158. *
  159. * @since 3.3
  160. */
  161. function search( $query = null, $params = array() )
  162. {
  163. $defaults = array(
  164. 'attachment_id' => null, // not searching for a single attachment ID
  165. 'instance' => 'attachments', // default instance
  166. 'post_type' => null, // search 'any' post type
  167. 'post_id' => null, // searching all posts
  168. 'post_status' => 'publish', // search only published posts
  169. 'fields' => null, // search all fields
  170. );
  171. $query = is_null( $query ) ? null : sanitize_text_field( $query );
  172. $params = array_merge( $defaults, $params );
  173. // sanitize parameters
  174. $params['attachment_id'] = is_null( $params['attachment_id'] ) ? null : intval( $params['attachment_id'] );
  175. $params['instance'] = !is_string( $params['instance'] ) ? 'attachments' : sanitize_text_field( $params['instance'] );
  176. $params['post_type'] = is_null( $params['post_type'] ) ? 'any' : sanitize_text_field( $params['post_type'] );
  177. $params['post_id'] = is_null( $params['post_id'] ) ? null : intval( $params['post_id'] );
  178. $params['post_status'] = sanitize_text_field( $params['post_status'] );
  179. if( is_string( $params['fields'] ) )
  180. $params['fields'] = array( $params['fields'] ); // we always want an array
  181. // since we have an array for our fields, we need to loop through and sanitize
  182. for( $i = 0; $i < count( $params['fields'] ); $i++ )
  183. $params['fields'][$i] = sanitize_text_field( $params['fields'][$i] );
  184. // prepare our search args
  185. $args = array(
  186. 'nopaging' => true,
  187. 'post_status' => $params['post_status'],
  188. 'meta_query' => array(
  189. array(
  190. 'key' => 'attachments',
  191. 'value' => $query,
  192. 'compare' => 'LIKE'
  193. )
  194. ),
  195. );
  196. // append any applicable parameters that got passed to the original method call
  197. if( $params['post_type'] )
  198. $args['post_type'] = $params['post_type'];
  199. if( $params['post_id'] )
  200. $args['post__in'] = array( $params['post_id'] ); // avoid using 'p' or 'page_id'
  201. // we haven't utilized all parameters yet because they're meta-value based so we need to
  202. // do some parsing on our end to validate the returned results
  203. $possible_posts = new WP_Query( $args );
  204. $potential_attachments = false; // stores valid attachments as restrictions are added
  205. if( $possible_posts->found_posts )
  206. {
  207. // we have results from the reliminary search, we need to quantify them
  208. while( $possible_posts->have_posts() )
  209. {
  210. $possible_posts->next_post();
  211. $possible_post_ids[] = $possible_posts->post->ID;
  212. }
  213. // now that we have our possible post IDs we can grab all Attachments for all of those posts
  214. foreach( $possible_post_ids as $possible_post_id )
  215. {
  216. $possible_attachments = $this->get_attachments( $params['instance'], $possible_post_id );
  217. foreach( $possible_attachments as $possible_attachment )
  218. $potential_attachments[] = $possible_attachment;
  219. }
  220. }
  221. // if there aren't even any potential attachments, we'll just short circuit
  222. if( !$potential_attachments )
  223. return;
  224. // first we need to make sure that our query matches each attachment
  225. // we need to do this because the LIKE query returned the entire meta record,
  226. // not necessarily tied to any specific Attachment
  227. $total_potentials = count( $potential_attachments );
  228. for( $i = 0; $i < $total_potentials; $i++ )
  229. {
  230. $valid = false;
  231. // if we need to limit our search to specific fields, we'll do that here
  232. if( $params['fields'] )
  233. {
  234. // we only want to check certain fields
  235. foreach( $params['fields'] as $field )
  236. if( isset( $potential_attachments[$i]->fields->$field ) ) // does the field exist?
  237. if( strpos( strtolower( $potential_attachments[$i]->fields->$field ),
  238. strtolower( $query ) ) !== false ) // does the value match?
  239. $valid = true;
  240. }
  241. else
  242. {
  243. // we want to check all fields
  244. foreach( $potential_attachments[$i]->fields as $field_name => $field_value )
  245. if( strpos( strtolower( $field_value) , strtolower( $query ) ) !== false )
  246. $valid = true;
  247. }
  248. if( !$valid )
  249. unset( $potential_attachments[$i] );
  250. // now our potentials have been limited to each match the query based on any field
  251. }
  252. // limit to attachment ID if applicable
  253. if( $params['attachment_id'] )
  254. {
  255. $total_potentials = count( $potential_attachments );
  256. for( $i = 0; $i < $total_potentials; $i++ )
  257. if( $potential_attachments[$i]->id != $params['attachment_id'] )
  258. unset( $potential_attachments[$i] );
  259. }
  260. $this->attachments = array_values( $potential_attachments );
  261. }
  262. /**
  263. * Returns whether or not the current object has any Attachments
  264. *
  265. * @since 3.0
  266. */
  267. function exist()
  268. {
  269. return !empty( $this->attachments );
  270. }
  271. /**
  272. * Returns the number of Attachments
  273. *
  274. * @since 3.0.6
  275. */
  276. function total()
  277. {
  278. return count( $this->attachments );
  279. }
  280. /**
  281. * Returns the next Attachment for the current object and increments the index
  282. *
  283. * @since 3.0
  284. */
  285. function get()
  286. {
  287. $this->attachments_ref++;
  288. if( !count( $this->attachments ) || $this->attachments_ref >= count( $this->attachments ) )
  289. return false;
  290. return $this->attachments[$this->attachments_ref];
  291. }
  292. /**
  293. * Returns a specific Attachment
  294. *
  295. * @since 3.2
  296. */
  297. function get_single( $index )
  298. {
  299. return isset( $this->attachments[$index] ) ? $this->attachments[$index] : false;
  300. }
  301. /**
  302. * Returns the asset (array) for the current Attachment
  303. *
  304. * @since 3.0.6
  305. */
  306. function asset( $size = 'thumbnail', $index = null )
  307. {
  308. $index = is_null( $index ) ? $this->attachments_ref : intval( $index );
  309. // do we have our meta yet?
  310. if( !isset( $this->attachments[$index]->meta ) )
  311. $this->attachments[$index]->meta = wp_get_attachment_metadata( $this->attachments[$index]->id );
  312. // is it an image?
  313. if(
  314. isset( $this->attachments[$index]->meta['sizes'] ) && // is it an image?
  315. in_array( $size, $this->image_sizes ) ) // do we have the right size?
  316. {
  317. $asset = wp_get_attachment_image_src( $this->attachments[$index]->id, $size );
  318. }
  319. else
  320. {
  321. // either it's not an image or we don't have the proper size, so we'll use the icon
  322. $asset = $this->icon( $index );
  323. }
  324. return $asset;
  325. }
  326. /**
  327. * Returns the icon (array) for the current Attachment
  328. *
  329. * @since 3.0.6
  330. */
  331. function icon( $index = null )
  332. {
  333. $index = is_null( $index ) ? $this->attachments_ref : intval( $index );
  334. $asset = wp_get_attachment_image_src( $this->attachments[$index]->id, null, true );
  335. return $asset;
  336. }
  337. /**
  338. * Returns an appropriate <img /> for the current Attachment if it's an image
  339. *
  340. * @since 3.0
  341. */
  342. function image( $size = 'thumbnail', $index = null )
  343. {
  344. $asset = $this->asset( $size, $index );
  345. $image_src = $asset[0];
  346. $image_width = $asset[1];
  347. $image_height = $asset[2];
  348. $index = is_null( $index ) ? $this->attachments_ref : intval( $index );
  349. $image_alt = get_post_meta( $this->attachments[$index]->id, '_wp_attachment_image_alt', true );
  350. $image = '<img src="' . $image_src . '" width="' . $image_width . '" height="' . $image_height . '" alt="' . $image_alt . '" />';
  351. return $image;
  352. }
  353. /**
  354. * Returns the URL for the current Attachment if it's an image
  355. *
  356. * @since 3.0
  357. */
  358. function src( $size = 'thumbnail', $index = null )
  359. {
  360. $asset = $this->asset( $size, $index );
  361. return $asset[0];
  362. }
  363. /**
  364. * Returns the formatted filesize of the current Attachment
  365. *
  366. * @since 3.0
  367. */
  368. function filesize( $index = null )
  369. {
  370. $index = is_null( $index ) ? $this->attachments_ref : intval( $index );
  371. if( !isset( $this->attachments[$index]->id ) )
  372. return false;
  373. $url = wp_get_attachment_url( $this->attachments[$index]->id );
  374. $uploads = wp_upload_dir();
  375. $file_path = str_replace( $uploads['baseurl'], $uploads['basedir'], $url );
  376. $formatted = '0 bytes';
  377. if( file_exists( $file_path ) )
  378. {
  379. $formatted = size_format( @filesize( $file_path ) );
  380. }
  381. return $formatted;
  382. }
  383. /**
  384. * Returns the type of the current Attachment
  385. *
  386. * @since 3.0
  387. */
  388. function type( $index = null )
  389. {
  390. $index = is_null( $index ) ? $this->attachments_ref : intval( $index );
  391. if( !isset( $this->attachments[$index]->id ) )
  392. return false;
  393. $attachment_mime = explode( '/', get_post_mime_type( $this->attachments[$index]->id ) );
  394. return isset( $attachment_mime[0] ) ? $attachment_mime[0] : false;
  395. }
  396. /**
  397. * Returns the subtype of the current Attachment
  398. *
  399. * @since 3.0
  400. */
  401. function subtype( $index = null )
  402. {
  403. $index = is_null( $index ) ? $this->attachments_ref : intval( $index );
  404. if( !isset( $this->attachments[$index]->id ) )
  405. return false;
  406. $attachment_mime = explode( '/', get_post_mime_type( $this->attachments[$index]->id ) );
  407. return isset( $attachment_mime[1] ) ? $attachment_mime[1] : false;
  408. }
  409. /**
  410. * Returns the id of the current Attachment
  411. *
  412. * @since 3.0
  413. */
  414. function id( $index = null )
  415. {
  416. $index = is_null( $index ) ? $this->attachments_ref : intval( $index );
  417. return isset( $this->attachments[$index]->id ) ? $this->attachments[$index]->id : false;
  418. }
  419. /**
  420. * Returns the $post->ID of the current Attachment
  421. *
  422. * @since 3.3
  423. */
  424. function post_id( $index = null )
  425. {
  426. $index = is_null( $index ) ? $this->attachments_ref : intval( $index );
  427. return isset( $this->attachments[$index]->post_id ) ? $this->attachments[$index]->post_id : false;
  428. }
  429. /**
  430. * Returns the URL for the current Attachment
  431. *
  432. * @since 3.0
  433. */
  434. function url( $index = null )
  435. {
  436. $index = is_null( $index ) ? $this->attachments_ref : intval( $index );
  437. return isset( $this->attachments[$index]->id ) ? wp_get_attachment_url( $this->attachments[$index]->id ) : false;
  438. }
  439. /**
  440. * Returns the field value for the submitted field name
  441. *
  442. * @since 3.0
  443. */
  444. function field( $name = 'title', $index = null )
  445. {
  446. $index = is_null( $index ) ? $this->attachments_ref : intval( $index );
  447. return isset( $this->attachments[$index]->fields->$name ) ? $this->attachments[$index]->fields->$name : false;
  448. }
  449. /**
  450. * Fires all of our actions
  451. *
  452. * @since 3.0
  453. */
  454. function setup_instances()
  455. {
  456. // implement our default instance if appropriate
  457. if( !defined( 'ATTACHMENTS_DEFAULT_INSTANCE' ) )
  458. $this->register();
  459. // facilitate user-defined instance registration
  460. do_action( 'attachments_register', $this );
  461. }
  462. /**
  463. * Enqueues our necessary assets
  464. *
  465. * @since 3.0
  466. */
  467. function assets( $hook )
  468. {
  469. global $post;
  470. // we only want our assets on edit screens
  471. if( !empty( $this->instances_for_post_type ) && 'edit.php' != $hook && 'post.php' != $hook && 'post-new.php' != $hook )
  472. return;
  473. // we only want to enqueue if appropriate
  474. if( empty( $this->instances_for_post_type ) )
  475. return;
  476. $post_id = isset( $post->ID ) ? $post->ID : null;
  477. wp_enqueue_media( array( 'post' => $post_id ) );
  478. wp_enqueue_style( 'attachments', trailingslashit( $this->url ) . 'css/attachments.css', null, $this->version, 'screen' );
  479. wp_enqueue_script( 'attachments', trailingslashit( $this->url ) . 'js/attachments.js', array( 'jquery', 'jquery-ui-sortable' ), $this->version, true );
  480. }
  481. /**
  482. * Registers meta box(es) for the current edit screen
  483. *
  484. * @since 3.0
  485. */
  486. function meta_box_init()
  487. {
  488. $nonce_sent = false;
  489. if( !empty( $this->instances_for_post_type ) )
  490. {
  491. foreach( $this->instances_for_post_type as $instance )
  492. {
  493. $instance_name = $instance;
  494. $instance = (object) $this->instances[$instance];
  495. $instance->name = $instance_name;
  496. $position = isset($instance->position) ? $instance->position : 'normal';
  497. $priority = isset($instance->priority) ? $instance->priority : 'high';
  498. 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 ) );
  499. $nonce_sent = true;
  500. }
  501. }
  502. }
  503. /**
  504. * Callback that outputs the meta box markup
  505. *
  506. * @since 3.0
  507. */
  508. function meta_box_markup( $post, $metabox )
  509. {
  510. // single out our $instance
  511. $instance = (object) $metabox['args']['instance'];
  512. if( $metabox['args']['setup_nonce'] )
  513. wp_nonce_field( 'attachments_save', 'attachments_nonce' );
  514. ?>
  515. <div id="attachments-<?php echo $instance->name; ?>" class="attachments-parent-container<?php if( $instance->append == false ) : ?> attachments-prepend<?php endif; ?>">
  516. <?php if( !empty( $instance->note ) ) : ?>
  517. <div class="attachments-note"><?php echo apply_filters( 'the_content', $instance->note ); ?></div>
  518. <?php endif; ?>
  519. <?php if( $instance->append == false ) : ?>
  520. <div class="attachments-invoke-wrapper">
  521. <a class="button attachments-invoke"><?php _e( esc_attr( $instance->button_text ), 'attachments' ); ?></a>
  522. </div>
  523. <?php endif; ?>
  524. <div class="attachments-container attachments-<?php echo $instance->name; ?>"><?php
  525. if( isset( $instance->attachments ) && !empty( $instance->attachments ) )
  526. {
  527. foreach( $instance->attachments as $attachment )
  528. {
  529. // we need to give our Attachment a uid to carry through to all the fields
  530. $attachment->uid = uniqid();
  531. // we'll create the attachment
  532. $this->create_attachment( $instance->name, $attachment );
  533. }
  534. }
  535. ?></div>
  536. <?php if( $instance->append == true ) : ?>
  537. <div class="attachments-invoke-wrapper">
  538. <a class="button attachments-invoke"><?php _e( esc_attr( $instance->button_text ), 'attachments' ); ?></a>
  539. </div>
  540. <?php endif; ?>
  541. </div>
  542. <script type="text/javascript">
  543. jQuery(document).ready(function($){
  544. var $element = $('#attachments-<?php echo esc_attr( $instance->name ); ?>'),
  545. title = '<?php echo __( esc_attr( $instance->label ) ); ?>',
  546. button = '<?php echo __( esc_attr( $instance->modal_text ) ); ?>',
  547. router = '<?php echo __( esc_attr( $instance->router ) ); ?>',
  548. attachmentsframe;
  549. $element.on( 'click', '.attachments-invoke', function( event ) {
  550. var options, attachment;
  551. event.preventDefault();
  552. // if the frame already exists, open it
  553. if ( attachmentsframe ) {
  554. attachmentsframe.open();
  555. attachmentsframe.content.mode(router);
  556. return;
  557. }
  558. // set our seetings
  559. attachmentsframe = wp.media({
  560. title: title,
  561. <?php if( $instance->limit < 0 || $instance->limit > 1 ) : ?>
  562. multiple: true,
  563. <?php endif; ?>
  564. library: {
  565. type: '<?php echo esc_attr( implode( ",", $instance->filetype ) ); ?>'
  566. },
  567. // Customize the submit button.
  568. button: {
  569. // Set the text of the button.
  570. text: button
  571. }
  572. });
  573. // set up our select handler
  574. attachmentsframe.on( 'select', function() {
  575. selection = attachmentsframe.state().get('selection');
  576. if ( ! selection )
  577. return;
  578. // compile our Underscore template using Mustache syntax
  579. _.templateSettings = {
  580. variable : 'attachments',
  581. interpolate : /\{\{(.+?)\}\}/g
  582. }
  583. var template = _.template($('script#tmpl-attachments-<?php echo $instance->name; ?>').html());
  584. // loop through the selected files
  585. selection.each( function( attachment ) {
  586. // set our attributes to the template
  587. attachment.attributes.attachment_uid = attachments_uniqid( 'attachmentsjs' );
  588. // by default use the generic icon
  589. attachment.attributes.attachment_thumb = attachment.attributes.icon;
  590. // only thumbnails have sizes which is what we're on the hunt for
  591. // TODO: this is a mess
  592. if(attachments_isset(attachment.attributes)){
  593. if(attachments_isset(attachment.attributes.sizes)){
  594. if(attachments_isset(attachment.attributes.sizes.thumbnail)){
  595. if(attachments_isset(attachment.attributes.sizes.thumbnail.url)){
  596. // use the thumbnail
  597. attachment.attributes.attachment_thumb = attachment.attributes.sizes.thumbnail.url;
  598. }
  599. }
  600. }
  601. }
  602. var templateData = attachment.attributes;
  603. // append the template
  604. $element.find('.attachments-container').<?php if( $instance->append ) : ?>append<?php else : ?>prepend<?php endif; ?>(template(templateData));
  605. // if we're in a sidebar we DO want to show the fields which are normally hidden on load via CSS
  606. if($element.parents('#side-sortables')){
  607. $element.find('.attachments-attachment:last .attachments-fields').show();
  608. }
  609. // see if we need to set a default
  610. // TODO: can we tie this into other field types (select, radio, checkbox)?
  611. $element.find('.attachments-attachment:last .attachments-fields input, .attachments-attachment:last .attachments-fields textarea').each(function(){
  612. if($(this).data('default')){
  613. var meta_for_default = $(this).data('default');
  614. if(attachments_isset(attachment.attributes)){
  615. if(attachments_isset(attachment.attributes[meta_for_default])){
  616. $(this).val(attachment.attributes[meta_for_default]);
  617. }
  618. }
  619. }
  620. });
  621. $('body').trigger('attachments/new');
  622. // if it wasn't an image we need to ditch the dimensions
  623. if(!attachments_isset(attachment.attributes.width)||!attachments_isset(attachment.attributes.height)){
  624. $element.find('.attachments-attachment:last .dimensions').hide();
  625. }
  626. });
  627. });
  628. // open the frame
  629. attachmentsframe.open();
  630. attachmentsframe.content.mode(router);
  631. });
  632. $element.on( 'click', '.delete-attachment a', function( event ) {
  633. var targetAttachment;
  634. event.preventDefault();
  635. targetAttachment = $(this).parents('.attachments-attachment');
  636. targetAttachment.find('.attachment-meta').fadeOut(125);
  637. targetAttachment.css('min-height',0).animate(
  638. {
  639. padding: 0,
  640. margin: 0,
  641. height: 0
  642. },
  643. 600,
  644. function(){
  645. targetAttachment.remove();
  646. }
  647. );
  648. } );
  649. });
  650. </script>
  651. <?php }
  652. /**
  653. * Support the inclusion of custom, user-defined field types
  654. * Borrowed implementation from Custom Field Suite by Matt Gibbs
  655. * https://uproot.us/docs/creating-custom-field-types/
  656. *
  657. * @since 3.0
  658. **/
  659. function get_field_types()
  660. {
  661. $field_types = array(
  662. 'text' => ATTACHMENTS_DIR . 'classes/fields/class.field.text.php',
  663. 'textarea' => ATTACHMENTS_DIR . 'classes/fields/class.field.textarea.php',
  664. 'select' => ATTACHMENTS_DIR . 'classes/fields/class.field.select.php',
  665. 'wysiwyg' => ATTACHMENTS_DIR . 'classes/fields/class.field.wysiwyg.php',
  666. );
  667. // support custom field types
  668. // $field_types = apply_filters( 'attachments_fields', $field_types );
  669. $field_index = 0;
  670. foreach( $field_types as $type => $path )
  671. {
  672. // proceed with inclusion
  673. if( file_exists( $path ) )
  674. {
  675. // include the file
  676. include_once( $path );
  677. // store the registered classes so we can single out what gets added
  678. $existing_classes = get_declared_classes();
  679. // we're going to use our Attachments class as a reference because
  680. // during subsequent instantiations of Attachments (e.g. within template files)
  681. // these field classes WILL NOT be added to the array again because
  682. // we're using include_once() so that strategy is no longer useful
  683. // determine it's class
  684. $flag = array_search( 'Attachments_Field', $existing_classes );
  685. // the field's class is next
  686. $field_class = $existing_classes[$flag + $field_index + 1];
  687. // create our link using our new field class
  688. $field_types[$type] = $field_class;
  689. $field_index++;
  690. }
  691. }
  692. // send it back
  693. return $field_types;
  694. }
  695. /**
  696. * Registers a field type for use within an instance
  697. *
  698. * @since 3.0
  699. */
  700. function register_field( $params = array() )
  701. {
  702. $defaults = array(
  703. 'name' => 'title',
  704. 'type' => 'text',
  705. 'label' => __( 'Title', 'attachments' ),
  706. 'meta' => array(),
  707. );
  708. $params = array_merge( $defaults, $params );
  709. // ensure it's a valid type
  710. if( !isset( $this->fields[$params['type']] ) )
  711. return false;
  712. // sanitize
  713. if( isset( $params['name'] ) )
  714. $params['name'] = str_replace( '-', '_', sanitize_title( $params['name'] ) );
  715. if( isset( $params['label'] ) )
  716. $params['label'] = __( esc_html( $params['label'] ) );
  717. if( !isset( $params['meta'] ) || !is_array( $params['meta'] ) )
  718. $params['meta'] = array();
  719. // instantiate the class for this field and send it back
  720. return new $this->fields[ $params['type'] ]( $params['name'], $params['label'], $params['meta'] );
  721. }
  722. /**
  723. * Registers an Attachments instance
  724. *
  725. * @since 3.0
  726. */
  727. function register( $name = 'attachments', $params = array() )
  728. {
  729. $defaults = array(
  730. // title of the meta box (string)
  731. 'label' => __( 'Attachments', 'attachments' ),
  732. // all post types to utilize (string|array)
  733. 'post_type' => array( 'post', 'page' ),
  734. // meta box position (string) (normal, side or advanced)
  735. 'position' => 'normal',
  736. // meta box priority (string) (high, default, low, core)
  737. 'priority' => 'high',
  738. // maximum number of Attachments (int) (-1 is unlimited)
  739. 'limit' => -1,
  740. // allowed file type(s) (array) (image|video|text|audio|application)
  741. 'filetype' => null, // no filetype limit
  742. // include a note within the meta box (string)
  743. 'note' => null, // no note
  744. // by default new Attachments will be appended to the list
  745. // but you can have then prepend if you set this to false
  746. 'append' => true,
  747. // text for 'Attach' button (string)
  748. 'button_text' => __( 'Attach', 'attachments' ),
  749. // text for modal 'Attach' button (string)
  750. 'modal_text' => __( 'Attach', 'attachments' ),
  751. // which tab should be the default in the modal (string) (browse|upload)
  752. 'router' => 'browse',
  753. // fields for this instance (array)
  754. 'fields' => array(
  755. array(
  756. 'name' => 'title', // unique field name
  757. 'type' => 'text', // registered field type
  758. 'label' => __( 'Title', 'attachments' ), // label to display
  759. 'default' => 'title', // default value upon selection
  760. ),
  761. array(
  762. 'name' => 'caption', // unique field name
  763. 'type' => 'wysiwyg', // registered field type
  764. 'label' => __( 'Caption', 'attachments' ), // label to display
  765. 'default' => 'caption', // default value upon selection
  766. ),
  767. ),
  768. );
  769. $params = array_merge( $defaults, $params );
  770. // sanitize
  771. if( !is_array( $params['post_type'] ) )
  772. $params['post_type'] = array( $params['post_type'] ); // we always want an array
  773. if( !is_array( $params['filetype'] ) )
  774. $params['filetype'] = array( $params['filetype'] ); // we always want an array
  775. $params['label'] = esc_html( $params['label'] );
  776. $params['limit'] = intval( $params['limit'] );
  777. $params['note'] = esc_sql( $params['note'] );
  778. $params['button_text'] = esc_attr( $params['button_text'] );
  779. $params['modal_text'] = esc_attr( $params['modal_text'] );
  780. // make sure we've got valid filetypes
  781. if( is_array( $params['filetype'] ) )
  782. {
  783. foreach( $params['filetype'] as $key => $filetype )
  784. {
  785. if( !in_array( $filetype, $this->valid_filetypes ) )
  786. {
  787. unset( $params['filetype'][$key] );
  788. }
  789. }
  790. }
  791. // WordPress sanitizes post type names when registering, so we will too
  792. foreach( $params['post_type'] as $key => $post_type )
  793. $params['post_type'][$key] = sanitize_key( $post_type );
  794. // make sure the instance name is proper
  795. $instance = str_replace( '-', '_', sanitize_title( $name ) );
  796. // register the fields
  797. if( isset( $params['fields'] ) && is_array( $params['fields'] ) && count( $params['fields'] ) )
  798. {
  799. foreach( $params['fields'] as $field )
  800. {
  801. // register the field
  802. $this->register_field( $field );
  803. }
  804. }
  805. // set the instance
  806. $this->instances[$instance] = $params;
  807. // set the Attachments for this instance
  808. $this->instances[$instance]['attachments'] = $this->get_attachments( $instance );
  809. }
  810. /**
  811. * Gets the applicable Attachments instances for the current post type
  812. *
  813. * @since 3.0
  814. */
  815. function get_instances_for_post_type( $post_type = null )
  816. {
  817. $post_type = ( !is_null( $post_type ) && post_type_exists( $post_type ) ) ? $post_type : $this->get_post_type();
  818. $instances = array();
  819. if( !empty( $this->instances ) )
  820. {
  821. foreach( $this->instances as $name => $params )
  822. {
  823. if( in_array( $post_type, $params['post_type'] ) )
  824. {
  825. $instances[] = $name;
  826. }
  827. }
  828. }
  829. return $instances;
  830. }
  831. /**
  832. * Our own implementation of WordPress' get_post_type() as it's not
  833. * functional when we need it
  834. *
  835. * @since 3.0
  836. */
  837. function get_post_type()
  838. {
  839. global $post;
  840. // TODO: Retrieving the post_type at this point is ugly to say the least. This needs major cleanup.
  841. if( empty( $post->ID ) && isset( $_GET['post_type'] ) )
  842. {
  843. $post_type = str_replace( '-', '_', sanitize_title( $_GET['post_type'] ) ); // TODO: Better sanitization
  844. }
  845. elseif( !empty( $post->ID ) )
  846. {
  847. $post_type = get_post_type( $post->ID );
  848. }
  849. elseif( isset( $_GET['post'] ) )
  850. {
  851. $post_type = get_post_type( intval( $_GET['post'] ) );
  852. }
  853. else
  854. {
  855. $post_type = 'post';
  856. }
  857. return $post_type;
  858. }
  859. /**
  860. * Sets the applicable Attachments instances for the current post type
  861. *
  862. * @since 3.0
  863. */
  864. function set_instances_for_current_post_type()
  865. {
  866. // store the applicable instances for this post type
  867. $this->instances_for_post_type = $this->get_instances_for_post_type( $this->get_post_type() );
  868. }
  869. /**
  870. * Outputs HTML for a single Attachment within an instance
  871. *
  872. * @since 3.0
  873. */
  874. function create_attachment_field( $instance, $field, $attachment = null )
  875. {
  876. // the $field at this point is just the user-declared array
  877. // we need to make it a field object
  878. $type = $field['type'];
  879. if( isset( $this->fields[$type] ) )
  880. {
  881. $name = sanitize_title( $field['name'] );
  882. $label = esc_html( $field['label'] );
  883. $default = isset( $field['default'] ) ? $field['default'] : false; // validated in the class
  884. $meta = isset( $field['meta'] ) ? $field['meta'] : array();
  885. $value = isset( $attachment->fields->$name ) ? $attachment->fields->$name : null;
  886. $field = new $this->fields[$type]( $name, $label, $value, $meta );
  887. $field->value = $field->format_value_for_input( $field->value );
  888. // does this field already have a unique ID?
  889. $uid = ( isset( $attachment->uid ) ) ? $attachment->uid : null;
  890. // TODO: make sure we've got a registered instance
  891. $field->set_field_instance( $instance, $field );
  892. $field->set_field_identifiers( $field, $uid );
  893. $field->set_field_type( $type );
  894. $field->set_field_default( $default );
  895. ?>
  896. <div class="attachments-attachment-field attachments-attachment-field-<?php echo $instance; ?> attachments-attachment-field-<?php echo $field->type; ?> attachment-field-<?php echo $field->name; ?>">
  897. <div class="attachment-label attachment-label-<?php echo $instance; ?>">
  898. <label for="<?php echo $field->field_id; ?>"><?php echo $field->label; ?></label>
  899. </div>
  900. <div class="attachment-field attachment-field-<?php echo $instance; ?>">
  901. <?php echo $this->create_field( $instance, $field ); ?>
  902. </div>
  903. </div>
  904. <?php
  905. }
  906. else
  907. {
  908. $field = false;
  909. }
  910. return $field;
  911. }
  912. /**
  913. * Outputs HTML for submitted field
  914. *
  915. * @since 3.0
  916. */
  917. function create_field( $instance, $field )
  918. {
  919. $field = (object) $field;
  920. // with all of our attributes properly set, we can output
  921. $field->html( $field );
  922. }
  923. /**
  924. * Outputs all the necessary markup for an Attachment
  925. *
  926. * @since 3.0
  927. */
  928. function create_attachment( $instance, $attachment = null )
  929. {
  930. ?>
  931. <div class="attachments-attachment attachments-attachment-<?php echo $instance; ?>">
  932. <?php $array_flag = ( isset( $attachment->uid ) ) ? $attachment->uid : '{{ attachments.attachment_uid }}'; ?>
  933. <input type="hidden" name="attachments[<?php echo $instance; ?>][<?php echo $array_flag; ?>][id]" value="<?php echo isset( $attachment->id ) ? $attachment->id : '{{ attachments.id }}' ; ?>" />
  934. <?php
  935. // since attributes can change over time (image gets replaced, cropped, etc.) we'll pull that info
  936. if( isset( $attachment->id ) )
  937. {
  938. // we'll just use the full size since that's what Media in 3.5 uses
  939. $attachment_meta = wp_get_attachment_metadata( $attachment->id );
  940. // only images return the 'file' key
  941. if( !isset( $attachment_meta['file'] ))
  942. $attachment_meta['file'] = get_attached_file( $attachment->id );
  943. $attachment->width = isset( $attachment_meta['width'] ) ? $attachment_meta['width'] : null;
  944. $attachment->height = isset( $attachment_meta['height'] ) ? $attachment_meta['height'] : null;
  945. $attachment->filename = end( explode( "/", $attachment_meta['file'] ) );
  946. $attachment_mime = explode( '/', get_post_mime_type( $attachment->id ) );
  947. $attachment->type = isset( $attachment_mime[0] ) ? $attachment_mime[0] : null;
  948. $attachment->subtype = isset( $attachment_mime[1] ) ? $attachment_mime[1] : null;
  949. }
  950. ?>
  951. <div class="attachment-meta media-sidebar">
  952. <?php
  953. $thumbnail = isset( $attachment->id ) ? wp_get_attachment_image_src( $attachment->id, 'thumbnail', true ) : false;
  954. $image = $thumbnail ? $thumbnail[0] : '{{ attachments.attachment_thumb }}';
  955. ?>
  956. <div class="attachment-thumbnail">
  957. <img src="<?php echo $image; ?>" alt="Thumbnail" />
  958. </div>
  959. <div class="attachment-details attachment-info details">
  960. <div class="filename"><?php echo isset( $attachment->filename ) ? $attachment->filename : '{{ attachments.filename }}' ; ?></div>
  961. <?php if( ( isset( $attachment->id ) && isset( $attachment->width ) ) || !isset( $attachment->id ) ) : ?>
  962. <div class="dimensions"><?php echo isset( $attachment->width ) ? $attachment->width : '{{ attachments.width }}' ; ?> &times; <?php echo isset( $attachment->height ) ? $attachment->height : '{{ attachments.height }}' ; ?></div>
  963. <?php endif; ?>
  964. <div class="delete-attachment"><a href="#"><?php _e( 'Remove', 'attachments' ); ?></a></div>
  965. <div class="attachments-attachment-fields-toggle"><a href="#"><?php _e( 'Toggle Fields', 'attachments' ); ?></a></div>
  966. </div>
  967. </div>
  968. <div class="attachments-handle"><img src="<?php echo trailingslashit( $this->url ) . 'images/handle.gif'; ?>" alt="Handle" width="20" height="20" /></div>
  969. <div class="attachments-fields">
  970. <?php
  971. foreach( $this->instances[$instance]['fields'] as $field )
  972. $field_ref = $this->create_attachment_field( $instance, $field, $attachment );
  973. ?>
  974. </div>
  975. </div>
  976. <?php
  977. }
  978. /**
  979. * Callback to fire the assets() function for reach registered field
  980. *
  981. * @since 3.1
  982. */
  983. function field_assets()
  984. {
  985. global $post;
  986. // we only want to enqueue if we're on an edit screen and it's applicable
  987. if( empty( $this->instances_for_post_type ) || empty( $post ) )
  988. return;
  989. // all metaboxes have been put in place, we can now determine which field assets need to be included
  990. // first we'll get a list of the field types on screen
  991. $fieldtypes = array();
  992. foreach( $this->instances_for_post_type as $instance )
  993. foreach( $this->instances[$instance]['fields'] as $field )
  994. $fieldtypes[] = $field['type'];
  995. // we only want to dump out assets once for each field type
  996. $fieldtypes = array_unique( $fieldtypes );
  997. // loop through and dump out all the assets
  998. foreach( $fieldtypes as $fieldtype )
  999. {
  1000. $field = new $this->fields[$fieldtype];
  1001. $field->assets();
  1002. }
  1003. }
  1004. /**
  1005. * Callback to fire the init() function for reach registered field
  1006. *
  1007. * @since 3.1
  1008. */
  1009. function field_inits()
  1010. {
  1011. global $post;
  1012. // we only want to enqueue if we're on an edit screen and it's applicable
  1013. if( empty( $this->instances_for_post_type ) || empty( $post ) )
  1014. return;
  1015. // all metaboxes have been put in place, we can now determine which field assets need to be included
  1016. // first we'll get a list of the field types on screen
  1017. $fieldtypes = array();
  1018. foreach( $this->instances_for_post_type as $instance )
  1019. foreach( $this->instances[$instance]['fields'] as $field )
  1020. $fieldtypes[] = $field['type'];
  1021. // we only want to dump out assets once for each field type
  1022. $fieldtypes = array_unique( $fieldtypes );
  1023. // loop through and dump out all the assets
  1024. foreach( $fieldtypes as $fieldtype )
  1025. {
  1026. $field = new $this->fields[$fieldtype];
  1027. $field->init();
  1028. }
  1029. }
  1030. /**
  1031. * Outputs all necessary Backbone templates
  1032. * Each Backbone template includes each field present in an instance
  1033. *
  1034. * @since 3.0
  1035. */
  1036. function admin_footer()
  1037. {
  1038. if( !empty( $this->instances_for_post_type ) )
  1039. { ?>
  1040. <script type="text/javascript">
  1041. var ATTACHMENTS_VIEWS = {};
  1042. </script>
  1043. <?php
  1044. foreach( $this->instances_for_post_type as $instance ) : ?>
  1045. <script type="text/template" id="tmpl-attachments-<?php echo $instance; ?>">
  1046. <?php $this->create_attachment( $instance ); ?>
  1047. </script>
  1048. <?php endforeach;
  1049. }
  1050. }
  1051. /**
  1052. * Saves our Attachments metadata when the post is saved
  1053. *
  1054. * @since 3.0
  1055. */
  1056. function save( $post_id )
  1057. {
  1058. // is the user logged in?
  1059. if( !is_user_logged_in() )
  1060. return $post_id;
  1061. // is the nonce set?
  1062. if( !isset( $_POST['attachments_nonce'] ) )
  1063. return $post_id;
  1064. // is the nonce valid?
  1065. if( !wp_verify_nonce( $_POST['attachments_nonce'], 'attachments_save' ) )
  1066. return $post_id;
  1067. // can this user edit this post?
  1068. if( !current_user_can( 'edit_post', $post_id ) )
  1069. return $post_id;
  1070. // passed authentication, proceed with save
  1071. // if the user deleted all Attachments we won't have our key
  1072. $attachments_meta = isset( $_POST['attachments'] ) ? $_POST['attachments'] : array();
  1073. // final data store
  1074. $attachments = array();
  1075. // loop through each submitted instance
  1076. foreach( $attachments_meta as $instance => $instance_attachments )
  1077. {
  1078. // loop through each Attachment of this instance
  1079. foreach( $instance_attachments as $key => $attachment )
  1080. {
  1081. // since we're using JSON for storage in the database, we need
  1082. // to make sure that characters are encoded that would otherwise
  1083. // break the JSON
  1084. if( isset( $attachment['fields'] ) && is_array( $attachment['fields'] ) )
  1085. {
  1086. foreach( $attachment['fields'] as $key => $field_value )
  1087. {
  1088. // take care of our returns
  1089. $field_value = str_replace( "\r\n", "\n", $field_value );
  1090. $field_value = str_replace( "\r", "\n", $field_value );
  1091. // we dont want to strip out our newlines so we're going to flag them
  1092. $field_value = str_replace("\n", "%%ATTACHMENTS_NEWLINE%%", $field_value );
  1093. // slashes were already added so we're going to strip them
  1094. $field_value = stripslashes_deep( $field_value );
  1095. // put back our newlines
  1096. $field_value = str_replace("%%ATTACHMENTS_NEWLINE%%", "\\n", $field_value );
  1097. // encode the whole thing
  1098. $field_value = $this->encode_field_value( $field_value );
  1099. // encode things properly
  1100. $attachment['fields'][$key] = $field_value;
  1101. }
  1102. }
  1103. $attachments[$instance][] = $attachment;
  1104. }
  1105. }
  1106. if( !empty( $attachments ) )
  1107. {
  1108. // we're going to store JSON (JSON_UNESCAPED_UNICODE is PHP 5.4+)
  1109. $attachments = version_compare( PHP_VERSION, '5.4.0', '>=' ) ? json_encode( $attachments, JSON_UNESCAPED_UNICODE ) : json_encode( $attachments );
  1110. // we're going to wipe out any existing Attachments meta (because we'll put it back)
  1111. update_post_meta( $post_id, $this->meta_key, $attachments );
  1112. }
  1113. else
  1114. {
  1115. // there are no attachments so we'll clean up the record
  1116. delete_post_meta( $post_id, $this->meta_key );
  1117. }
  1118. return $post_id;
  1119. }
  1120. /**
  1121. * Recursive function to encode a field type before saving
  1122. * @param mixed $field_value The field value
  1123. * @return mixed The encoded field value
  1124. *
  1125. * @since 3.3
  1126. */
  1127. function encode_field_value( $field_value = null )
  1128. {
  1129. if( is_null( $field_value ) )
  1130. return;
  1131. if( is_object( $field_value ) )
  1132. {
  1133. $input = get_object_vars( $field_value );
  1134. foreach( $input as $key => $val )
  1135. $field_value[$key] = $this->encode_field_value( $val );
  1136. $field_value = (object) $field_value;
  1137. }
  1138. elseif( is_array( $field_value ) )
  1139. {
  1140. foreach( $field_value as $key => $val )
  1141. $field_value[$key] = $this->encode_field_value( $val );
  1142. }
  1143. else
  1144. $field_value = htmlentities( $field_value, ENT_QUOTES, 'UTF-8' );
  1145. return $field_value;
  1146. }
  1147. /**
  1148. * Retrieves all Attachments for the submitted instance and post ID
  1149. *
  1150. * @since 3.0
  1151. */
  1152. function get_attachments( $instance = null, $post_id = null )
  1153. {
  1154. $post_id = $this->determine_post_id( $post_id );
  1155. if( !$post_id )
  1156. return false;
  1157. $attachments = array();
  1158. $attachments_raw = $this->get_attachments_metadata( $post_id );
  1159. // we need to decode the fields (that were encoded during save) and run them through
  1160. // their format_value_for_input as defined in it's class
  1161. if( !is_null( $instance ) && is_string( $instance ) && isset( $attachments_raw->$instance ) )
  1162. {
  1163. foreach( $attachments_raw->$instance as $attachment )
  1164. $attachments[] = $this->process_attachment( $attachment, $instance );
  1165. }
  1166. elseif( is_null( $instance ) )
  1167. {
  1168. // return them all, regardless of instance
  1169. if( is_array( $attachments_raw ) && count( $attachments_raw ) )
  1170. foreach( $attachments_raw as $instance => $attachments_unprocessed )
  1171. foreach( $attachments_unprocessed as $unprocessed_attachment )
  1172. $attachments[] = $this->process_attachment( $unprocessed_attachment, $instance );
  1173. }
  1174. // tack on the post ID for each attachment
  1175. for( $i = 0; $i < count( $attachments ); $i++ )
  1176. $attachments[$i]->post_id = $post_id;
  1177. // we don't want the filter to run on the admin side of things
  1178. if( !is_admin() )
  1179. $attachments = apply_filters( "attachments_get_{$instance}", $attachments );
  1180. return $attachments;
  1181. }
  1182. /**
  1183. * Retrieves the post_meta record (saved as JSON) and processes it
  1184. * @param int $post_id The post ID
  1185. * @return object Attachments for the post
  1186. *
  1187. * @since 3.3
  1188. */
  1189. function get_attachments_metadata( $post_id )
  1190. {
  1191. $post_id = intval( $post_id );
  1192. // grab our JSON and decode it
  1193. $attachments_json = get_post_meta( $post_id, $this->meta_key, true );
  1194. $attachments_raw = is_string( $attachments_json ) ? json_decode( $attachments_json ) : false;
  1195. return $attachments_raw;
  1196. }
  1197. /**
  1198. * Determines a proper post ID based on whether one was passed, the global $post object, or a GET variable
  1199. * @param int $post_id Desired post ID
  1200. * @return mixed The understood post ID
  1201. */
  1202. function determine_post_id( $post_id = null )
  1203. {
  1204. global $post;
  1205. // if a post id was passed, we'll use it
  1206. if( !is_null( $post_id ) )
  1207. {
  1208. $post_id = intval( $post_id );
  1209. }
  1210. elseif( is_null( $post_id ) && is_object( $post ) && isset( $post->ID ) )
  1211. {
  1212. $post_id = $post->ID;
  1213. }
  1214. elseif( isset( $_GET['post'] ) )
  1215. {
  1216. $post_id = intval( $_GET['post'] );
  1217. }
  1218. else
  1219. {
  1220. // no post ID, nothing to do...
  1221. $post_id = false;
  1222. }
  1223. return $post_id;
  1224. }
  1225. /**
  1226. * Processes Attachment (including fields) in preparation for return
  1227. * @param object $attachment An Attachment object as it was stored as post metadata
  1228. * @return object Post-processed Attachment data
  1229. *
  1230. * @since 3.3
  1231. */
  1232. function process_attachment( $attachment, $instance )
  1233. {
  1234. if( !is_object( $attachment ) || !is_string( $instance ) )
  1235. return $attachment;
  1236. if( is_object( $attachment->fields ) )
  1237. {
  1238. foreach( $attachment->fields as $key => $value )
  1239. {
  1240. // loop through the instance fields to get the type
  1241. if( isset( $this->instances[$instance]['fields'] ) )
  1242. {
  1243. $type = '';
  1244. foreach( $this->instances[$instance]['fields'] as $field )
  1245. {
  1246. if( isset( $field['name'] ) && $field['name'] == $key )
  1247. {
  1248. $type = isset( $field['type'] ) ? $field['type'] : false;
  1249. break;
  1250. }
  1251. }
  1252. if( isset( $this->fields[$type] ) )
  1253. {
  1254. // we need to decode the html entities that were encoded for the save
  1255. $attachment->fields->$key = $this->decode_field_value( $attachment->fields->$key );
  1256. }
  1257. else
  1258. {
  1259. // the type doesn't exist
  1260. $attachment->fields->$key = false;
  1261. }
  1262. }
  1263. else
  1264. {
  1265. // this was a theme file request, just grab it
  1266. $attachment->fields->$key = $this->decode_field_value( $attachment->fields->$key );
  1267. }
  1268. }
  1269. }
  1270. return $attachment;
  1271. }
  1272. /**
  1273. * Recursive function to decode a field value when retrieving it
  1274. * @param mixed $field_value The field value
  1275. * @return mixed The encoded field value
  1276. *
  1277. * @since 3.3
  1278. */
  1279. function decode_field_value( $field_value = null )
  1280. {
  1281. if( is_null( $field_value ) )
  1282. return;
  1283. if( is_object( $field_value ) )
  1284. {
  1285. $input = get_object_vars( $field_value );
  1286. foreach( $input as $key => $val )
  1287. $field_value[$key] = $this->decode_field_value( $val );
  1288. $field_value = (object) $field_value;
  1289. }
  1290. elseif( is_array( $field_value ) )
  1291. {
  1292. foreach( $field_value as $key => $val )
  1293. $field_value[$key] = $this->decode_field_value( $val );
  1294. }
  1295. else
  1296. $field_value = html_entity_decode( $field_value, ENT_QUOTES, 'UTF-8' );
  1297. return $field_value;
  1298. }
  1299. /**
  1300. * Determines whether or not there is 'active' legacy data the user may not know about
  1301. *
  1302. * @since 3.0
  1303. */
  1304. function has_outstanding_legacy_data()
  1305. {
  1306. if(
  1307. // migration has not taken place and we have legacy data
  1308. ( false == get_option( 'attachments_migrated' ) && !empty( $this->legacy ) )
  1309. &&
  1310. // we're not intentionally ignoring the message
  1311. ( false == get_option( 'attachments_ignore_migration' ) )
  1312. )
  1313. {
  1314. return true;
  1315. }
  1316. else
  1317. {
  1318. return false;
  1319. }
  1320. }
  1321. /**
  1322. * Outputs a WordPress message to notify user of legacy data
  1323. *
  1324. * @since 3.0
  1325. */
  1326. function admin_notice()
  1327. {
  1328. if( $this->has_outstanding_legacy_data() && ( isset( $_GET['page'] ) && $_GET['page'] !== 'attachments' || !isset( $_GET['page'] ) ) ) : ?>
  1329. <div class="message updated" id="message">
  1330. <p><?php _e( '<strong>Attachments has detected legacy Attachments data.</strong> A lot has changed since Attachments 1.x.' ,'attachments' ); ?> <a href="options-general.php?page=attachments&amp;overview=1"><?php _e( 'Find out more', 'attachments' ); ?>.</a></p>
  1331. </div>
  1332. <?php endif;
  1333. }
  1334. /**
  1335. * Implements our WordPress pointer if necessary
  1336. *
  1337. * @since 3.0
  1338. */
  1339. function admin_pointer( $hook_suffix )
  1340. {
  1341. // Assume pointer shouldn't be shown
  1342. $enqueue_pointer_script_style = false;
  1343. // Get array list of dismissed pointers for current user and convert it to array
  1344. $dismissed_pointers = explode( ',', get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) );
  1345. // Check if our pointer is not among dismissed ones
  1346. if( $this->legacy && !in_array( 'attachments_legacy', $dismissed_pointers ) ) {
  1347. $enqueue_pointer_script_style = true;
  1348. // Add footer scripts using callback function
  1349. add_action( 'admin_print_footer_scripts', array( $this, 'pointer_legacy' ) );
  1350. }
  1351. // Enqueue pointer CSS and JS files, if needed
  1352. if( $enqueue_pointer_script_style ) {
  1353. wp_enqueue_style( 'wp-pointer' );
  1354. wp_enqueue_script( 'wp-pointer' );
  1355. }
  1356. }
  1357. /**
  1358. * Pointer that calls attention to legacy data
  1359. *
  1360. * @since 3.0
  1361. */
  1362. function pointer_legacy()
  1363. {
  1364. $pointer_content = "<h3>". __( esc_html( 'Attachments 3.0 brings big changes!' ), 'attachments' ) ."</h3>";
  1365. $pointer_content .= "<p>". __( esc_html( 'It is very important that you take a few minutes to see what has been updated. The changes will affect your themes/plugins.' ), 'attachments' ) ."</p>";
  1366. ?>
  1367. <script type="text/javascript">
  1368. jQuery(document).ready( function($) {
  1369. $('#message a').pointer({
  1370. content:'<?php echo $pointer_content; ?>',
  1371. position:{
  1372. edge:'top',
  1373. align:'center'
  1374. },
  1375. pointerWidth:350,
  1376. close:function() {
  1377. $.post( ajaxurl, {
  1378. pointer: 'attachments_legacy',
  1379. action: 'dismiss-wp-pointer'
  1380. });
  1381. }
  1382. }).pointer('open');
  1383. });
  1384. </script>
  1385. <?php
  1386. }
  1387. /**
  1388. * Callback to implement our Settings page
  1389. *
  1390. * @since 3.0
  1391. */
  1392. function admin_page()
  1393. {
  1394. add_options_page( 'Settings', __( 'Attachments', 'attachments' ), 'manage_options', 'attachments', array( $this, 'options_page' ) );
  1395. }
  1396. /**
  1397. * Callback to output our Settings page markup
  1398. *
  1399. * @since 3.0
  1400. */
  1401. function options_page()
  1402. {
  1403. include_once( ATTACHMENTS_DIR . '/views/options.php' );
  1404. }
  1405. }
  1406. endif; // class_exists check
  1407. new Attachments();