PageRenderTime 84ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/custom_metadata.php

https://github.com/imranglobal/custom-metadata
PHP | 1432 lines | 1000 code | 292 blank | 140 comment | 175 complexity | 4ab9e737758f0067040c2b83a47b22ea MD5 | raw file
  1. <?php
  2. /*
  3. Plugin Name: Custom Metadata Manager
  4. Plugin URI: http://wordpress.org/extend/plugins/custom-metadata/
  5. Description: An easy way to add custom fields to your object types (post, pages, custom post types, users)
  6. Author: Automattic, Stresslimit & Contributors
  7. Version: 0.8-dev
  8. Author URI: https://github.com/Automattic/custom-metadata/
  9. Copyright 2010-2013 The Contributors
  10. GNU General Public License, Free Software Foundation <http://creativecommons.org/licenses/GPL/2.0/>
  11. This program is free software; you can redistribute it and/or modify
  12. it under the terms of the GNU General Public License as published by
  13. the Free Software Foundation; either version 2 of the License, or
  14. (at your option) any later version.
  15. This program is distributed in the hope that it will be useful,
  16. but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. GNU General Public License for more details.
  19. You should have received a copy of the GNU General Public License
  20. along with this program; if not, write to the Free Software
  21. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  22. */
  23. /**
  24. * set this to true in your wp-config.php file to enable debug/test mode
  25. */
  26. if ( ! defined( 'CUSTOM_METADATA_MANAGER_DEBUG' ) )
  27. define( 'CUSTOM_METADATA_MANAGER_DEBUG', false );
  28. if ( CUSTOM_METADATA_MANAGER_DEBUG )
  29. include_once 'custom_metadata_examples.php';
  30. class custom_metadata_manager {
  31. var $errors = array();
  32. var $metadata = array();
  33. var $_non_post_types = array( 'user', 'comment' );
  34. // Object types that come "built-in" with WordPress
  35. var $_builtin_object_types = array( 'post', 'page', 'user', 'comment' );
  36. // Column filter names
  37. var $_column_types = array( 'posts', 'pages', 'users', 'comments' );
  38. // field types
  39. var $_field_types = array( 'text', 'textarea', 'password', 'number', 'email', 'telephone', 'checkbox', 'radio', 'select', 'multi_select', 'upload', 'wysiwyg', 'datepicker', 'datetimepicker', 'timepicker', 'colorpicker', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_checkbox', 'link', 'thumbnail' );
  40. // field types that are cloneable
  41. var $_cloneable_field_types = array( 'text', 'textarea', 'upload', 'password', 'number', 'email', 'tel' );
  42. // field types that support a default value
  43. var $_field_types_that_support_default_value = array( 'text', 'textarea', 'password', 'number', 'email', 'telephone', 'upload', 'wysiwyg', 'datepicker', 'datetimepicker', 'timepicker', 'link' );
  44. // field types that support the placeholder attribute
  45. var $_field_types_that_support_placeholder = array( 'text', 'textarea', 'password', 'number', 'email', 'tel', 'upload', 'datepicker', 'datetimepicker', 'timepicker', 'link' );
  46. // field types that are read only by default
  47. var $_field_types_that_are_read_only = array( 'upload', 'link', 'datepicker', 'datetimepicker', 'timepicker' );
  48. // field types that support being part of a multifield group
  49. // @todo: workarounds needed for other field types
  50. var $_field_types_that_support_multifield = array( 'text', 'textarea', 'password', 'number', 'email', 'tel', 'select' );
  51. // taxonomy types
  52. var $_taxonomy_fields = array( 'taxonomy_select', 'taxonomy_radio', 'taxonomy_checkbox', 'taxonomy_multi_select' );
  53. // filed types that are saved as multiples but not cloneable
  54. var $_multiple_not_cloneable = array( 'taxonomy_checkbox' );
  55. // fields that always save as an array
  56. var $_always_multiple_fields = array( 'taxonomy_checkbox', 'multi_select', 'taxonomy_multi_select' );
  57. // Object types whose columns are generated through apply_filters instead of do_action
  58. var $_column_filter_object_types = array( 'user' );
  59. // Whitelisted pages that get stylesheets and scripts
  60. var $_pages_whitelist = array( 'edit.php', 'post.php', 'post-new.php', 'users.php', 'profile.php', 'user-edit.php', 'edit-comments.php', 'comment.php' );
  61. // the default args used for the wp_editor function
  62. var $default_editor_args = array();
  63. // singleton instance
  64. private static $instance;
  65. public static function instance() {
  66. if ( isset( self::$instance ) )
  67. return self::$instance;
  68. self::$instance = new custom_metadata_manager;
  69. self::$instance->run_initial_hooks();
  70. return self::$instance;
  71. }
  72. // do nothing on construct
  73. function __construct() {}
  74. function run_initial_hooks() {
  75. add_action( 'admin_init', array( $this, 'admin_init' ), 1000, 0 );
  76. }
  77. function admin_init() {
  78. global $pagenow;
  79. // filter our vars
  80. $this->_non_post_types = apply_filters( 'custom_metadata_manager_non_post_types', $this->_non_post_types );
  81. $this->_builtin_object_types = apply_filters( 'custom_metadata_manager_builtin_object_types', $this->_builtin_object_types );
  82. $this->_column_types = apply_filters( 'custom_metadata_manager_column_types', $this->_column_types );
  83. $this->_field_types = apply_filters( 'custom_metadata_manager_field_types', $this->_field_types );
  84. $this->_cloneable_field_types = apply_filters( 'custom_metadata_manager_cloneable_field_types', $this->_cloneable_field_types );
  85. $this->_field_types_that_support_default_value = apply_filters( 'custom_metadata_manager_field_types_that_support_default_value', $this->_field_types_that_support_default_value );
  86. $this->_field_types_that_support_placeholder = apply_filters( 'custom_metadata_manager_field_types_that_support_placeholder', $this->_field_types_that_support_placeholder );
  87. $this->_field_types_that_are_read_only = apply_filters( 'custom_metadata_manager_field_types_that_are_read_only', $this->_field_types_that_are_read_only );
  88. $this->_field_types_that_support_multifield = apply_filters( 'custom_metadata_manager_field_types_that_support_multifield', $this->_field_types_that_support_multifield );
  89. $this->_taxonomy_fields = apply_filters( 'custom_metadata_manager_cloneable_field_types', $this->_taxonomy_fields );
  90. $this->_column_filter_object_types = apply_filters( 'custom_metadata_manager_column_filter_object_types', $this->_column_filter_object_types );
  91. $this->_pages_whitelist = apply_filters( 'custom_metadata_manager_pages_whitelist', $this->_pages_whitelist );
  92. $this->default_editor_args = apply_filters( 'custom_metadata_manager_default_editor_args', $this->default_editor_args );
  93. define( 'CUSTOM_METADATA_MANAGER_SELECT2_VERSION', '3.2' ); // version for included select2.js
  94. define( 'CUSTOM_METADATA_MANAGER_TIMEPICKER_VERSION', '1.2' ); // version for included timepicker
  95. define( 'CUSTOM_METADATA_MANAGER_VERSION', '0.8-dev' );
  96. define( 'CUSTOM_METADATA_MANAGER_URL' , apply_filters( 'custom_metadata_manager_url', trailingslashit( plugins_url( '', __FILE__ ) ) ) );
  97. $this->init_object_types();
  98. // Hook into load to initialize custom columns
  99. if ( in_array( $pagenow, $this->_pages_whitelist ) ) {
  100. add_action( 'load-' . $pagenow, array( $this, 'init_metadata' ) );
  101. }
  102. // Hook into admin_notices to show errors
  103. if ( current_user_can( 'manage_options' ) )
  104. add_action( 'admin_notices', array( $this, '_display_registration_errors' ) );
  105. do_action( 'custom_metadata_manager_init' );
  106. do_action( 'custom_metadata_manager_admin_init' );
  107. }
  108. function init_object_types() {
  109. foreach ( array_merge( get_post_types(), $this->_builtin_object_types ) as $object_type )
  110. $this->metadata[$object_type] = array();
  111. }
  112. function init_metadata() {
  113. $object_type = $this->_get_object_type_context();
  114. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
  115. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) );
  116. $this->init_columns();
  117. // Handle actions related to users
  118. if ( $object_type == 'user' ) {
  119. global $user_id;
  120. if ( empty( $user_id ) )
  121. $user_id = get_current_user_id();
  122. // Editing another user's profile
  123. add_action( 'edit_user_profile', array( $this, 'add_user_metadata_groups' ) );
  124. add_action( 'edit_user_profile_update', array( $this, 'save_user_metadata' ) );
  125. // Allow user-editable fields on "Your Profile"
  126. add_action( 'show_user_profile', array( $this, 'add_user_metadata_groups' ) );
  127. add_action( 'personal_options_update', array( $this, 'save_user_metadata' ) );
  128. } else {
  129. // Hook in to metaboxes
  130. add_action( 'add_meta_boxes', array( $this, "add_post_metadata_groups" ) );
  131. // Hook in to save
  132. add_action( 'save_post', array( $this, 'save_post_metadata' ) );
  133. add_action( 'edit_comment', array( $this, 'save_comment_metadata' ) );
  134. }
  135. do_action( 'custom_metadata_manager_init_metadata', $object_type );
  136. add_action( 'admin_footer', array( $this, '_display_wp_link_dialog' ) );
  137. }
  138. function init_columns() {
  139. $object_type = $this->_get_object_type_context();
  140. // This is not really that clean, but it works. Damn inconsistencies!
  141. if ( post_type_exists( $object_type ) ) {
  142. $column_header_name = sprintf( '%s_posts', $object_type );
  143. $column_content_name = ( 'page' != $object_type ) ? 'posts' : 'pages';
  144. } elseif ( $object_type == 'comment' ) {
  145. $column_header_name = 'edit-comments';
  146. $column_content_name = 'comments';
  147. } else {
  148. // users
  149. $column_header_name = $column_content_name = $object_type . 's';
  150. }
  151. // Hook into Column Headers
  152. add_filter( "manage_{$column_header_name}_columns", array( $this, 'add_metadata_column_headers' ) );
  153. // User and Posts have different functions
  154. $custom_column_content_function = array( $this, "add_{$object_type}_metadata_column_content" );
  155. if ( ! is_callable( $custom_column_content_function ) )
  156. $custom_column_content_function = array( $this, 'add_metadata_column_content' );
  157. // Hook into Column Content. Users get filtered, others get actioned.
  158. if ( ! in_array( $object_type, $this->_column_filter_object_types ) )
  159. add_action( "manage_{$column_content_name}_custom_column", $custom_column_content_function, 10, 3 );
  160. else
  161. add_filter( "manage_{$column_content_name}_custom_column", $custom_column_content_function, 10, 3 );
  162. }
  163. function enqueue_scripts() {
  164. wp_enqueue_media();
  165. wp_enqueue_script( 'wplink' );
  166. wp_enqueue_script( 'wpdialogs-popup' );
  167. wp_enqueue_style( 'wp-jquery-ui-dialog' );
  168. wp_enqueue_script( 'select2', apply_filters( 'custom_metadata_manager_select2_js', CUSTOM_METADATA_MANAGER_URL .'js/select2.min.js' ), array( 'jquery' ), CUSTOM_METADATA_MANAGER_SELECT2_VERSION, true );
  169. wp_enqueue_script( 'timepicker', apply_filters( 'custom_metadata_manager_select2_js', CUSTOM_METADATA_MANAGER_URL .'js/jquery-ui-timepicker.min.js' ), array( 'jquery', 'jquery-ui-datepicker' ), CUSTOM_METADATA_MANAGER_TIMEPICKER_VERSION, true );
  170. wp_enqueue_script( 'custom-metadata-manager-js', apply_filters( 'custom_metadata_manager_default_js', CUSTOM_METADATA_MANAGER_URL .'js/custom-metadata-manager.js' ), array( 'jquery', 'jquery-ui-datepicker', 'select2' ), CUSTOM_METADATA_MANAGER_VERSION, true );
  171. wp_enqueue_script( 'wp-color-picker' );
  172. }
  173. function enqueue_styles() {
  174. wp_enqueue_style( 'wp-jquery-ui-dialog' );
  175. wp_enqueue_style( 'editor-buttons' );
  176. wp_enqueue_style( 'custom-metadata-manager-css', apply_filters( 'custom_metadata_manager_default_css', CUSTOM_METADATA_MANAGER_URL .'css/custom-metadata-manager.css' ), array(), CUSTOM_METADATA_MANAGER_VERSION );
  177. wp_enqueue_style( 'jquery-ui-datepicker', apply_filters( 'custom_metadata_manager_jquery_ui_css', CUSTOM_METADATA_MANAGER_URL .'css/jquery-ui-smoothness.css' ), array(), CUSTOM_METADATA_MANAGER_VERSION );
  178. wp_enqueue_style( 'select2', apply_filters( 'custom_metadata_manager_select2_css', CUSTOM_METADATA_MANAGER_URL .'css/select2.css' ), array(), CUSTOM_METADATA_MANAGER_SELECT2_VERSION );
  179. wp_enqueue_style( 'wp-color-picker' );
  180. }
  181. function add_metadata_column_headers( $columns ) {
  182. $object_type = $this->_get_object_type_context();
  183. if ( $object_type ) {
  184. $fields = $this->get_fields_in_object_type( $object_type );
  185. foreach ( $fields as $field_slug => $field ) {
  186. if ( $this->is_field_addable_to_columns( $field_slug, $field ) ) {
  187. $columns[$field_slug] = is_string( $field->display_column ) ? $field->display_column : $field->label;
  188. }
  189. }
  190. }
  191. return $columns;
  192. }
  193. function add_user_metadata_column_content( $param, $name, $object_id ) {
  194. return $this->add_metadata_column_content( $name, $object_id, $param );
  195. }
  196. function add_metadata_column_content( $name, $object_id, $column_content = '' ) {
  197. $object_type = $this->_get_object_type_context();
  198. $field_slug = $name;
  199. if ( $this->is_registered_object_type( $object_type ) && $this->is_registered_field( $field_slug, null, $object_type ) ) {
  200. $field = $this->get_field( $field_slug, null, $object_type );
  201. $column_content = $this->_metadata_column_content( $field_slug, $field, $object_type, $object_id );
  202. }
  203. if ( $column_content && ! in_array( $object_type, $this->_column_filter_object_types ) )
  204. echo $column_content;
  205. else
  206. return $column_content;
  207. }
  208. function add_metadata_field( $field_slug, $object_types = array( 'post' ), $args = array() ) {
  209. $defaults = array(
  210. 'group' => '', // To which meta_box the field should be added
  211. 'multifield' => false, // which multifield does this field belong to, if any
  212. 'field_type' => 'text', // The type of field; possibly values: text, checkbox, radio, select, image
  213. 'label' => $field_slug, // Label for the field
  214. 'slug' => $field_slug, // Slug for the field
  215. 'description' => '', // Description of the field, displayed below the input
  216. 'values' => array(), // values for select, checkbox, radio buttons
  217. 'default_value' => '', // default value
  218. 'placeholder' => '',
  219. 'display_callback' => '', // function to custom render the input
  220. 'sanitize_callback' => '',
  221. 'display_column' => false, // Add the field to the columns when viewing all posts
  222. 'display_column_callback' => '',
  223. 'add_to_quick_edit' => false, // (post only) Add the field to Quick edit
  224. 'required_cap' => false, // the cap required to view and edit the field
  225. 'multiple' => false, // can the field be duplicated with a click of a button
  226. 'readonly' => false, // makes the field be readonly
  227. 'select2' => false, // applies select2.js (work on select and multi select field types)
  228. 'min' => false, // a minimum value (for number field only)
  229. 'max' => false, // a maximum value (for number field only)
  230. 'preview' => 'thumbnail', // type of preview in admin area ( for thumbnail field type only)
  231. 'upload_modal_title' => __( 'Choose a file', 'custom-metadata' ), // upload modal title (for upload field only)
  232. 'upload_modal_button_text' => __( 'Select this file', 'custom-metadata' ), // upload modal button text (for upload field only)
  233. 'upload_clear_button_text' => __( 'Clear', 'custom-metadata' ), // upload clear field text (for upload field only)
  234. 'link_modal_button_text' => __( 'Select', 'custom-metadata' ), // link field button text
  235. );
  236. // upload field is readonly by default (can be set explicitly to false though)
  237. if ( ! empty( $args['field_type'] ) && in_array( $args['field_type'], $this->_field_types_that_are_read_only ) )
  238. $defaults['readonly'] = true;
  239. // `chosen` arg is the same as `select2` arg
  240. if ( isset( $args['chosen'] ) ) {
  241. $args['select2'] = $args['chosen'];
  242. unset( $args['chosen'] );
  243. }
  244. // Merge defaults with args
  245. $field = wp_parse_args( $args, $defaults );
  246. $field = (object) $field;
  247. // Sanitize slug
  248. $field_slug = sanitize_key( $field_slug );
  249. $group_slug = sanitize_key( $field->group );
  250. // Check to see if the user should see this field
  251. if ( ! empty( $field->required_cap ) && ! current_user_can( $field->required_cap ) )
  252. return;
  253. $field = apply_filters( 'custom_metadata_manager_add_metadata_field', $field, $field_slug, $group_slug, $object_types );
  254. if ( ! $this->_validate_metadata_field( $field_slug, $field, $group_slug, $object_types ) )
  255. return;
  256. // $object_types = (array) $object_type;
  257. // if ( $field->multifield && $this->_multifield_exists_for_group_object( $field->multifield, $group_slug, array_shift( $object_types ) ) ) {
  258. // $this->add_field_to_multifield( $field_slug, $field, $group_slug, $object_types );
  259. // } else {
  260. // add to group
  261. $this->add_field_to_group( $field_slug, $field, $group_slug, $object_types );
  262. // }
  263. }
  264. function add_multifield( $slug, $object_types = array( 'post' ), $args = array() ) {
  265. $defaults = array(
  266. 'group' => '', // To which meta_box the multifield should be added
  267. 'label' => $slug, // Label for the multifield
  268. 'description' => '', // Description of the multifield, displayed below all the fields
  269. 'required_cap' => false, // the cap required to view and edit the multifield
  270. );
  271. // Merge defaults with args
  272. $multifield = wp_parse_args( $args, $defaults );
  273. $multifield['multifield'] = true; // force it
  274. $multifield = (object) $multifield;
  275. // Sanitize slug
  276. $slug = sanitize_key( $slug );
  277. $group_slug = sanitize_key( $multifield->group );
  278. // Check to see if the user should see this field
  279. if ( ! empty( $multifield->required_cap ) && ! current_user_can( $multifield->required_cap ) )
  280. return;
  281. $multifield = apply_filters( 'custom_metadata_manager_add_multifield', $multifield, $slug, $group_slug, $object_types );
  282. if ( ! $this->_validate_metadata_field( $slug, $multifield, $group_slug, $object_types ) )
  283. return;
  284. // Add to group
  285. $this->add_multifield_to_group( $slug, $multifield, $group_slug, $object_types );
  286. }
  287. function add_metadata_group( $group_slug, $object_types, $args = array() ) {
  288. $defaults = array(
  289. 'label' => $group_slug, // Label for the group
  290. 'description' => '', // Description of the group
  291. 'context' => 'normal', // (post only)
  292. 'priority' => 'default', // (post only)
  293. 'autosave' => false, // (post only) Should the group be saved in autosave?
  294. 'required_cap' => false, // the cap required to view and edit the group
  295. );
  296. // Merge defaults with args
  297. $group = wp_parse_args( $args, $defaults );
  298. $group = (object) $group;
  299. // Sanitize slug
  300. $group_slug = sanitize_key( $group_slug );
  301. $group = apply_filters( 'custom_metadata_manager_add_metadata_group', $group, $group_slug, $object_types );
  302. // Check to see if the user has caps to view/edit this group
  303. if ( ! empty( $group->required_cap ) && ! current_user_can( $group->required_cap ) )
  304. return;
  305. if ( !$this->_validate_metadata_group( $group_slug, $group, $object_types ) )
  306. return;
  307. $this->add_group_to_object_type( $group_slug, $group, $object_types );
  308. }
  309. function add_field_to_group( $field_slug, $field, $group_slug, $object_types ) {
  310. $object_types = (array) $object_types;
  311. foreach ( $object_types as $object_type ) {
  312. if ( ! $group_slug ) {
  313. $group_slug = sprintf( 'single-group-%1$s-%2$s', $object_type, $field_slug );
  314. }
  315. // If group doesn't exist, create group
  316. if ( ! $this->is_registered_group( $group_slug, $object_type ) ) {
  317. $this->add_metadata_group( $group_slug, $object_type, array( 'label' => ( ! empty( $field->label ) ) ? $field->label : $field_slug ) );
  318. $field->group = $group_slug;
  319. }
  320. $this->_push_field( $field_slug, $field, $group_slug, $object_type );
  321. }
  322. }
  323. function add_multifield_to_group( $slug, $multifield, $group_slug, $object_types ) {
  324. $object_types = (array) $object_types;
  325. foreach ( $object_types as $object_type ) {
  326. if ( ! $group_slug ) {
  327. $group_slug = sprintf( 'single-group-%1$s-%2$s', $object_type, $slug );
  328. }
  329. // If group doesn't exist, create group
  330. if ( ! $this->is_registered_group( $group_slug, $object_type ) ) {
  331. $this->add_metadata_group( $group_slug, $object_type, array( 'label' => ( ! empty( $multifield->label ) ) ? $multifield->label : $slug ) );
  332. $multifield->group = $group_slug;
  333. }
  334. $this->_push_multifield( $slug, $multifield, $group_slug, $object_type );
  335. }
  336. }
  337. function add_group_to_object_type( $group_slug, $group, $object_types ) {
  338. $object_types = (array) $object_types;
  339. foreach ( $object_types as $object_type ) {
  340. if ( ( $this->is_registered_object_type( $object_type ) && ! $this->is_group_in_object_type( $group_slug, $object_type ) ) ) {
  341. $group->fields = array();
  342. $this->_push_group( $group_slug, $group, $object_type );
  343. }
  344. }
  345. }
  346. function _validate_metadata_group( $group_slug, $group, $object_type ) {
  347. $valid = true;
  348. // TODO: only validate when DEBUG is on?
  349. /*
  350. if( ! $group_slug ) {
  351. } elseif ( $this->is_registered_group( $group_slug, $object_type ) ) {
  352. // TODO: check that it hasn't been registered already
  353. } elseif ( $this->is_restricted_group( $group_slug, $object_type ) ) {
  354. // TODO: check that it isn't restricted
  355. }
  356. */
  357. return $valid;
  358. }
  359. function _validate_metadata_field( $field_slug, $field, $group_slug, $object_types ) {
  360. // TODO: only validate when DEBUG is on?
  361. $valid = true;
  362. /*
  363. if( !$field_slug ) {
  364. // Check that
  365. $this->_add_registration_error( $field_slug, __( 'You entered an empty slug name for this field!', 'custom-metadata-manager' ) );
  366. $valid = false;
  367. } else if( $this->is_registered_field( $field_slug, $group_slug, $object_type ) ) {
  368. // does field name already exists
  369. $this->_add_registration_error( $field_slug, __( 'This field already exists. Check to see that you\'re not registering the field twice, or use a different slug.', 'custom-metadata-manager' ) );
  370. $valid = false;
  371. } else if( $this->is_restricted_field( $field_slug, $object_type ) ) {
  372. // is field restricted
  373. $this->_add_registration_error( $field_slug, __( 'This field is restricted. Please use a different slug.', 'custom-metadata-manager' ) );
  374. $valid = false;
  375. }
  376. // if display_callback not defined
  377. // show admin_notices error
  378. // show as text field (?)
  379. */
  380. // TODO: valid object_type?
  381. return $valid;
  382. }
  383. function _add_registration_error( $field_slug, $error_message ) {
  384. $this->errors[] = sprintf( __( '<strong>%1$s:</strong> %2$s', 'custom-metadata-manager' ), $field_slug, $error_message );
  385. }
  386. function add_post_metadata_groups() {
  387. global $post, $comment;
  388. $object_id = 0;
  389. if ( isset( $post ) ) {
  390. $object_id = $post->ID;
  391. } elseif ( isset( $comment ) ) {
  392. $object_id = $comment->comment_ID;
  393. }
  394. $object_type = $this->_get_object_type_context();
  395. $groups = $this->get_groups_in_object_type( $object_type );
  396. if ( $object_id && !empty( $groups ) ) {
  397. foreach ( $groups as $group_slug => $group ) {
  398. $this->add_post_metadata_group( $group_slug, $group, $object_type, $object_id );
  399. }
  400. }
  401. }
  402. function add_post_metadata_group( $group_slug, $group, $object_type, $object_id ) {
  403. $fields = $this->get_fields_in_group( $group_slug, $object_type );
  404. if ( ! empty( $fields ) && $this->is_thing_added_to_object( $group_slug, $group, $object_type, $object_id ) ) {
  405. add_meta_box( $group_slug, $group->label, array( $this, '_display_post_metadata_box' ), $object_type, $group->context, $group->priority, array( 'group' => $group, 'fields' => $fields ) );
  406. }
  407. }
  408. function add_user_metadata_groups() {
  409. global $user_id;
  410. if ( !$user_id ) return;
  411. $object_type = 'user';
  412. $groups = $this->get_groups_in_object_type( $object_type );
  413. if ( !empty( $groups ) ) {
  414. foreach ( $groups as $group_slug => $group ) {
  415. $this->add_user_metadata_group( $group_slug, $group, $object_type, $user_id );
  416. }
  417. }
  418. }
  419. function add_user_metadata_group( $group_slug, $group, $object_type, $user_id ) {
  420. $fields = $this->get_fields_in_group( $group_slug, $object_type );
  421. if ( ! empty( $fields ) && $this->is_thing_added_to_object( $group_slug, $group, $object_type, $user_id ) )
  422. $this->_display_user_metadata_box( $group_slug, $group, $object_type, $fields );
  423. }
  424. function _display_user_metadata_box( $group_slug, $group, $object_type, $fields ) {
  425. global $user_id;
  426. ?>
  427. <h3><?php echo $group->label; ?></h3>
  428. <table class="form-table user-metadata-group">
  429. <?php foreach ( $fields as $field_slug => $field ) : ?>
  430. <?php if ( $this->is_thing_added_to_object( $field_slug, $field, $object_type, $user_id ) ) : ?>
  431. <tr valign="top">
  432. <td scope="row">
  433. <?php $this->_display_metadata_field( $field_slug, $field, $object_type, $user_id ); ?>
  434. </td>
  435. </tr>
  436. <?php endif; ?>
  437. <?php endforeach; ?>
  438. </table>
  439. <?php
  440. $this->_display_group_nonce( $group_slug, $object_type );
  441. }
  442. function _display_post_metadata_box( $object, $meta_box ) {
  443. $group_slug = $meta_box['id'];
  444. $group = $meta_box['args']['group'];
  445. $fields = $meta_box['args']['fields'];
  446. $object_type = $this->_get_object_type_context();
  447. // I really don't like using variable variables, but this is the path of least resistence.
  448. if ( isset( $object->{$object_type . '_ID'} ) ) {
  449. $object_id = $object->{$object_type . '_ID'};
  450. } elseif ( isset( $object->ID ) ) {
  451. $object_id = $object->ID;
  452. } else {
  453. _e( 'Uh oh, something went wrong!', 'custom-metadata-manager' );
  454. return;
  455. }
  456. $this->_display_group_description( $group );
  457. foreach ( $fields as $field_slug => $field ) {
  458. if ( $this->is_thing_added_to_object( $field_slug, $field, $object_type, $object_id ) ) {
  459. if ( $this->_is_multifield( $field_slug ) ) {
  460. $this->_display_metadata_multifield( $field_slug, $field, $object_type, $object_id );
  461. } elseif ( empty( $field->multifield ) ) {
  462. $this->_display_metadata_field( $field_slug, $field, $object_type, $object_id );
  463. }
  464. }
  465. }
  466. // Each group gets its own nonce
  467. $this->_display_group_nonce( $group_slug, $object_type );
  468. }
  469. function _display_group_description( $group ) {
  470. if ( ! empty( $group->description ) )
  471. printf( '<div class="custom-metadata-group-description description">%s</div>', $group->description );
  472. }
  473. function _display_group_nonce( $group_slug, $object_type ) {
  474. $nonce_key = $this->build_nonce_key( $group_slug, $object_type );
  475. wp_nonce_field( 'save-metadata', $nonce_key, false );
  476. }
  477. function verify_group_nonce( $group_slug, $object_type ) {
  478. $nonce_key = $this->build_nonce_key( $group_slug, $object_type );
  479. if ( isset( $_POST[$nonce_key] ) )
  480. return wp_verify_nonce( $_POST[$nonce_key], 'save-metadata' );
  481. else
  482. return false;
  483. }
  484. function build_nonce_key( $group_slug, $object_type ) {
  485. return sprintf( 'metadata-%1$s-%2$s', $object_type, $group_slug );
  486. }
  487. function save_user_metadata( $user_id ) {
  488. $object_type = 'user';
  489. $groups = $this->get_groups_in_object_type( $object_type );
  490. foreach ( $groups as $group_slug => $group ) {
  491. $this->save_metadata_group( $group_slug, $group, $object_type, $user_id );
  492. }
  493. }
  494. function save_post_metadata( $post_id ) {
  495. $post_type = $this->_get_object_type_context();
  496. $groups = $this->get_groups_in_object_type( $post_type );
  497. foreach ( $groups as $group_slug => $group ) {
  498. // TODO: Allow hook into autosave
  499. if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE && !$group->autosave )
  500. return $post_id;
  501. $this->save_metadata_group( $group_slug, $group, $post_type, $post_id );
  502. }
  503. }
  504. function save_comment_metadata( $comment_id ) {
  505. $object_type = 'comment';
  506. $groups = $this->get_groups_in_object_type( $object_type );
  507. foreach ( $groups as $group_slug => $group ) {
  508. $this->save_metadata_group( $group_slug, $group, $object_type, $comment_id );
  509. }
  510. }
  511. function save_metadata_group( $group_slug, $group, $object_type, $object_id ) {
  512. if ( !$this->verify_group_nonce( $group_slug, $object_type ) ) {
  513. return $object_id;
  514. }
  515. $fields = $this->get_fields_in_group( $group_slug, $object_type );
  516. foreach ( $fields as $field_slug => $field ) {
  517. if ( true === $field->multifield ) {
  518. $this->save_metadata_multifield( $field_slug, $field, $object_type, $object_id );
  519. } elseif ( ! $field->multifield ) {
  520. $this->save_metadata_field( $field_slug, $field, $object_type, $object_id );
  521. }
  522. }
  523. }
  524. function save_metadata_multifield( $slug, $multifield, $object_type, $object_id ) {
  525. if ( isset( $_POST[$slug] ) ) {
  526. $multifield_value = array();
  527. $groupings = $_POST[$slug];
  528. $fields = $this->get_fields_in_multifield( $multifield->group, $slug, $object_type );
  529. foreach ( $groupings as $grouping ) {
  530. $grouping_values = array();
  531. foreach ( $fields as $field_slug => $field ) {
  532. if ( ! empty( $grouping[$field_slug] ) ) {
  533. $grouping_values[$field_slug] = $this->_sanitize_field_value( $field_slug, $field, $object_type, $object_id, $grouping[$field_slug] );
  534. } else {
  535. $grouping_values[$field_slug] = '';
  536. }
  537. }
  538. $multifield_value[] = $grouping_values;
  539. }
  540. $slug = sanitize_key( $slug );
  541. if ( ! in_array( $object_type, $this->_non_post_types ) )
  542. $object_type = 'post';
  543. update_metadata( $object_type, $object_id, $slug, $multifield_value );
  544. } else {
  545. $slug = sanitize_key( $slug );
  546. if ( ! in_array( $object_type, $this->_non_post_types ) )
  547. $object_type = 'post';
  548. delete_metadata( $object_type, $object_id, $slug );
  549. }
  550. }
  551. function save_metadata_field( $field_slug, $field, $object_type, $object_id ) {
  552. if ( isset( $_POST[$field_slug] ) ) {
  553. $value = $this->_sanitize_field_value( $field_slug, $field, $object_type, $object_id, $_POST[$field_slug] );
  554. $this->_save_field_value( $field_slug, $field, $object_type, $object_id, $value );
  555. // save the attachment ID of the upload field as well
  556. if ( $field->field_type == 'upload' && isset( $_POST[$field_slug . '_attachment_id'] ) )
  557. $this->_save_field_value( $field_slug . '_attachment_id', $field, $object_type, $object_id, absint( $_POST[$field_slug . '_attachment_id'] ) );
  558. } else {
  559. $this->_delete_field_value( $field_slug, $field, $object_type, $object_id );
  560. // delete the attachment ID of the upload field as well
  561. if ( $field->field_type == 'upload' && isset( $_POST[$field_slug . '_attachment_id'] ) )
  562. $this->_delete_field_value( $field_slug . '_attachment_id', $field, $object_type, $object_id );
  563. }
  564. }
  565. function get_metadata_mulitifield_value( $slug, $multifield, $object_type, $object_id ) {
  566. return $this->_get_field_value( $slug, $multifield, $object_type, $object_id, true );
  567. }
  568. function get_metadata_field_value( $field_slug, $field, $object_type, $object_id ) {
  569. return $this->_get_field_value( $field_slug, $field, $object_type, $object_id );
  570. }
  571. function is_registered_object_type( $object_type ) {
  572. return array_key_exists( $object_type, $this->metadata ) /*&& is_array( $this->metadata[$object_type] )*/;
  573. }
  574. function is_registered_group( $group_slug, $object_type ) {
  575. return $this->is_registered_object_type( $object_type ) && array_key_exists( $group_slug, $this->get_groups_in_object_type( $object_type ) );
  576. }
  577. function is_registered_field( $field_slug, $group_slug = '', $object_type ) {
  578. if ( $group_slug )
  579. return $this->is_registered_group( $group_slug, $object_type ) && array_key_exists( $field_slug, $this->get_fields_in_group( $group_slug, $object_type ) );
  580. else
  581. return array_key_exists( $field_slug, $this->get_fields_in_object_type( $object_type ) );
  582. }
  583. function is_field_in_group( $field_slug, $group_slug, $object_type ) {
  584. return in_array( $field_slug, $this->get_fields_in_group( $group_slug, $object_type ) );
  585. }
  586. function is_group_in_object_type( $group_slug, $object_type ) {
  587. return array_key_exists( $group_slug, $this->get_groups_in_object_type( $object_type ) );
  588. }
  589. function is_field_addable_to_columns( $field_slug, $field ) {
  590. return is_string( $field->display_column ) || ( is_bool( $field->display_column ) && $field->display_column );
  591. }
  592. function get_field( $field_slug, $group_slug, $object_type ) {
  593. if ( $this->is_registered_field( $field_slug, $group_slug, $object_type ) ) {
  594. if ( $group_slug ) {
  595. return $this->get_single_field_in_group( $field_slug, $group_slug, $object_type );
  596. } else {
  597. return $this->get_single_field_in_object_type( $field_slug, $object_type );
  598. }
  599. }
  600. return null;
  601. }
  602. function get_group( $group_slug, $object_type ) {
  603. if ( $this->is_registered_group( $group_slug, $object_type ) ) {
  604. $groups = $this->get_groups_in_object_type( $object_type );
  605. $group = $groups[$group_slug];
  606. return $group;
  607. }
  608. return null;
  609. }
  610. function get_object_types() {
  611. return array_keys( $this->metadata );
  612. }
  613. function get_groups_in_object_type( $object_type ) {
  614. if ( $this->is_registered_object_type( $object_type ) )
  615. return $this->metadata[$object_type];
  616. return array();
  617. }
  618. function get_single_field_in_group( $field_slug, $group_slug, $object_type ) {
  619. $fields = $this->get_fields_in_group( $group_slug, $object_type );
  620. return isset( $fields[$field_slug] ) ? $fields[$field_slug] : null;
  621. }
  622. function get_fields_in_group( $group_slug, $object_type ) {
  623. $group = $this->get_group( $group_slug, $object_type );
  624. if ( $group ) return (array) $group->fields;
  625. return array();
  626. }
  627. function get_fields_in_multifield( $group_slug, $multifield_slug, $object_type ) {
  628. $group = $this->get_group( $group_slug, $object_type );
  629. $fields_in_multifield = array();
  630. if ( empty( $group ) || empty( $group->fields ) || empty( $group->fields[$multifield_slug] ) )
  631. return $fields_in_multifield;
  632. $_multifields = wp_list_pluck( $group->fields, 'multifield' );
  633. foreach ( $_multifields as $_field_key => $_multifield ) {
  634. if ( empty( $_multifield ) || true === $_multifield )
  635. continue;
  636. if ( $multifield_slug == $_multifield || $multifield_slug == '_x_multifield_' . $_multifield )
  637. $fields_in_multifield[$_field_key] = $group->fields[$_field_key];
  638. }
  639. return $fields_in_multifield;
  640. }
  641. function get_single_field_in_object_type( $field_slug, $object_type ) {
  642. $fields = $this->get_fields_in_object_type( $object_type );
  643. return isset( $fields[$field_slug] ) ? $fields[$field_slug] : null;
  644. }
  645. function get_fields_in_object_type( $object_type ) {
  646. $fields = array();
  647. foreach ( $this->get_groups_in_object_type( $object_type ) as $group_slug => $group ) {
  648. $fields = array_merge( $fields, $this->get_fields_in_group( $group_slug, $object_type ) );
  649. }
  650. return $fields;
  651. }
  652. function _push_group( $group_slug, $group, $object_type ) {
  653. $this->metadata[$object_type][$group_slug] = $group;
  654. }
  655. function _push_field( $field_slug, $field, $group_slug, $object_type ) {
  656. $this->metadata[$object_type][$group_slug]->fields[$field_slug] = $field;
  657. }
  658. function _push_multifield( $slug, $multifield, $group_slug, $object_type ) {
  659. $this->metadata[$object_type][$group_slug]->fields['_x_multifield_' . $slug] = $multifield;
  660. }
  661. function _multifield_exists_for_group_object( $slug, $group_slug, $object_type ) {
  662. $slug = '_x_multifield_' . $slug;
  663. return (
  664. ! empty( $this->metadata[$object_type] ) &&
  665. ! empty( $this->metadata[$object_type][$group_slug] ) &&
  666. ! empty( $this->metadata[$object_type][$group_slug]->fields ) &&
  667. array_key_exists( $slug, $this->metadata[$object_type][$group_slug]->fields )
  668. );
  669. }
  670. function _is_multifield( $slug ) {
  671. return ( 0 === strpos( $slug, '_x_multifield' ) );
  672. }
  673. function is_thing_added_to_object( $thing_slug, $thing, $object_type, $object_id, $object_slug = '' ) {
  674. if ( isset( $thing->exclude ) ) {
  675. if ( is_callable( $thing->exclude ) )
  676. return ! (bool) call_user_func( $thing->exclude, $thing_slug, $thing, $object_type, $object_id, $object_slug );
  677. return ! $this->does_id_array_match_object( $thing->exclude, $object_type, $object_id, $object_slug );
  678. }
  679. if ( isset( $thing->include ) ) {
  680. if ( is_callable( $thing->include ) )
  681. return (bool) call_user_func( $thing->include, $thing_slug, $thing, $object_type, $object_id, $object_slug );
  682. return $this->does_id_array_match_object( $thing->include, $object_type, $object_id, $object_slug );
  683. }
  684. return true;
  685. }
  686. function does_id_array_match_object( $id_array, $object_type, $object_id, $object_slug = '' ) {
  687. if ( is_array( $id_array ) ) {
  688. if ( isset( $id_array[$object_type] ) ) {
  689. if ( is_array( $id_array[$object_type] ) ) {
  690. // array( 'user' => array( 123, 'postname' ) )
  691. return $this->does_id_array_match_object( $id_array[$object_type], $object_type, $object_id, $object_slug );
  692. } else {
  693. // array( 'post' => 123 )
  694. return $this->does_id_match_object( $id_array[$object_type], $object_id, $object_slug );
  695. }
  696. } else {
  697. // array( 123, 456, 'postname' )
  698. $match = false;
  699. foreach ( $id_array as $id ) {
  700. if ( $this->does_id_match_object( $id, $object_id, $object_slug ) ) {
  701. $match = true;
  702. break;
  703. }
  704. }
  705. return $match;
  706. }
  707. } else {
  708. // 123 || 'postname' || 'username' || 'comment-name'(?)
  709. return $this->does_id_match_object( $id_array, $object_id, $object_slug );
  710. }
  711. }
  712. function does_id_match_object( $id, $object_id, $object_slug = '' ) {
  713. if ( is_int( $id ) ) {
  714. // 123
  715. return $id == $object_id;
  716. } elseif ( is_string( $id ) ) {
  717. // 'postname' || 'username' || 'comment-name' ??
  718. return $id == $object_slug;
  719. }
  720. return false;
  721. }
  722. function is_restricted_field( $field_slug, $object_type ) {
  723. // TODO: Build this out
  724. $post_restricted = array( 'post_title', 'post_author' );
  725. $page_restricted = array( );
  726. $user_restricted = array( );
  727. switch ( $object_type ) {
  728. case 'user':
  729. return in_array( $field_slug, $user_restricted );
  730. case 'page':
  731. return in_array( $field_slug, $page_restricted ) || in_array( $field_slug, $post_restricted );
  732. case 'post':
  733. default:
  734. return in_array( $field_slug, $post_restricted );
  735. }
  736. return false;
  737. }
  738. function is_restricted_group( $group_slug, $object_type ) {
  739. // TODO: Build this out
  740. // Built-in metaboxes: title, custom-fields, revisions, author, etc.
  741. return false;
  742. }
  743. function _get_object_type_context() {
  744. global $current_screen, $pagenow;
  745. $object_type = '';
  746. if ( $pagenow == 'profile.php' || $pagenow == 'user-edit.php' || $pagenow == 'users.php' ) {
  747. return 'user';
  748. }
  749. if ( isset( $current_screen->post_type ) ) {
  750. $object_type = $current_screen->post_type;
  751. } elseif ( isset( $current_screen->base ) ) {
  752. foreach ( $this->_builtin_object_types as $builtin_type ) {
  753. if ( strpos( $current_screen->base, $builtin_type ) !== false ) {
  754. $object_type = $builtin_type;
  755. break;
  756. }
  757. }
  758. }
  759. return $object_type;
  760. }
  761. function _get_value_callback( $field, $object_type ) {
  762. $callback = isset( $field->value_callback ) ? $field->value_callback : '';
  763. if ( ! ( $callback && is_callable( $callback ) ) )
  764. $callback = '';
  765. return apply_filters( 'custom_metadata_manager_get_value_callback', $callback, $field, $object_type );
  766. }
  767. function _get_save_callback( $field, $object_type ) {
  768. $callback = isset( $field->save_callback ) ? $field->save_callback : '';
  769. if ( ! ( $callback && is_callable( $callback ) ) )
  770. $callback = '';
  771. return apply_filters( 'custom_metadata_manager_get_save_callback', $callback, $field, $object_type );
  772. }
  773. function get_sanitize_callback( $field, $object_type ) {
  774. $callback = $field->sanitize_callback;
  775. if ( ! ( $callback && is_callable( $callback ) ) )
  776. $callback = '';
  777. return apply_filters( 'custom_metadata_manager_get_sanitize_callback', $callback, $field, $object_type );
  778. }
  779. function get_display_column_callback( $field, $object_type ) {
  780. $callback = $field->display_column_callback;
  781. if ( ! ( $callback && is_callable( $callback ) ) )
  782. $callback = '';
  783. return apply_filters( 'custom_metadata_manager_get_display_column_callback', $callback, $field, $object_type );
  784. }
  785. function _get_field_value( $field_slug, $field, $object_type, $object_id, $single = false ) {
  786. $get_value_callback = $this->_get_value_callback( $field, $object_type );
  787. if ( $get_value_callback )
  788. return call_user_func( $get_value_callback, $object_type, $object_id, $field_slug );
  789. if ( !in_array( $object_type, $this->_non_post_types ) )
  790. $object_type = 'post';
  791. $value = get_metadata( $object_type, $object_id, $field_slug, $single );
  792. return $value;
  793. }
  794. function _save_field_value( $field_slug, $field, $object_type, $object_id, $value ) {
  795. $save_callback = $this->_get_save_callback( $field, $object_type );
  796. if ( $save_callback )
  797. return call_user_func( $save_callback, $object_type, $object_id, $field_slug, $value );
  798. if ( ! in_array( $object_type, $this->_non_post_types ) )
  799. $object_type = 'post';
  800. $field_slug = sanitize_key( $field_slug );
  801. // save the taxonomy as a taxonomy [as well as a custom field]
  802. if ( in_array( $field->field_type, $this->_taxonomy_fields ) && !in_array( $object_type, $this->_non_post_types ) ) {
  803. wp_set_object_terms( $object_id, $value, $field->taxonomy );
  804. }
  805. if ( is_array( $value ) ) {
  806. // multiple values
  807. delete_metadata( $object_type, $object_id, $field_slug ); // delete the old values and add the new ones
  808. foreach ( $value as $v ) {
  809. add_metadata( $object_type, $object_id, $field_slug, $v, false );
  810. }
  811. } else {
  812. // single value
  813. update_metadata( $object_type, $object_id, $field_slug, $value );
  814. }
  815. // delete metadata entries if empty
  816. if ( empty( $value ) ) {
  817. delete_metadata( $object_type, $object_id, $field_slug );
  818. }
  819. }
  820. function _delete_field_value( $field_slug, $field, $object_type, $object_id, $value = false ) {
  821. if ( ! in_array( $object_type, $this->_non_post_types ) )
  822. $object_type = 'post';
  823. $field_slug = sanitize_key( $field_slug );
  824. delete_metadata( $object_type, $object_id, $field_slug, $value );
  825. }
  826. function _sanitize_field_value( $field_slug, $field, $object_type, $object_id, $value ) {
  827. $sanitize_callback = $this->get_sanitize_callback( $field, $object_type );
  828. // convert date to unix timestamp
  829. if ( in_array( $field->field_type, array( 'datepicker', 'datetimepicker', 'timepicker' ) ) ) {
  830. $value = strtotime( $value );
  831. }
  832. if ( $sanitize_callback )
  833. return call_user_func( $sanitize_callback, $field_slug, $field, $object_type, $object_id, $value );
  834. return $value;
  835. }
  836. function _metadata_column_content( $field_slug, $field, $object_type, $object_id ) {
  837. $value = $this->get_metadata_field_value( $field_slug, $field, $object_type, $object_id );
  838. $display_column_callback = $this->get_display_column_callback( $field, $object_type );
  839. if ( $display_column_callback )
  840. return call_user_func( $display_column_callback, $field_slug, $field, $object_type, $object_id, $value );
  841. if ( is_array( $value ) )
  842. return implode( ', ', $value );
  843. return $value;
  844. }
  845. function _display_metadata_multifield( $slug, $multifield, $object_type, $object_id ) {
  846. echo '<div class="custom-metadata-multifield" data-slug="' . esc_attr( $slug ) . '" id="' . esc_attr( 'custom-metadata-multifield-' . str_replace( '_', '-', str_replace( '_x_multifield_', '', $slug ) ) ) . '">';
  847. if ( ! empty( $multifield->label ) ) {
  848. printf( '<h2>%s</h2>', esc_html( $multifield->label ) );
  849. }
  850. if ( ! empty( $multifield->description ) ) {
  851. printf( '<p class="description">%s</p>', esc_html( $multifield->description ) );
  852. }
  853. $fields = $this->get_fields_in_multifield( $multifield->group, $slug, $object_type );
  854. // validate/weed out the fields that can't be part of mulitified
  855. foreach ( $fields as $field_slug => $field ) {
  856. if ( ! in_array( $field->field_type, $this->_field_types_that_support_multifield ) ) {
  857. unset( $fields[$field_slug] );
  858. }
  859. }
  860. $_values = $this->get_metadata_mulitifield_value( $slug, $multifield, $object_type, $object_id );
  861. $_values = ( ! empty( $_values ) ) ? $_values : array( array() );
  862. $grouping_count = 0;
  863. foreach ( $_values as $grouping_of_values ) {
  864. $grouping_count++;
  865. $grouping_id = $slug . '-' . $grouping_count;
  866. printf( '<div id="%s" class="custom-metadata-multifield-grouping">', esc_attr( $grouping_id ) );
  867. foreach ( $fields as $field_slug => $field ) {
  868. $value = ( isset( $grouping_of_values[$field_slug] ) ) ? $grouping_of_values[$field_slug] : false;
  869. $field_id = $slug . '[' . ( $grouping_count - 1 ) . ']' . '[' . $field_slug . ']';
  870. $display_field_slug = $field_slug . '-' . $grouping_count;
  871. $this->_display_metadata_field( $display_field_slug, $field, $object_type, $object_id, $field_id, $value );
  872. }
  873. echo '<div class="clear"></div>';
  874. printf( '<a title="%s" class="custom-metadata-multifield-clone hide-if-no-js" href="#">+</a>', __( 'duplicate this set of fields' ) );
  875. if ( $grouping_count > 1 ) {
  876. printf( '<a title="%s" class="custom-metadata-multifield-delete hide-if-no-js" href="#">-</a>', __( 'remove this set of fields' ) );
  877. }
  878. echo '</div>';
  879. }
  880. echo '</div>';
  881. }
  882. function _display_metadata_field( $field_slug, $field, $object_type, $object_id, $field_id = null, $value = null ) {
  883. // this is a safety to prevent multifields from being displayed as a field
  884. if ( true === $field->multifield )
  885. return;
  886. if ( null === $value )
  887. $value = $this->get_metadata_field_value( $field_slug, $field, $object_type, $object_id );
  888. $callback = $field->display_callback;
  889. if ( $callback && is_callable( $callback ) ) {
  890. call_user_func( $callback, $field_slug, $field, $object_type, $object_id, $value );
  891. return;
  892. }
  893. echo '<div class="custom-metadata-field ' . sanitize_html_class( $field->field_type ) .'" data-slug="' . esc_attr( $field->slug ) . '">';
  894. if ( ! in_array( $object_type, $this->_non_post_types ) )
  895. global $post;
  896. if ( ! empty ($field->multiple ) && ( empty( $this->_cloneable_field_types ) || ! in_array( $field->field_type, $this->_cloneable_field_types ) ) ) {
  897. $field->multiple = false;
  898. printf( '<p class="error">%s</p>', __( '<strong>Note:</strong> this field type cannot be multiplied', 'custom-metadata-manager' ) );
  899. }
  900. if ( empty( $field_id ) ) {
  901. $field_id = ( ! empty( $field->multiple ) || in_array( $field->field_type, $this->_always_multiple_fields ) ) ? $field_slug . '[]' : $field_slug;
  902. }
  903. $cloneable = ( ! empty( $field->multiple ) ) ? true : false;
  904. $readonly_str = ( ! empty( $field->readonly ) ) ? ' readonly="readonly"' : '';
  905. $placeholder_str = ( in_array( $field->field_type, $this->_field_types_that_support_placeholder ) && ! empty( $field->placeholder ) ) ? ' placeholder="' . esc_attr( $field->placeholder ) . '"' : '';
  906. printf( '<label for="%s">%s</label>', esc_attr( $field_slug ), esc_html( $field->label ) );
  907. // check if there is a default value and set it if no value currently set
  908. if ( empty( $value ) && in_array( $field->field_type, $this->_field_types_that_support_default_value ) && ! empty( $field->default_value ) )
  909. $value = sanitize_text_field( $field->default_value );
  910. // if value is empty set to an empty string
  911. if ( empty( $value ) )
  912. $value = '';
  913. // make sure $value is an array
  914. $value = (array) $value;
  915. $count = 1;
  916. $container_class = sanitize_html_class( $field_slug );
  917. $container_class .= ( $cloneable ) ? ' cloneable' : '';
  918. foreach ( $value as $v ) :
  919. $container_id = $field_slug . '-' . $count;
  920. printf( '<div class="%s" id="%s">', esc_attr( $container_class ), esc_attr( $container_id ) );
  921. switch ( $field->field_type ) :
  922. case 'text' :
  923. printf( '<input type="text" id="%s" name="%s" value="%s"%s%s/>', esc_attr( $field_slug ), esc_attr( $field_id ), esc_attr( $v ), $readonly_str, $placeholder_str );
  924. break;
  925. case 'password' :
  926. printf( '<input type="password" id="%s" name="%s" value="%s"%s%s/>', esc_attr( $field_slug ), esc_attr( $field_id ), esc_attr( $v ), $readonly_str, $placeholder_str );
  927. break;
  928. case 'email' :
  929. printf( '<input type="email" id="%s" name="%s" value="%s"%s%s/>', esc_attr( $field_slug ), esc_attr( $field_id ), esc_attr( $v ), $readonly_str, $placeholder_str );
  930. break;
  931. case 'tel' :
  932. printf( '<input type="tel" id="%s" name="%s" value="%s"%s%s/>', esc_attr( $field_slug ), esc_attr( $field_id ), esc_attr( $v ), $readonly_str, $placeholder_str );
  933. break;
  934. case 'link' :
  935. printf( '<input type="text" id="%s" name="%s" value="%s" %s%s/>', esc_attr( $field_slug ), esc_attr( $field_id ), esc_attr( $v ), $readonly_str, $placeholder_str );
  936. printf( '<input type="button" class="button custom-metadata-link-button" value="%s"/>', esc_attr( $field->link_modal_button_text ) );
  937. break;
  938. case 'number' :
  939. $min = ( ! empty( $field->min ) ) ? ' min="' . (int) $field->min . '"': '';
  940. $max = ( ! empty( $field->max ) ) ? ' max="' . (int) $field->max . '"': '';
  941. printf( '<input type="number" id="%s" name="%s" value="%s"%s%s%s%s/>', esc_attr( $field_slug ), esc_attr( $field_id ), esc_attr( $v ), $readonly_str, $placeholder_str, $min, $max );
  942. break;
  943. case 'textarea' :
  944. printf( '<textarea id="%s" name="%s"%s%s>%s</textarea>', esc_attr( $field_slug ), esc_attr( $field_id ), $readonly_str, $placeholder_str, esc_textarea( $v ) );
  945. break;
  946. case 'checkbox' :
  947. printf( '<input type="checkbox" id="%s" name="%s" %s/>', esc_attr( $field_slug ), esc_attr( $field_id ), checked( $v, 'on', false ) );
  948. break;
  949. case 'radio' :
  950. foreach ( $field->values as $value_slug => $value_label ) {
  951. $value_id = sprintf( '%s_%s', $field_slug, $value_slug );
  952. printf( '<label for="%s" class="selectit">', esc_attr( $value_id ) );
  953. printf( '<input type="radio" id="%s" name="%s" id="%s" value="%s"%s/>', esc_attr( $value_id ), esc_attr( $field_id ), esc_attr( $value_id ), esc_attr( $value_slug ), checked( $v, $value_slug, false ) );
  954. echo esc_html( $value_label );
  955. echo '</label>';
  956. }
  957. break;
  958. case 'select' :
  959. $select2 = ( $field->select2 ) ? ' class="custom-metadata-select2" ' : ' ';
  960. $select2 .= ( $field->placeholder ) ? ' data-placeholder="'. esc_attr( $field->placeholder ) . '" ' : ' ';
  961. printf( '<select id="%s" name="%s"%s>', esc_attr( $field_slug ), esc_attr( $field_id ), $select2 );
  962. foreach ( $field->values as $value_slug => $value_label ) {
  963. printf( '<option value="%s"%s>', esc_attr( $value_slug ), selected( $v, $value_slug, false ) );
  964. echo esc_html( $value_label );
  965. echo '</option>';
  966. }
  967. echo '</select>';
  968. break;
  969. case 'thumbnail':
  970. $image_id = ! empty( $v )? intval( $v ) : '' ;
  971. $set_css = ( !empty( $image_id ) ) ? 'display:none;' : '';
  972. $reset_css = ( empty( $image_id ) ) ? 'display:none;' : '';
  973. $preview = in_array( $field->preview, array( 'thumbnail', 'medium', 'full' ) ) ? $field->preview : 'thumbnail';
  974. echo '<span class="set-custom-media-container"><p class="hide-if-no-js">';
  975. // Set image link
  976. printf( '<a title="Set %s" href="#" id="add-custom-%s-media" class="add-custom-metadata-media" style="%s" data-preview="%s" >Set %s</a>', esc_attr( $field->label ), esc_attr( $field_slug ), esc_attr( $set_css ), esc_attr( $preview ) ,esc_attr( $field->label ) );
  977. // Image
  978. echo '</p><p class="hide-if-no-js"><p class="hide-if-no-js">';
  979. if ( !empty( $image_id ) ) {
  980. echo wp_get_attachment_image( $image_id, $preview, false, array( 'class' => 'custom-metadata-media-image' ) );
  981. } else {
  982. echo '<img class="custom-metadata-media-image" src="" style="display: none" />';
  983. }
  984. // Image ID
  985. printf( '<input class="custom-metadata-media-id" type="hidden" name="%s" value="%s" />', esc_attr( $field_slug ), esc_attr( $image_id ) );
  986. echo '</p><p class="hide-if-no-js">';
  987. // Remove image link
  988. printf( '<a title="Remove %s" href="#" class="remove-custom-metadata-media" style="%s">Remove %s</a>', esc_attr( $field->label ), esc_attr( $reset_css ), esc_attr( $field->label ) );
  989. echo '</p></span>';
  990. break;
  991. case 'datepicker' :
  992. $datepicker_value = ! empty( $v ) ? esc_attr( date( 'm/d/Y', $v ) ) : '';
  993. printf( '<input type="text" name="%s" value="%s"%s%s/>', esc_attr( $field_id ), $datepicker_value, $readonly_str, $placeholder_str );
  994. break;
  995. case 'colorpicker':
  996. printf( '<input type="text" name="%s" value="%s"%s%s/>', esc_attr( $field_id ), esc_attr( $v ), $readonly_str, $placeholder_str );
  997. break;
  998. case 'datetimepicker' :
  999. $datetimepicker_value = ! empty( $v ) ? esc_attr( date( 'm/d/Y G:i', $v ) ) : '';
  1000. printf( '<input type="text" name="%s" value="%s"%s%s/>', esc_attr( $field_id ), $datetimepicker_value, $readonly_str, $placeholder_str );
  1001. break;
  1002. case 'timepicker' :
  1003. $timepicker = ! empty( $v ) ? esc_attr( date( 'G:i', $v ) ) : '';
  1004. printf( '<input type="text" name="%s" value="%s"%s%s/>', esc_attr( $field_id ), $timepicker, $readonly_str, $placeholder_str );
  1005. break;
  1006. case 'wysiwyg' :
  1007. $wysiwyg_args = apply_filters( 'custom_metadata_manager_wysiwyg_args_field_' . $field_id, $this->default_editor_args, $field_slug, $field, $object_type, $object_id );
  1008. wp_editor( $v, $field_id, $wysiwyg_args );
  1009. break;
  1010. case 'upload' :
  1011. $_attachment_id = $this->get_metadata_field_value( $field_slug . '_attachment_id', $field, $object_type, $object_id );
  1012. $attachment_id = array_shift( array_values( $_attachment_id ) ); // get the first value in the array
  1013. printf( '<input type="text" name="%s" value="%s" class="custom-metadata-upload-url"%s%s/>', esc_attr( $field_id ), esc_attr( $v ), $readonly_str, $placeholder_str );
  1014. printf( '<input type="button" data-uploader-title="%s" data-uploader-button-text="%s" class="button custom-metadata-upload-button" value="%s"/>', esc_attr( $field->upload_modal_title ), esc_attr( $field->upload_modal_button_text ), esc_attr( $field->upload_modal_title ) );
  1015. printf( '<input type="button" class="button custom-metadata-clear-button" value="%s"/>', $field->upload_clear_button_text );
  1016. printf( '<input type="hidden" name="%s" value="%s" class="custom-metadata-upload-id"/>', esc_attr( $field_id . '_attachment_id' ), esc_attr( $attachment_id ) );
  1017. break;
  1018. case 'taxonomy_select' :
  1019. $terms = get_terms( $field->taxonomy, array( 'hide_empty' => false ) );
  1020. if ( empty( $terms ) ) {
  1021. printf( __( 'There are no %s to select from yet.', $field->taxonomy ) );
  1022. break;
  1023. }
  1024. $select2 = ( $field->select2 ) ? ' class="custom-metadata-select2" ' : ' ';
  1025. $select2 .= ( $field->placeholder ) ? ' data-placeholder="'. esc_attr( $field->placeholder ) . '" ' : ' ';
  1026. printf( '<select name="%s" id="%s"%s>', esc_attr( $field_id ), esc_attr( $field_slug ), $select2 );
  1027. echo '<option value=""></option>';
  1028. foreach ( $terms as $term ) {
  1029. printf( '<option value="%s"%s>%s</option>', esc_attr( $term->slug ), selected( $v, $term->slug, false ), esc_html( $term->name ) );
  1030. }
  1031. echo '</select>';
  1032. break;
  1033. case 'taxonomy_radio' :
  1034. $terms = get_terms( $field->taxonomy, array( 'hide_empty' => false ) );
  1035. if ( empty( $terms ) ) {
  1036. printf( __( 'There are no %s to select from yet.', $field->taxonomy ) );
  1037. break;
  1038. }
  1039. foreach ( $terms as $term ) {
  1040. printf( '<label for="%s" class="selectit">', esc_attr( $term->slug ) );
  1041. printf( '<input type="radio" name="%s" value="%s" id="%s"%s>', esc_attr( $field_id ), esc_attr( $term->slug ), esc_attr( $term->slug ), checked( $v, $term->slug, false ) );
  1042. echo esc_html( $term->name );
  1043. echo '</label>';
  1044. }
  1045. break;
  1046. endswitch;
  1047. if ( $cloneable && $count > 1 )
  1048. echo '<a href="#" class="del-multiple hide-if-no-js">' . __( 'Delete', 'custom-metadata-manager' ) . '</a>';
  1049. $count++;
  1050. echo '</div>';
  1051. endforeach;
  1052. if ( in_array( $field->field_type, $this->_always_multiple_fields ) ) :
  1053. $container_id = $field_slug . '-' . 1;
  1054. printf( '<div class="%s" id="%s">', esc_attr( $container_class ), esc_attr( $container_id ) );
  1055. // fields that save as arrays are not part of the foreach, otherwise they would display for each value, which is not the desired behaviour
  1056. switch ( $field->field_type ) :
  1057. case 'multi_select' :
  1058. $select2 = ( $field->select2 ) ? ' class="custom-metadata-select2" ' : ' ';
  1059. $select2 .= ( $field->placeholder ) ? ' data-placeholder="'. esc_attr( $field->placeholder ) . '" ' : ' ';
  1060. printf( '<select id="%s" name="%s"%smultiple>', esc_attr( $field_slug ), esc_attr( $field_id ), $select2 );
  1061. foreach ( $field->values as $value_slug => $value_label ) {
  1062. printf( '<option value="%s"%s>', esc_attr( $value_slug ), selected( in_array( $value_slug, $value ), true, false ) );
  1063. echo esc_html( $value_label );
  1064. echo '</option>';
  1065. }
  1066. echo '</select>';
  1067. break;
  1068. case 'taxonomy_checkbox' :
  1069. $terms = get_terms( $field->taxonomy, array( 'hide_empty' => false ) );
  1070. if ( empty( $terms ) ) {
  1071. printf( __( 'There are no %s to select from yet.', $field->taxonomy ) );
  1072. break;
  1073. }
  1074. foreach ( $terms as $term ) {
  1075. printf( ' <label for="%s" class="selectit">', esc_attr( $term->slug ) );
  1076. printf( '<input type="checkbox" name="%s" value="%s" id="%s"%s>', esc_attr( $field_id ), esc_attr( $term->slug ), esc_attr( $term->slug ), checked( in_array( $term->slug, $value ), true, false ) );
  1077. echo esc_html( $term->name );
  1078. echo '</label>';
  1079. }
  1080. break;
  1081. case 'taxonomy_multi_select' :
  1082. $terms = get_terms( $field->taxonomy, array( 'hide_empty' => false ) );
  1083. if ( empty( $terms ) ) {
  1084. printf( __( 'There are no %s to select from yet.', $field->taxonomy ) );
  1085. break;
  1086. }
  1087. $select2 = ( $field->select2 ) ? ' class="custom-metadata-select2" ' : ' ';
  1088. $select2 .= ( $field->placeholder ) ? ' data-placeholder="'. esc_attr( $field->placeholder ) . '" ' : ' ';
  1089. printf( '<select name="%s" id="%s"%smultiple>', esc_attr( $field_id ), esc_attr( $field_slug ), $select2 );
  1090. foreach ( $terms as $term ) {
  1091. printf( '<option value="%s"%s>%s</option>', esc_attr( $term->slug ), selected( in_array( $term->slug, $value ), true, false ), esc_html( $term->name ) );
  1092. }
  1093. echo '</select>';
  1094. break;
  1095. endswitch;
  1096. echo '</div>';
  1097. endif;
  1098. if ( $cloneable )
  1099. printf( '<p><a href="#" class="add-multiple hide-if-no-js" id="%s">%s</a></p>', esc_attr( 'add-' . $field_slug ), __( '+ Add New', 'custom-metadata-manager' ) );
  1100. $this->_display_field_description( $field_slug, $field, $object_type, $object_id, $value );
  1101. echo '</div>';
  1102. }
  1103. function _display_field_description( $field_slug, $field, $object_type, $object_id, $value ) {
  1104. if ( $field->description )
  1105. echo '<span class="description">' . $field->description . '</span>';
  1106. }
  1107. function _display_registration_errors() {
  1108. if ( empty( $this->errors ) )
  1109. return;
  1110. echo '<div class="message error">';
  1111. foreach ( $this->errors as $error => $error_message )
  1112. printf( '<li>%s</li>', esc_html( $error_message ) );
  1113. echo '</div>';
  1114. }
  1115. function _display_wp_link_dialog() {
  1116. if ( ! class_exists( '_WP_Editors' ) )
  1117. require( ABSPATH . WPINC . '/class-wp-editor.php' );
  1118. if ( ! has_action( 'admin_footer', array( '_WP_Editors', 'enqueue_scripts' ) ) )
  1119. _WP_Editors::wp_link_dialog();
  1120. }
  1121. }
  1122. global $custom_metadata_manager; // for backwards-compatibility we keep the global around, but it shouldn't be used
  1123. $custom_metadata_manager = custom_metadata_manager::instance();
  1124. function x_add_metadata_field( $slug, $object_types = 'post', $args = array() ) {
  1125. custom_metadata_manager::instance()->add_metadata_field( $slug, $object_types, $args );
  1126. }
  1127. function x_add_metadata_multifield( $slug, $object_types = 'post', $args = array() ) {
  1128. custom_metadata_manager::instance()->add_multifield( $slug, $object_types, $args );
  1129. }
  1130. function x_add_metadata_group( $slug, $object_types, $args = array() ) {
  1131. custom_metadata_manager::instance()->add_metadata_group( $slug, $object_types, $args );
  1132. }