├── butterbean.php ├── changelog.md ├── class-butterbean.php ├── contributing.md ├── css ├── butterbean.css └── butterbean.min.css ├── inc ├── class-control.php ├── class-manager.php ├── class-section.php ├── class-setting.php ├── controls │ ├── class-control-checkboxes.php │ ├── class-control-color.php │ ├── class-control-datetime.php │ ├── class-control-excerpt.php │ ├── class-control-image.php │ ├── class-control-multi-avatars.php │ ├── class-control-palette.php │ ├── class-control-parent.php │ ├── class-control-radio-image.php │ ├── class-control-radio.php │ ├── class-control-select-group.php │ └── class-control-textarea.php ├── functions-core.php └── settings │ ├── class-setting-array.php │ ├── class-setting-datetime.php │ └── class-setting-multiple.php ├── js ├── butterbean.js └── butterbean.min.js ├── license.md ├── readme.md └── tmpl ├── control-checkbox.php ├── control-checkboxes.php ├── control-color.php ├── control-datetime.php ├── control-image.php ├── control-multi-avatars.php ├── control-palette.php ├── control-parent.php ├── control-radio-image.php ├── control-radio.php ├── control-select-group.php ├── control-select.php ├── control-textarea.php ├── control.php ├── manager.php ├── nav.php └── section.php /butterbean.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 13 | * @link https://github.com/justintadlock/butterbean 14 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 15 | */ 16 | 17 | // For each version release, the priority needs to decrement by 1. This is so that 18 | // we can load newer versions earlier than older versions when there's a conflict. 19 | add_action( 'init', 'butterbean_loader_100', 9999 ); 20 | 21 | if ( ! function_exists( 'butterbean_loader_100' ) ) { 22 | 23 | /** 24 | * Loader function. Note to change the name of this function to use the 25 | * current version number of the plugin. `1.0.0` is `100`, `1.3.4` = `134`. 26 | * 27 | * @since 1.0.0 28 | * @access public 29 | * @return void 30 | */ 31 | function butterbean_loader_100() { 32 | 33 | // If not in the admin, bail. 34 | if ( ! is_admin() ) 35 | return; 36 | 37 | // If ButterBean hasn't been loaded, let's load it. 38 | if ( ! defined( 'BUTTERBEAN_LOADED' ) ) { 39 | define( 'BUTTERBEAN_LOADED', true ); 40 | 41 | require_once( trailingslashit( plugin_dir_path( __FILE__ ) ) . 'class-butterbean.php' ); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.0.0] - 2016-08-29 4 | 5 | * Plugin launch. Everything's new! 6 | -------------------------------------------------------------------------------- /class-butterbean.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 8 | * @link https://github.com/justintadlock/butterbean 9 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 10 | */ 11 | 12 | if ( ! class_exists( 'ButterBean' ) ) { 13 | 14 | /** 15 | * Main ButterBean class. Runs the show. 16 | * 17 | * @since 1.0.0 18 | * @access public 19 | */ 20 | final class ButterBean { 21 | 22 | /** 23 | * Directory path to the plugin folder. 24 | * 25 | * @since 1.0.0 26 | * @access public 27 | * @var string 28 | */ 29 | public $dir_path = ''; 30 | 31 | /** 32 | * Directory URI to the plugin folder. 33 | * 34 | * @since 1.0.0 35 | * @access public 36 | * @var string 37 | */ 38 | public $dir_uri = ''; 39 | 40 | /** 41 | * Directory path to the template folder. 42 | * 43 | * @since 1.0.0 44 | * @access public 45 | * @var string 46 | */ 47 | public $tmpl_path = ''; 48 | 49 | /** 50 | * Array of managers. 51 | * 52 | * @since 1.0.0 53 | * @access public 54 | * @var array 55 | */ 56 | public $managers = array(); 57 | 58 | /** 59 | * Array of manager types. 60 | * 61 | * @since 1.0.0 62 | * @access public 63 | * @var array 64 | */ 65 | public $manager_types = array(); 66 | 67 | /** 68 | * Array of section types. 69 | * 70 | * @since 1.0.0 71 | * @access public 72 | * @var array 73 | */ 74 | public $section_types = array(); 75 | 76 | /** 77 | * Array of control types. 78 | * 79 | * @since 1.0.0 80 | * @access public 81 | * @var array 82 | */ 83 | public $control_types = array(); 84 | 85 | /** 86 | * Array of setting types. 87 | * 88 | * @since 1.0.0 89 | * @access public 90 | * @var array 91 | */ 92 | public $setting_types = array(); 93 | 94 | /** 95 | * Whether this is a new post. Once the post is saved and we're 96 | * no longer on the `post-new.php` screen, this is going to be 97 | * `false`. 98 | * 99 | * @since 1.0.0 100 | * @access public 101 | * @var bool 102 | */ 103 | public $is_new_post = false; 104 | 105 | /** 106 | * Returns the instance. 107 | * 108 | * @since 1.0.0 109 | * @access public 110 | * @return object 111 | */ 112 | public static function get_instance() { 113 | 114 | static $instance = null; 115 | 116 | if ( is_null( $instance ) ) { 117 | $instance = new self; 118 | $instance->setup(); 119 | $instance->includes(); 120 | $instance->setup_actions(); 121 | } 122 | 123 | return $instance; 124 | } 125 | 126 | /** 127 | * Constructor method. 128 | * 129 | * @since 1.0.0 130 | * @access private 131 | * @return void 132 | */ 133 | private function __construct() {} 134 | 135 | /** 136 | * Initial plugin setup. 137 | * 138 | * @since 1.0.0 139 | * @access private 140 | * @return void 141 | */ 142 | private function setup() { 143 | 144 | $this->dir_path = apply_filters( 'butterbean_dir_path', trailingslashit( plugin_dir_path( __FILE__ ) ) ); 145 | $this->dir_uri = apply_filters( 'butterbean_dir_uri', trailingslashit( plugin_dir_url( __FILE__ ) ) ); 146 | 147 | $this->tmpl_path = trailingslashit( $this->dir_path . 'tmpl' ); 148 | } 149 | 150 | /** 151 | * Loads include and admin files for the plugin. 152 | * 153 | * @since 1.0.0 154 | * @access private 155 | * @return void 156 | */ 157 | private function includes() { 158 | 159 | // If not in the admin, bail. 160 | if ( ! is_admin() ) 161 | return; 162 | 163 | // Load base classes. 164 | require_once( $this->dir_path . 'inc/class-manager.php' ); 165 | require_once( $this->dir_path . 'inc/class-section.php' ); 166 | require_once( $this->dir_path . 'inc/class-control.php' ); 167 | require_once( $this->dir_path . 'inc/class-setting.php' ); 168 | 169 | // Load control sub-classes. 170 | require_once( $this->dir_path . 'inc/controls/class-control-checkboxes.php' ); 171 | require_once( $this->dir_path . 'inc/controls/class-control-color.php' ); 172 | require_once( $this->dir_path . 'inc/controls/class-control-datetime.php' ); 173 | require_once( $this->dir_path . 'inc/controls/class-control-image.php' ); 174 | require_once( $this->dir_path . 'inc/controls/class-control-palette.php' ); 175 | require_once( $this->dir_path . 'inc/controls/class-control-radio.php' ); 176 | require_once( $this->dir_path . 'inc/controls/class-control-radio-image.php' ); 177 | require_once( $this->dir_path . 'inc/controls/class-control-select-group.php' ); 178 | require_once( $this->dir_path . 'inc/controls/class-control-textarea.php' ); 179 | 180 | require_once( $this->dir_path . 'inc/controls/class-control-excerpt.php' ); 181 | require_once( $this->dir_path . 'inc/controls/class-control-multi-avatars.php' ); 182 | require_once( $this->dir_path . 'inc/controls/class-control-parent.php' ); 183 | 184 | // Load setting sub-classes. 185 | require_once( $this->dir_path . 'inc/settings/class-setting-multiple.php' ); 186 | require_once( $this->dir_path . 'inc/settings/class-setting-datetime.php' ); 187 | require_once( $this->dir_path . 'inc/settings/class-setting-array.php' ); 188 | 189 | // Load functions. 190 | require_once( $this->dir_path . 'inc/functions-core.php' ); 191 | } 192 | 193 | /** 194 | * Sets up initial actions. 195 | * 196 | * @since 1.0.0 197 | * @access private 198 | * @return void 199 | */ 200 | private function setup_actions() { 201 | 202 | // Call the register function. 203 | add_action( 'load-post.php', array( $this, 'register' ), 95 ); 204 | add_action( 'load-post-new.php', array( $this, 'register' ), 95 ); 205 | 206 | // Register default types. 207 | add_action( 'butterbean_register', array( $this, 'register_manager_types' ), -95 ); 208 | add_action( 'butterbean_register', array( $this, 'register_section_types' ), -95 ); 209 | add_action( 'butterbean_register', array( $this, 'register_control_types' ), -95 ); 210 | add_action( 'butterbean_register', array( $this, 'register_setting_types' ), -95 ); 211 | } 212 | 213 | /** 214 | * Registration callback. Fires the `butterbean_register` action hook to 215 | * allow plugins to register their managers. 216 | * 217 | * @since 1.0.0 218 | * @access public 219 | * @return void 220 | */ 221 | public function register() { 222 | 223 | // If this is a new post, set the new post boolean. 224 | if ( 'load-post-new.php' === current_action() ) 225 | $this->is_new_post = true; 226 | 227 | // Get the current post type. 228 | $post_type = get_current_screen()->post_type; 229 | 230 | // Action hook for registering managers. 231 | do_action( 'butterbean_register', $this, $post_type ); 232 | 233 | // Loop through the managers to see if we're using on on this screen. 234 | foreach ( $this->managers as $manager ) { 235 | 236 | // If we found a matching post type, add our actions/filters. 237 | if ( ! in_array( $post_type, (array) $manager->post_type ) ) { 238 | $this->unregister_manager( $manager->name ); 239 | continue; 240 | } 241 | 242 | // Sort controls and sections by priority. 243 | uasort( $manager->controls, array( $this, 'priority_sort' ) ); 244 | uasort( $manager->sections, array( $this, 'priority_sort' ) ); 245 | } 246 | 247 | // If no managers registered, bail. 248 | if ( ! $this->managers ) 249 | return; 250 | 251 | // Add meta boxes. 252 | add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 5 ); 253 | 254 | // Save settings. 255 | add_action( 'save_post', array( $this, 'update' ) ); 256 | 257 | // Load scripts and styles. 258 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); 259 | add_action( 'butterbean_enqueue_scripts', array( $this, 'enqueue' ) ); 260 | 261 | // Localize scripts and Undescore templates. 262 | add_action( 'admin_footer', array( $this, 'localize_scripts' ) ); 263 | add_action( 'admin_footer', array( $this, 'print_templates' ) ); 264 | 265 | // Renders our Backbone views. 266 | add_action( 'admin_print_footer_scripts', array( $this, 'render_views' ), 95 ); 267 | } 268 | 269 | /** 270 | * Register a manager. 271 | * 272 | * @since 1.0.0 273 | * @access public 274 | * @param object|string $manager 275 | * @param array $args 276 | * @return void 277 | */ 278 | public function register_manager( $manager, $args = array() ) { 279 | 280 | if ( ! is_object( $manager ) ) { 281 | 282 | $type = isset( $args['type'] ) ? $this->get_manager_type( $args['type'] ) : $this->get_manager_type( 'default' ); 283 | 284 | $manager = new $type( $manager, $args ); 285 | } 286 | 287 | if ( ! $this->manager_exists( $manager->name ) ) 288 | $this->managers[ $manager->name ] = $manager; 289 | 290 | return $manager; 291 | } 292 | 293 | /** 294 | * Unregisters a manager object. 295 | * 296 | * @since 1.0.0 297 | * @access public 298 | * @param string $name 299 | * @return void 300 | */ 301 | public function unregister_manager( $name ) { 302 | 303 | if ( $this->manager_exists( $name ) ) 304 | unset( $this->managers[ $name ] ); 305 | } 306 | 307 | /** 308 | * Returns a manager object. 309 | * 310 | * @since 1.0.0 311 | * @access public 312 | * @param string $name 313 | * @return object|bool 314 | */ 315 | public function get_manager( $name ) { 316 | 317 | return $this->manager_exists( $name ) ? $this->managers[ $name ] : false; 318 | } 319 | 320 | /** 321 | * Checks if a manager exists. 322 | * 323 | * @since 1.0.0 324 | * @access public 325 | * @param string $name 326 | * @return bool 327 | */ 328 | public function manager_exists( $name ) { 329 | 330 | return isset( $this->managers[ $name ] ); 331 | } 332 | 333 | /** 334 | * Registers a manager type. This is just a method of telling ButterBean 335 | * the class of your custom manager type. It allows the manager to be 336 | * called without having to pass an object to `register_manager()`. 337 | * 338 | * @since 1.0.0 339 | * @access public 340 | * @param string $type 341 | * @param string $class 342 | * @return void 343 | */ 344 | public function register_manager_type( $type, $class ) { 345 | 346 | if ( ! $this->manager_type_exists( $type ) ) 347 | $this->manager_types[ $type ] = $class; 348 | } 349 | 350 | /** 351 | * Unregisters a manager type. 352 | * 353 | * @since 1.0.0 354 | * @access public 355 | * @param string $type 356 | * @return void 357 | */ 358 | public function unregister_manager_type( $type ) { 359 | 360 | if ( $this->manager_type_exists( $type ) ) 361 | unset( $this->manager_types[ $type ] ); 362 | } 363 | 364 | /** 365 | * Returns the class name for the manager type. 366 | * 367 | * @since 1.0.0 368 | * @access public 369 | * @param string $type 370 | * @return string 371 | */ 372 | public function get_manager_type( $type ) { 373 | 374 | return $this->manager_type_exists( $type ) ? $this->manager_types[ $type ] : $this->manager_types[ 'default' ]; 375 | } 376 | 377 | /** 378 | * Checks if a manager type exists. 379 | * 380 | * @since 1.0.0 381 | * @access public 382 | * @param string $type 383 | * @return bool 384 | */ 385 | public function manager_type_exists( $type ) { 386 | 387 | return isset( $this->manager_types[ $type ] ); 388 | } 389 | 390 | /** 391 | * Registers a section type. This is just a method of telling ButterBean 392 | * the class of your custom section type. It allows the section to be 393 | * called without having to pass an object to `register_section()`. 394 | * 395 | * @since 1.0.0 396 | * @access public 397 | * @param string $type 398 | * @param string $class 399 | * @return void 400 | */ 401 | public function register_section_type( $type, $class ) { 402 | 403 | if ( ! $this->section_type_exists( $type ) ) 404 | $this->section_types[ $type ] = $class; 405 | } 406 | 407 | /** 408 | * Unregisters a section type. 409 | * 410 | * @since 1.0.0 411 | * @access public 412 | * @param string $type 413 | * @return void 414 | */ 415 | public function unregister_section_type( $type ) { 416 | 417 | if ( $this->section_type_exists( $type ) ) 418 | unset( $this->section_types[ $type ] ); 419 | } 420 | 421 | /** 422 | * Returns the class name for the section type. 423 | * 424 | * @since 1.0.0 425 | * @access public 426 | * @param string $type 427 | * @return string 428 | */ 429 | public function get_section_type( $type ) { 430 | 431 | return $this->section_type_exists( $type ) ? $this->section_types[ $type ] : $this->section_types[ 'default' ]; 432 | } 433 | 434 | /** 435 | * Checks if a section type exists. 436 | * 437 | * @since 1.0.0 438 | * @access public 439 | * @param string $type 440 | * @return bool 441 | */ 442 | public function section_type_exists( $type ) { 443 | 444 | return isset( $this->section_types[ $type ] ); 445 | } 446 | 447 | /** 448 | * Registers a control type. This is just a method of telling ButterBean 449 | * the class of your custom control type. It allows the control to be 450 | * called without having to pass an object to `register_control()`. 451 | * 452 | * @since 1.0.0 453 | * @access public 454 | * @param string $type 455 | * @param string $class 456 | * @return void 457 | */ 458 | public function register_control_type( $type, $class ) { 459 | 460 | if ( ! $this->control_type_exists( $type ) ) 461 | $this->control_types[ $type ] = $class; 462 | } 463 | 464 | /** 465 | * Unregisters a control type. 466 | * 467 | * @since 1.0.0 468 | * @access public 469 | * @param string $type 470 | * @return void 471 | */ 472 | public function unregister_control_type( $type ) { 473 | 474 | if ( $this->control_type_exists( $type ) ) 475 | unset( $this->control_types[ $type ] ); 476 | } 477 | 478 | /** 479 | * Returns the class name for the control type. 480 | * 481 | * @since 1.0.0 482 | * @access public 483 | * @param string $type 484 | * @return string 485 | */ 486 | public function get_control_type( $type ) { 487 | 488 | return $this->control_type_exists( $type ) ? $this->control_types[ $type ] : $this->control_types[ 'default' ]; 489 | } 490 | 491 | /** 492 | * Checks if a control type exists. 493 | * 494 | * @since 1.0.0 495 | * @access public 496 | * @param string $type 497 | * @return bool 498 | */ 499 | public function control_type_exists( $type ) { 500 | 501 | return isset( $this->control_types[ $type ] ); 502 | } 503 | 504 | /** 505 | * Registers a setting type. This is just a method of telling ButterBean 506 | * the class of your custom setting type. It allows the setting to be 507 | * called without having to pass an object to `register_setting()`. 508 | * 509 | * @since 1.0.0 510 | * @access public 511 | * @param string $type 512 | * @param string $class 513 | * @return void 514 | */ 515 | public function register_setting_type( $type, $class ) { 516 | 517 | if ( ! $this->setting_type_exists( $type ) ) 518 | $this->setting_types[ $type ] = $class; 519 | } 520 | 521 | /** 522 | * Unregisters a setting type. 523 | * 524 | * @since 1.0.0 525 | * @access public 526 | * @param string $type 527 | * @return void 528 | */ 529 | public function unregister_setting_type( $type ) { 530 | 531 | if ( $this->setting_type_exists( $type ) ) 532 | unset( $this->setting_types[ $type ] ); 533 | } 534 | 535 | /** 536 | * Returns the class name for the setting type. 537 | * 538 | * @since 1.0.0 539 | * @access public 540 | * @param string $type 541 | * @return string 542 | */ 543 | public function get_setting_type( $type ) { 544 | 545 | return $this->setting_type_exists( $type ) ? $this->setting_types[ $type ] : $this->setting_types[ 'default' ]; 546 | } 547 | 548 | /** 549 | * Checks if a setting type exists. 550 | * 551 | * @since 1.0.0 552 | * @access public 553 | * @param string $type 554 | * @return bool 555 | */ 556 | public function setting_type_exists( $type ) { 557 | 558 | return isset( $this->setting_types[ $type ] ); 559 | } 560 | 561 | /** 562 | * Registers our manager types so that devs don't have to directly instantiate 563 | * the class each time they register a manager. Instead, they can use the 564 | * `type` argument. 565 | * 566 | * @since 1.0.0 567 | * @access public 568 | * @return void 569 | */ 570 | public function register_manager_types() { 571 | 572 | $this->register_manager_type( 'default', 'ButterBean_Manager' ); 573 | } 574 | 575 | /** 576 | * Registers our section types so that devs don't have to directly instantiate 577 | * the class each time they register a section. Instead, they can use the 578 | * `type` argument. 579 | * 580 | * @since 1.0.0 581 | * @access public 582 | * @return void 583 | */ 584 | public function register_section_types() { 585 | 586 | $this->register_section_type( 'default', 'ButterBean_Section' ); 587 | } 588 | 589 | /** 590 | * Registers our control types so that devs don't have to directly instantiate 591 | * the class each time they register a control. Instead, they can use the 592 | * `type` argument. 593 | * 594 | * @since 1.0.0 595 | * @access public 596 | * @return void 597 | */ 598 | public function register_control_types() { 599 | 600 | $this->register_control_type( 'default', 'ButterBean_Control' ); 601 | $this->register_control_type( 'checkboxes', 'ButterBean_Control_Checkboxes' ); 602 | $this->register_control_type( 'color', 'ButterBean_Control_Color' ); 603 | $this->register_control_type( 'datetime', 'ButterBean_Control_Datetime' ); 604 | $this->register_control_type( 'excerpt', 'ButterBean_Control_Excerpt' ); 605 | $this->register_control_type( 'image', 'ButterBean_Control_Image' ); 606 | $this->register_control_type( 'palette', 'ButterBean_Control_Palette' ); 607 | $this->register_control_type( 'radio', 'ButterBean_Control_Radio' ); 608 | $this->register_control_type( 'radio-image', 'ButterBean_Control_Radio_Image' ); 609 | $this->register_control_type( 'select-group', 'ButterBean_Control_Select_Group' ); 610 | $this->register_control_type( 'textarea', 'ButterBean_Control_Textarea' ); 611 | $this->register_control_type( 'multi-avatars', 'ButterBean_Control_Multi_Avatars' ); 612 | $this->register_control_type( 'parent', 'ButterBean_Control_Parent' ); 613 | } 614 | 615 | /** 616 | * Registers our setting types so that devs don't have to directly instantiate 617 | * the class each time they register a setting. Instead, they can use the 618 | * `type` argument. 619 | * 620 | * @since 1.0.0 621 | * @access public 622 | * @return void 623 | */ 624 | public function register_setting_types() { 625 | 626 | $this->register_setting_type( 'default', 'ButterBean_Setting' ); 627 | $this->register_setting_type( 'single', 'ButterBean_Setting' ); 628 | $this->register_setting_type( 'multiple', 'ButterBean_Setting_Multiple' ); 629 | $this->register_setting_type( 'array', 'ButterBean_Setting_Array' ); 630 | $this->register_setting_type( 'datetime', 'ButterBean_Setting_Datetime' ); 631 | } 632 | 633 | /** 634 | * Fires an action hook to register/enqueue scripts/styles. 635 | * 636 | * @since 1.0.0 637 | * @access public 638 | * @return void 639 | */ 640 | public function enqueue_scripts() { 641 | 642 | do_action( 'butterbean_enqueue_scripts' ); 643 | } 644 | 645 | /** 646 | * Loads scripts and styles. 647 | * 648 | * @since 1.0.0 649 | * @access public 650 | * @return void 651 | */ 652 | public function enqueue() { 653 | $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; 654 | 655 | // Enqueue the main plugin script. 656 | wp_enqueue_script( 'butterbean', $this->dir_uri . "js/butterbean{$min}.js", array( 'backbone', 'wp-util' ), '', true ); 657 | 658 | // Enqueue the main plugin style. 659 | wp_enqueue_style( 'butterbean', $this->dir_uri . "css/butterbean{$min}.css" ); 660 | 661 | // Loop through the manager and its controls and call each control's `enqueue()` method. 662 | foreach ( $this->managers as $manager ) { 663 | 664 | $manager->enqueue(); 665 | 666 | foreach ( $manager->sections as $section ) 667 | $section->enqueue(); 668 | 669 | foreach ( $manager->controls as $control ) 670 | $control->enqueue(); 671 | } 672 | } 673 | 674 | /** 675 | * Callback function for adding meta boxes. This function adds a meta box 676 | * for each of the managers. 677 | * 678 | * @since 1.0.0 679 | * @access public 680 | * @param string $post_type 681 | * @return void 682 | */ 683 | public function add_meta_boxes( $post_type ) { 684 | 685 | foreach ( $this->managers as $manager ) { 686 | 687 | // If the manager is registered for the current post type, add a meta box. 688 | if ( in_array( $post_type, (array) $manager->post_type ) && $manager->check_capabilities() ) { 689 | 690 | add_meta_box( 691 | "butterbean-ui-{$manager->name}", 692 | $manager->label, 693 | array( $this, 'meta_box' ), 694 | $post_type, 695 | $manager->context, 696 | $manager->priority, 697 | array( 'manager' => $manager ) 698 | ); 699 | } 700 | } 701 | } 702 | 703 | /** 704 | * Displays the meta box. Note that the actual content of the meta box is 705 | * handled via Underscore.js templates. The only thing we're outputting here 706 | * is the nonce field. 707 | * 708 | * @since 1.0.0 709 | * @access public 710 | * @param object $post 711 | * @param array $metabox 712 | * @return void 713 | */ 714 | public function meta_box( $post, $metabox ) { 715 | 716 | $manager = $metabox['args']['manager']; 717 | 718 | $manager->post_id = $this->post_id = $post->ID; 719 | 720 | // Nonce field to validate on save. 721 | wp_nonce_field( "butterbean_{$manager->name}_nonce", "butterbean_{$manager->name}" ); 722 | } 723 | 724 | /** 725 | * Passes the appropriate section and control json data to the JS file. 726 | * 727 | * @since 1.0.0 728 | * @access public 729 | * @return void 730 | */ 731 | public function localize_scripts() { 732 | 733 | $json = array( 'managers' => array() ); 734 | 735 | foreach ( $this->managers as $manager ) { 736 | 737 | if ( $manager->check_capabilities() ) 738 | $json['managers'][] = $manager->get_json(); 739 | } 740 | 741 | wp_localize_script( 'butterbean', 'butterbean_data', $json ); 742 | } 743 | 744 | /** 745 | * Prints the Underscore.js templates. 746 | * 747 | * @since 1.0.0 748 | * @access public 749 | * @return void 750 | */ 751 | public function print_templates() { 752 | 753 | $m_templates = array(); 754 | $s_templates = array(); 755 | $c_templates = array(); ?> 756 | 757 | 760 | 761 | managers as $manager ) { 762 | 763 | if ( ! $manager->check_capabilities() ) 764 | continue; 765 | 766 | if ( ! in_array( $manager->type, $m_templates ) ) { 767 | $m_templates[] = $manager->type; 768 | 769 | $manager->print_template(); 770 | } 771 | 772 | foreach ( $manager->sections as $section ) { 773 | 774 | if ( ! in_array( $section->type, $s_templates ) ) { 775 | $s_templates[] = $section->type; 776 | 777 | $section->print_template(); 778 | } 779 | } 780 | 781 | foreach ( $manager->controls as $control ) { 782 | 783 | if ( ! in_array( $control->type, $c_templates ) ) { 784 | $c_templates[] = $control->type; 785 | 786 | $control->print_template(); 787 | } 788 | } 789 | } 790 | } 791 | 792 | /** 793 | * Renders our Backbone views. We're calling this late in the page load so 794 | * that other scripts have an opportunity to extend with their own, custom 795 | * views for custom controls and such. 796 | * 797 | * @since 1.0.0 798 | * @access public 799 | * @return void 800 | */ 801 | public function render_views() { ?> 802 | 803 | 810 | managers as $manager ) { 829 | 830 | if ( $manager->check_capabilities() ) 831 | $manager->save( $post_id ); 832 | } 833 | } 834 | 835 | /** 836 | * Helper method for sorting sections and controls by priority. 837 | * 838 | * @since 1.0.0 839 | * @access protected 840 | * @param object $a 841 | * @param object $b 842 | * @return int 843 | */ 844 | protected function priority_sort( $a, $b ) { 845 | 846 | if ( $a->priority === $b->priority ) 847 | return $a->instance_number - $b->instance_number; 848 | 849 | return $a->priority - $b->priority; 850 | } 851 | } 852 | 853 | /** 854 | * Gets the instance of the `ButterBean` class. This function is useful for quickly grabbing data 855 | * used throughout the plugin. 856 | * 857 | * @since 1.0.0 858 | * @access public 859 | * @return object 860 | */ 861 | function butterbean() { 862 | return ButterBean::get_instance(); 863 | } 864 | 865 | // Let's do this thang! 866 | butterbean(); 867 | } 868 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The code for the project is handled via its [GitHub Repository](https://github.com/justintadlock/butterbean). You can open tickets, create patches, and send pull requests there. 4 | 5 | ## Pull requests 6 | 7 | Problem first. Solution second. 8 | 9 | Pull requests should have a ticket open for discussion first. I rarely accept pull requests that aren't for a specific issue for various reasons. It's far better to post an issue and let me or the community provide feedback prior to creating a pull request. 10 | 11 | Please don't make pull requests against the `master` branch. This is the latest, stable code. You can make a pull request against one of the point branches or the `dev` (future release) branch. 12 | 13 | ## Coding standards 14 | 15 | In general, the project follows all WordPress [coding standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards). There are instances where it doesn't, opting for personal choices of my own, but in terms of contributing, following the WordPress standards is best practice. 16 | 17 | ## Script and style files 18 | 19 | The project consists of several script and style files. When making patches or pull requests with changes to these files, only do so to the primary file. Don't create patches for the minified (`.min`) versions of the files. Those will be minified after a patch is merged into the code base. 20 | 21 | ## Language 22 | 23 | All text strings follow U.S. English by default. While such guides are generally unneeded, in cases where style considerations are necessary, these will typically follow conventions laid out in *Elements of Style* or the *AP Stylebook*. 24 | 25 | ## Licensing 26 | 27 | Any code contributed to the project via patches, pull requests, or other means will be licensed under the [GPL version 2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) or later. By contributing code to the project, you provide consent to use such code under this license. The exception to this rule is when bringing in third-party code with an alternate open source license. 28 | 29 | ## Versioning 30 | 31 | The project uses [semantic versioning](http://semver.org). Version numbers will look like `3.2.1` where `3` is the "major" release, `2` is the minor release, and `1` is the patch release. 32 | -------------------------------------------------------------------------------- /css/butterbean.css: -------------------------------------------------------------------------------- 1 | 2 | /* Wrapper box */ 3 | 4 | .butterbean-ui { } 5 | 6 | .butterbean-ui > .hndle { 7 | padding: 10px !important; 8 | border-bottom: 1px solid #eee; 9 | } 10 | 11 | .butterbean-ui .inside { 12 | margin: 0 !important; 13 | padding: 0; 14 | } 15 | 16 | /* Tabs wrapper. */ 17 | 18 | .butterbean-manager-default { 19 | overflow: hidden; 20 | background: #fff; 21 | background: linear-gradient( 90deg, #fafafa 0%, #fafafa 180px, #fff 180px, #fff 100% ); 22 | } 23 | 24 | #side-sortables .butterbean-manager-default { 25 | background: linear-gradient( 90deg, #fafafa 0%, #fafafa 48px, #fff 48px, #fff 100% ); 26 | } 27 | 28 | @media only screen and ( max-width: 782px ), ( max-width: 980px ) and ( min-width: 851px ) { 29 | 30 | .butterbean-manager-default { 31 | background: linear-gradient( 90deg, #fafafa 0%, #fafafa 48px, #fff 48px, #fff 100% ); 32 | } 33 | } 34 | 35 | /* Tab nav. */ 36 | 37 | .butterbean-manager-default .butterbean-nav { 38 | position: relative; 39 | float: left; 40 | list-style: none; 41 | width: 180px;/*20%;*/ 42 | line-height: 1em; 43 | margin: 0 0 -1px 0; 44 | padding: 0; 45 | background-color: #fafafa; 46 | border-right: 1px solid #eee; 47 | box-sizing: border-box; 48 | } 49 | 50 | .butterbean-manager-default .butterbean-nav li { 51 | display: block; 52 | position: relative; 53 | margin: 0; 54 | padding: 0; 55 | line-height: 20px; 56 | } 57 | 58 | .butterbean-manager-default .butterbean-nav li a { 59 | display: block; 60 | margin: 0; 61 | padding: 10px; 62 | line-height: 20px !important; 63 | text-decoration: none; 64 | border-bottom: 1px solid #eee; 65 | box-shadow: none; 66 | } 67 | 68 | .butterbean-manager-default .butterbean-nav .dashicons { 69 | line-height: 20px; 70 | margin-right: 3px; 71 | } 72 | 73 | .butterbean-manager-default .butterbean-nav li[aria-selected="true"] a { 74 | position: relative; 75 | font-weight: bold; 76 | color: #555; 77 | background-color: #e0e0e0; 78 | } 79 | 80 | @media only screen and ( max-width: 782px ), ( max-width: 980px ) and ( min-width: 851px ) { 81 | .butterbean-manager-default .butterbean-nav { width: 48px; } 82 | 83 | .butterbean-manager-default .butterbean-nav .dashicons { 84 | width: 24px; 85 | height: 24px; 86 | font-size: 24px; 87 | line-height: 24px; 88 | } 89 | 90 | .butterbean-manager-default .butterbean-nav .dashicons::before { 91 | width: 24px; 92 | height: 24px; 93 | } 94 | 95 | .butterbean-manager-default .butterbean-nav .label { 96 | overflow: hidden; 97 | position: absolute; 98 | top: -1000em; 99 | left: -1000em; 100 | width: 1px; 101 | height: 1px; 102 | } 103 | } 104 | 105 | /* Tab content wrapper */ 106 | 107 | .butterbean-manager-default .butterbean-content { 108 | float: left; 109 | width: calc( 100% - 180px ); 110 | margin-left: -1px; 111 | border-left: 1px solid #eee; 112 | } 113 | 114 | @media only screen and ( max-width: 782px ), ( max-width: 980px ) and ( min-width: 851px ) { 115 | 116 | .butterbean-manager-default .butterbean-content { 117 | width: calc( 100% - 48px ); 118 | } 119 | } 120 | 121 | /* === Manager when in the side meta box. === */ 122 | 123 | @media only screen and ( min-width: 850px ) { 124 | 125 | #side-sortables .butterbean-manager-default { background: #fff; } 126 | 127 | #side-sortables .butterbean-manager-default .butterbean-content { width: 100%; } 128 | 129 | #side-sortables .butterbean-manager-default .butterbean-nav { 130 | display: table; 131 | width: 100%; 132 | } 133 | 134 | #side-sortables .butterbean-manager-default .butterbean-nav li { 135 | display: table-cell; 136 | text-align: center; 137 | border-right: 1px solid #eee; 138 | } 139 | 140 | #side-sortables .butterbean-manager-default .butterbean-nav li:last-of-type { border-right: none; } 141 | 142 | #side-sortables .butterbean-manager-default .butterbean-nav li a { 143 | padding: 10px 0; 144 | } 145 | 146 | #side-sortables .butterbean-manager-default .butterbean-nav .dashicons { 147 | width: 24px; 148 | height: 24px; 149 | font-size: 24px; 150 | line-height: 24px; 151 | } 152 | 153 | #side-sortables .butterbean-manager-default .butterbean-nav .dashicons::before { 154 | width: 24px; 155 | height: 24px; 156 | } 157 | 158 | #side-sortables .butterbean-manager-default .butterbean-nav .label { 159 | overflow: hidden; 160 | position: absolute; 161 | top: -1000em; 162 | left: -1000em; 163 | width: 1px; 164 | height: 1px; 165 | } 166 | } 167 | 168 | 169 | /* === Content === */ 170 | 171 | .butterbean-manager-default .butterbean-section { 172 | padding: 12px 12px 0; 173 | box-sizing: border-box; 174 | } 175 | 176 | .butterbean-manager-default .butterbean-section[aria-hidden="true"] { display: none; } 177 | .butterbean-manager-default .butterbean-section[aria-hidden="false"] { display: block; } 178 | 179 | .butterbean-manager-default .butterbean-control { 180 | margin-bottom: 20px; 181 | } 182 | 183 | .butterbean-manager-default .butterbean-label { 184 | display : block !important; /* this is getting overwritten somewhere */ 185 | font-weight : bold; 186 | display : inline-block; 187 | margin-bottom : 4px; 188 | } 189 | 190 | .butterbean-manager-default .butterbean-control-checkbox .butterbean-label { 191 | display: inline !important; 192 | } 193 | 194 | .butterbean-manager-default .butterbean-description { 195 | display : block; 196 | font-style : italic; 197 | margin-top : 4px; 198 | } 199 | 200 | .butterbean-manager-default .butterbean-label + .butterbean-description { 201 | margin-top : 0; 202 | margin-bottom : 4px; 203 | } 204 | 205 | /* === Media === */ 206 | 207 | .butterbean-control-image .butterbean-img { 208 | display: block; 209 | max-width: 100%; 210 | max-height: 300px; 211 | height: auto; 212 | } 213 | 214 | .butterbean-placeholder { 215 | width: 100%; 216 | position: relative; 217 | text-align: center; 218 | padding: 9px 0; 219 | line-height: 20px; 220 | border: 1px dashed rgb(180, 185, 190); 221 | box-sizing: border-box; 222 | } 223 | 224 | /* === Textarea Control === */ 225 | 226 | .butterbean-control-textarea textarea, 227 | .butterbean-control-excerpt textarea { 228 | display: block; 229 | width: 100%; 230 | height: 105px; 231 | } 232 | 233 | /* === Date Control === */ 234 | 235 | .butterbean-control-datetime select { 236 | vertical-align: top; 237 | } 238 | 239 | /* === Palette Control === */ 240 | 241 | .butterbean-control-palette label { 242 | display: block; 243 | padding: 0 10px 10px; 244 | } 245 | 246 | .butterbean-control-palette label[aria-selected="true"] { 247 | padding-top: 5px; 248 | background-color: #ddd; 249 | } 250 | 251 | .butterbean-palette-label { 252 | line-height: 28px; 253 | } 254 | 255 | .butterbean-palette-block { 256 | display: table; 257 | width: 100%; 258 | height: 45px; 259 | border: 1px solid rgb(238, 238, 238); 260 | box-sizing: border-box; 261 | } 262 | 263 | .butterbean-palette-color { 264 | display: table-cell; 265 | height: 100%; 266 | } 267 | 268 | /* === Radio Image Control === */ 269 | 270 | .butterbean-control-radio-image input[type="radio"] { 271 | clip: rect(1px, 1px, 1px, 1px); 272 | height: 1px; 273 | overflow: hidden; 274 | position: absolute !important; 275 | width: 1px; 276 | } 277 | 278 | .butterbean-control-radio-image img { 279 | box-sizing: border-box; 280 | max-width: 100%; 281 | height: auto; 282 | padding: 1px; 283 | border: 4px solid transparent; 284 | } 285 | 286 | .butterbean-control-radio-image img:hover, 287 | .butterbean-control-radio-image img:focus { 288 | border-color: #ccc; 289 | } 290 | 291 | .butterbean-control-radio-image input:checked + span + img { 292 | border-color: #00a0d2; 293 | } 294 | 295 | /* === Multi-avatars Control === */ 296 | 297 | .butterbean-multi-avatars-wrap label { 298 | display: inline-block; 299 | margin-top: 8px; 300 | } 301 | 302 | .butterbean-multi-avatars-wrap input[type="checkbox"] { 303 | clip: rect( 1px, 1px, 1px, 1px ); 304 | height: 1px; 305 | overflow: hidden; 306 | position: absolute !important; 307 | width: 1px; 308 | } 309 | 310 | .butterbean-multi-avatars-wrap .avatar { 311 | box-sizing: border-box; 312 | max-width: 100%; 313 | height: auto; 314 | padding: 1px; 315 | border: 4px solid transparent; 316 | } 317 | 318 | #side-sortables .butterbean-multi-avatars-wrap .avatar { 319 | max-width: 60px; 320 | max-height: 60px; 321 | } 322 | 323 | .butterbean-multi-avatars-wrap img:hover, 324 | .butterbean-multi-avatars-wrap img:focus { 325 | border-color: #ccc; 326 | } 327 | 328 | .butterbean-multi-avatars-wrap input:checked + span + img { 329 | border-color: #00a0d2; 330 | } 331 | -------------------------------------------------------------------------------- /css/butterbean.min.css: -------------------------------------------------------------------------------- 1 | .butterbean-ui>.hndle{padding:10px!important;border-bottom:1px solid #eee}.butterbean-ui .inside{margin:0!important;padding:0}.butterbean-manager-default{overflow:hidden;background:#fff;background:linear-gradient(90deg,#fafafa 0,#fafafa 180px,#fff 180px,#fff 100%)}#side-sortables .butterbean-manager-default{background:linear-gradient(90deg,#fafafa 0,#fafafa 48px,#fff 48px,#fff 100%)}@media only screen and (max-width:782px),(max-width:980px) and (min-width:851px){.butterbean-manager-default{background:linear-gradient(90deg,#fafafa 0,#fafafa 48px,#fff 48px,#fff 100%)}}.butterbean-manager-default .butterbean-nav{position:relative;float:left;list-style:none;width:180px;line-height:1em;margin:0 0 -1px 0;padding:0;background-color:#fafafa;border-right:1px solid #eee;box-sizing:border-box}.butterbean-manager-default .butterbean-nav li{display:block;position:relative;margin:0;padding:0;line-height:20px}.butterbean-manager-default .butterbean-nav li a{display:block;margin:0;padding:10px;line-height:20px!important;text-decoration:none;border-bottom:1px solid #eee;box-shadow:none}.butterbean-manager-default .butterbean-nav .dashicons{line-height:20px;margin-right:3px}.butterbean-manager-default .butterbean-nav li[aria-selected=true] a{position:relative;font-weight:700;color:#555;background-color:#e0e0e0}@media only screen and (max-width:782px),(max-width:980px) and (min-width:851px){.butterbean-manager-default .butterbean-nav{width:48px}.butterbean-manager-default .butterbean-nav .dashicons{width:24px;height:24px;font-size:24px;line-height:24px}.butterbean-manager-default .butterbean-nav .dashicons::before{width:24px;height:24px}.butterbean-manager-default .butterbean-nav .label{overflow:hidden;position:absolute;top:-1000em;left:-1000em;width:1px;height:1px}}.butterbean-manager-default .butterbean-content{float:left;width:calc(100% - 180px);margin-left:-1px;border-left:1px solid #eee}@media only screen and (max-width:782px),(max-width:980px) and (min-width:851px){.butterbean-manager-default .butterbean-content{width:calc(100% - 48px)}}@media only screen and (min-width:850px){#side-sortables .butterbean-manager-default{background:#fff}#side-sortables .butterbean-manager-default .butterbean-content{width:100%}#side-sortables .butterbean-manager-default .butterbean-nav{display:table;width:100%}#side-sortables .butterbean-manager-default .butterbean-nav li{display:table-cell;text-align:center;border-right:1px solid #eee}#side-sortables .butterbean-manager-default .butterbean-nav li:last-of-type{border-right:0}#side-sortables .butterbean-manager-default .butterbean-nav li a{padding:10px 0}#side-sortables .butterbean-manager-default .butterbean-nav .dashicons{width:24px;height:24px;font-size:24px;line-height:24px}#side-sortables .butterbean-manager-default .butterbean-nav .dashicons::before{width:24px;height:24px}#side-sortables .butterbean-manager-default .butterbean-nav .label{overflow:hidden;position:absolute;top:-1000em;left:-1000em;width:1px;height:1px}}.butterbean-manager-default .butterbean-section{padding:12px 12px 0;box-sizing:border-box}.butterbean-manager-default .butterbean-section[aria-hidden=true]{display:none}.butterbean-manager-default .butterbean-section[aria-hidden=false]{display:block}.butterbean-manager-default .butterbean-control{margin-bottom:20px}.butterbean-manager-default .butterbean-label{display:block!important;font-weight:700;display:inline-block;margin-bottom:4px}.butterbean-manager-default .butterbean-control-checkbox .butterbean-label{display:inline!important}.butterbean-manager-default .butterbean-description{display:block;font-style:italic;margin-top:4px}.butterbean-manager-default .butterbean-label+.butterbean-description{margin-top:0;margin-bottom:4px}.butterbean-control-image .butterbean-img{display:block;max-width:100%;max-height:300px;height:auto}.butterbean-placeholder{width:100%;position:relative;text-align:center;padding:9px 0;line-height:20px;border:1px dashed #b4b9be;box-sizing:border-box}.butterbean-control-textarea textarea,.butterbean-control-excerpt textarea{display:block;width:100%;height:105px}.butterbean-control-datetime select{vertical-align:top}.butterbean-control-palette label{display:block;padding:0 10px 10px}.butterbean-control-palette label[aria-selected=true]{padding-top:5px;background-color:#ddd}.butterbean-palette-label{line-height:28px}.butterbean-palette-block{display:table;width:100%;height:45px;border:1px solid #eee;box-sizing:border-box}.butterbean-palette-color{display:table-cell;height:100%}.butterbean-control-radio-image input[type=radio]{clip:rect(1px,1px,1px,1px);height:1px;overflow:hidden;position:absolute!important;width:1px}.butterbean-control-radio-image img{box-sizing:border-box;max-width:100%;height:auto;padding:1px;border:4px solid transparent}.butterbean-control-radio-image img:hover,.butterbean-control-radio-image img:focus{border-color:#ccc}.butterbean-control-radio-image input:checked+span+img{border-color:#00a0d2}.butterbean-multi-avatars-wrap label{display:inline-block;margin-top:8px}.butterbean-multi-avatars-wrap input[type=checkbox]{clip:rect(1px,1px,1px,1px);height:1px;overflow:hidden;position:absolute!important;width:1px}.butterbean-multi-avatars-wrap .avatar{box-sizing:border-box;max-width:100%;height:auto;padding:1px;border:4px solid transparent}#side-sortables .butterbean-multi-avatars-wrap .avatar{max-width:60px;max-height:60px}.butterbean-multi-avatars-wrap img:hover,.butterbean-multi-avatars-wrap img:focus{border-color:#ccc}.butterbean-multi-avatars-wrap input:checked+span+img{border-color:#00a0d2} -------------------------------------------------------------------------------- /inc/class-control.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 9 | * @link https://github.com/justintadlock/butterbean 10 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 11 | */ 12 | 13 | /** 14 | * Base control class. 15 | * 16 | * @since 1.0.0 17 | * @access public 18 | */ 19 | class ButterBean_Control { 20 | 21 | /** 22 | * Stores the manager object. 23 | * 24 | * @since 1.0.0 25 | * @access public 26 | * @var object 27 | */ 28 | public $manager; 29 | 30 | /** 31 | * Name/ID of the control. 32 | * 33 | * @since 1.0.0 34 | * @access public 35 | * @var string 36 | */ 37 | public $name = ''; 38 | 39 | /** 40 | * Label for the control. 41 | * 42 | * @since 1.0.0 43 | * @access public 44 | * @var string 45 | */ 46 | public $label = ''; 47 | 48 | /** 49 | * Description for the control. 50 | * 51 | * @since 1.0.0 52 | * @access public 53 | * @var string 54 | */ 55 | public $description = ''; 56 | 57 | /** 58 | * ID of the section the control is for. 59 | * 60 | * @since 1.0.0 61 | * @access public 62 | * @var string 63 | */ 64 | public $section = ''; 65 | 66 | /** 67 | * The setting key for the specific setting the control is tied to. 68 | * Controls can have multiple settings attached to them. The default 69 | * setting is `default`. 70 | * 71 | * @since 1.0.0 72 | * @access public 73 | * @var string 74 | */ 75 | public $setting = 'default'; 76 | 77 | /** 78 | * Array of settings if the control has multiple settings. 79 | * 80 | * @since 1.0.0 81 | * @access public 82 | * @var array 83 | */ 84 | public $settings = array(); 85 | 86 | /** 87 | * The type of control. 88 | * 89 | * @since 1.0.0 90 | * @access public 91 | * @var string 92 | */ 93 | public $type = 'text'; 94 | 95 | /** 96 | * Form field attributes. 97 | * 98 | * @since 1.0.0 99 | * @access public 100 | * @var array 101 | */ 102 | public $attr = ''; 103 | 104 | /** 105 | * Choices for fields with multiple choices. 106 | * 107 | * @since 1.0.0 108 | * @access public 109 | * @var array 110 | */ 111 | public $choices = array(); 112 | 113 | /** 114 | * Priority (order) the control should be output. 115 | * 116 | * @since 1.0.0 117 | * @access public 118 | * @var int 119 | */ 120 | public $priority = 10; 121 | 122 | /** 123 | * The number of instances created. 124 | * 125 | * @since 1.0.0 126 | * @access protected 127 | * @var int 128 | */ 129 | protected static $instance_count = 0; 130 | 131 | /** 132 | * The instance of the current control. 133 | * 134 | * @since 1.0.0 135 | * @access public 136 | * @var int 137 | */ 138 | public $instance_number; 139 | 140 | /** 141 | * A callback function for deciding if a control is active. 142 | * 143 | * @since 1.0.0 144 | * @access public 145 | * @var callable 146 | */ 147 | public $active_callback = ''; 148 | 149 | /** 150 | * A user role capability required to show the control. 151 | * 152 | * @since 1.0.0 153 | * @access public 154 | * @var string|array 155 | */ 156 | public $capability = ''; 157 | 158 | /** 159 | * A feature that the current post type must support to show the control. 160 | * 161 | * @since 1.0.0 162 | * @access public 163 | * @var string 164 | */ 165 | public $post_type_supports = ''; 166 | 167 | /** 168 | * A feature that the current theme must support to show the control. 169 | * 170 | * @since 1.0.0 171 | * @access public 172 | * @var string|array 173 | */ 174 | public $theme_supports = ''; 175 | 176 | /** 177 | * Stores the JSON data for the control. 178 | * 179 | * @since 1.0.0 180 | * @access public 181 | * @var array() 182 | */ 183 | public $json = array(); 184 | 185 | /** 186 | * Creates a new control object. 187 | * 188 | * @since 1.0.0 189 | * @access public 190 | * @param object $manager 191 | * @param string $name 192 | * @param array $args 193 | * @return void 194 | */ 195 | public function __construct( $manager, $name, $args = array() ) { 196 | 197 | foreach ( array_keys( get_object_vars( $this ) ) as $key ) { 198 | 199 | if ( isset( $args[ $key ] ) ) 200 | $this->$key = $args[ $key ]; 201 | } 202 | 203 | $this->manager = $manager; 204 | $this->name = $name; 205 | 206 | if ( empty( $args['settings'] ) || ! is_array( $args['settings'] ) ) 207 | $this->settings['default'] = $name; 208 | 209 | // Increment the instance count and set the instance number. 210 | self::$instance_count += 1; 211 | $this->instance_number = self::$instance_count; 212 | 213 | // Set the active callback function if not set. 214 | if ( ! $this->active_callback ) 215 | $this->active_callback = array( $this, 'active_callback' ); 216 | } 217 | 218 | /** 219 | * Enqueue scripts/styles for the control. 220 | * 221 | * @since 1.0.0 222 | * @access public 223 | * @return void 224 | */ 225 | public function enqueue() {} 226 | 227 | /** 228 | * Get the value for the setting. 229 | * 230 | * @since 1.0.0 231 | * @access public 232 | * @param string $setting 233 | * @return mixed 234 | */ 235 | public function get_value( $setting = 'default' ) { 236 | 237 | $setting = $this->get_setting( $setting ); 238 | 239 | return $setting ? $setting->get_value() : ''; 240 | } 241 | 242 | /** 243 | * Returns the setting object associated with this control. If no setting is 244 | * found, `false` is returned. 245 | * 246 | * @since 1.0.0 247 | * @access public 248 | * @param string $setting 249 | * @return object|bool 250 | */ 251 | public function get_setting( $setting = 'default' ) { 252 | 253 | return $this->manager->get_setting( $this->settings[ $setting ] ); 254 | } 255 | 256 | /** 257 | * Gets the attributes for the control. 258 | * 259 | * @since 1.0.0 260 | * @access public 261 | * @return array 262 | */ 263 | public function get_attr() { 264 | 265 | $defaults = array(); 266 | 267 | if ( isset( $this->settings[ $this->setting ] ) ) 268 | $defaults['name'] = $this->get_field_name( $this->setting ); 269 | 270 | return wp_parse_args( $this->attr, $defaults ); 271 | } 272 | 273 | /** 274 | * Returns the HTML field name for the control. 275 | * 276 | * @since 1.0.0 277 | * @access public 278 | * @param string $setting 279 | * @return array 280 | */ 281 | public function get_field_name( $setting = 'default' ) { 282 | 283 | return "butterbean_{$this->manager->name}_setting_{$this->settings[ $setting ]}"; 284 | } 285 | 286 | /** 287 | * Returns the json array. 288 | * 289 | * @since 1.0.0 290 | * @access public 291 | * @return array 292 | */ 293 | public function get_json() { 294 | $this->to_json(); 295 | 296 | return $this->json; 297 | } 298 | 299 | /** 300 | * Adds custom data to the json array. This data is passed to the Underscore template. 301 | * 302 | * @since 1.0.0 303 | * @access public 304 | * @return void 305 | */ 306 | public function to_json() { 307 | 308 | $this->json['manager'] = $this->manager->name; 309 | $this->json['section'] = $this->section; 310 | $this->json['setting'] = $this->setting; 311 | $this->json['settings'] = $this->settings; 312 | $this->json['name'] = $this->name; 313 | $this->json['label'] = $this->label; 314 | $this->json['type'] = $this->type; 315 | $this->json['description'] = $this->description; 316 | $this->json['choices'] = $this->choices; 317 | $this->json['active'] = $this->is_active(); 318 | 319 | $this->json['value'] = isset( $this->settings[ $this->setting ] ) ? $this->get_value( $this->setting ) : ''; 320 | $this->json['field_name'] = isset( $this->settings[ $this->setting ] ) ? $this->get_field_name( $this->setting ) : ''; 321 | 322 | $this->json['attr'] = ''; 323 | 324 | foreach ( $this->get_attr() as $attr => $value ) { 325 | $this->json['attr'] .= sprintf( '%s="%s" ', esc_html( $attr ), esc_attr( $value ) ); 326 | } 327 | } 328 | 329 | /** 330 | * Returns whether the control is active. 331 | * 332 | * @since 1.0.0 333 | * @access public 334 | * @return bool 335 | */ 336 | public function is_active() { 337 | 338 | $is_active = call_user_func( $this->active_callback, $this ); 339 | 340 | return apply_filters( 'butterbean_is_control_active', $is_active, $this ); 341 | } 342 | 343 | /** 344 | * Default active callback. 345 | * 346 | * @since 1.0.0 347 | * @access public 348 | * @return bool 349 | */ 350 | public function active_callback() { 351 | return true; 352 | } 353 | 354 | /** 355 | * Checks if the control should be allowed at all. 356 | * 357 | * @since 1.0.0 358 | * @access public 359 | * @return bool 360 | */ 361 | public function check_capabilities() { 362 | 363 | if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) ) 364 | return false; 365 | 366 | if ( $this->post_type_supports && ! call_user_func_array( 'post_type_supports', array( get_post_type( $this->manager->post_id ), $this->post_type_supports ) ) ) 367 | return false; 368 | 369 | if ( $this->theme_supports && ! call_user_func_array( 'theme_supports', (array) $this->theme_supports ) ) 370 | return false; 371 | 372 | return true; 373 | } 374 | 375 | /** 376 | * Prints Underscore.js template. 377 | * 378 | * @since 1.0.0 379 | * @access public 380 | * @return void 381 | */ 382 | public function print_template() { ?> 383 | 384 | 387 | type ); 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /inc/class-manager.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 10 | * @link https://github.com/justintadlock/butterbean 11 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 12 | */ 13 | 14 | /** 15 | * Base manager class. 16 | * 17 | * @since 1.0.0 18 | * @access public 19 | */ 20 | class ButterBean_Manager { 21 | 22 | /** 23 | * The type of manager. 24 | * 25 | * @since 1.0.0 26 | * @access public 27 | * @var string 28 | */ 29 | public $type = 'default'; 30 | 31 | /** 32 | * Name of this instance of the manager. 33 | * 34 | * @since 1.0.0 35 | * @access public 36 | * @var string 37 | */ 38 | public $name = ''; 39 | 40 | /** 41 | * Label for the manager. 42 | * 43 | * @since 1.0.0 44 | * @access public 45 | * @var string 46 | */ 47 | public $label = ''; 48 | 49 | /** 50 | * Post type this manager is used on. 51 | * 52 | * @since 1.0.0 53 | * @access public 54 | * @var string|array 55 | */ 56 | public $post_type = 'post'; 57 | 58 | /** 59 | * Location of the meta box. Accepted values: 'normal', 'advanced', 'side'. 60 | * 61 | * @link https://developer.wordpress.org/reference/functions/add_meta_box/ 62 | * @since 1.0.0 63 | * @access public 64 | * @var string 65 | */ 66 | public $context = 'advanced'; 67 | 68 | /** 69 | * Priority of the meta box. Accepted values: 'high', 'core', 'default', 'low'. 70 | * 71 | * @link https://developer.wordpress.org/reference/functions/add_meta_box/ 72 | * @since 1.0.0 73 | * @access public 74 | * @var string 75 | */ 76 | public $priority = 'default'; 77 | 78 | /** 79 | * Array of sections. 80 | * 81 | * @since 1.0.0 82 | * @access public 83 | * @var array 84 | */ 85 | public $sections = array(); 86 | 87 | /** 88 | * Array of controls. 89 | * 90 | * @since 1.0.0 91 | * @access public 92 | * @var array 93 | */ 94 | public $controls = array(); 95 | 96 | /** 97 | * Array of settings. 98 | * 99 | * @since 1.0.0 100 | * @access public 101 | * @var array 102 | */ 103 | public $settings = array(); 104 | 105 | /** 106 | * A user role capability required to show the manager. 107 | * 108 | * @since 1.0.0 109 | * @access public 110 | * @var string|array 111 | */ 112 | public $capability = ''; 113 | 114 | /** 115 | * A feature that the current post type must support to show the manager. 116 | * 117 | * @since 1.0.0 118 | * @access public 119 | * @var string 120 | */ 121 | public $post_type_supports = ''; 122 | 123 | /** 124 | * A feature that the current theme must support to show the manager. 125 | * 126 | * @since 1.0.0 127 | * @access public 128 | * @var string|array 129 | */ 130 | public $theme_supports = ''; 131 | 132 | /** 133 | * Stores the JSON data for the manager. 134 | * 135 | * @since 1.0.0 136 | * @access public 137 | * @var array() 138 | */ 139 | public $json = array(); 140 | 141 | /** 142 | * ID of the post that's being edited. 143 | * 144 | * @since 1.0.0 145 | * @access public 146 | * @var int 147 | */ 148 | public $post_id = 0; 149 | 150 | /** 151 | * Sets up the manager. 152 | * 153 | * @since 1.0.0 154 | * @access public 155 | * @param string $name 156 | * @param array $args 157 | * @return void 158 | */ 159 | public function __construct( $name, $args = array() ) { 160 | 161 | foreach ( array_keys( get_object_vars( $this ) ) as $key ) { 162 | 163 | if ( isset( $args[ $key ] ) ) 164 | $this->$key = $args[ $key ]; 165 | } 166 | 167 | // Make sure the post type is an array. 168 | $this->post_type = (array) $this->post_type; 169 | 170 | // Set the manager name. 171 | $this->name = sanitize_key( $name ); 172 | } 173 | 174 | /** 175 | * Enqueue scripts/styles for the manager. 176 | * 177 | * @since 1.0.0 178 | * @access public 179 | * @return void 180 | */ 181 | public function enqueue() {} 182 | 183 | /** 184 | * Register a section. 185 | * 186 | * @since 1.0.0 187 | * @access public 188 | * @param object|string $section 189 | * @param array $args 190 | * @return void 191 | */ 192 | public function register_section( $section, $args = array() ) { 193 | 194 | if ( ! is_object( $section ) ) { 195 | 196 | $type = isset( $args['type'] ) ? butterbean()->get_section_type( $args['type'] ) : butterbean()->get_section_type( 'default' ); 197 | 198 | $section = new $type( $this, $section, $args ); 199 | } 200 | 201 | if ( ! $this->section_exists( $section->name ) ) 202 | $this->sections[ $section->name ] = $section; 203 | } 204 | 205 | /** 206 | * Register a control. 207 | * 208 | * @since 1.0.0 209 | * @access public 210 | * @param object|string $control 211 | * @param array $args 212 | * @return void 213 | */ 214 | public function register_control( $control, $args = array() ) { 215 | 216 | if ( ! is_object( $control ) ) { 217 | 218 | $type = isset( $args['type'] ) ? butterbean()->get_control_type( $args['type'] ) : butterbean()->get_control_type( 'default' ); 219 | 220 | $control = new $type( $this, $control, $args ); 221 | } 222 | 223 | if ( ! $this->control_exists( $control->name ) ) 224 | $this->controls[ $control->name ] = $control; 225 | } 226 | 227 | /** 228 | * Register a setting. 229 | * 230 | * @since 1.0.0 231 | * @access public 232 | * @param object|string $setting 233 | * @param array $args 234 | * @return void 235 | */ 236 | public function register_setting( $setting, $args = array() ) { 237 | 238 | if ( ! is_object( $setting ) ) { 239 | 240 | $type = isset( $args['type'] ) ? butterbean()->get_setting_type( $args['type'] ) : butterbean()->get_setting_type( 'default' ); 241 | 242 | $setting = new $type( $this, $setting, $args ); 243 | } 244 | 245 | if ( ! $this->setting_exists( $setting->name ) ) 246 | $this->settings[ $setting->name ] = $setting; 247 | } 248 | 249 | /** 250 | * Register a control and setting object. 251 | * 252 | * @since 1.0.0 253 | * @access public 254 | * @param string $name 255 | * @param object|array $control Control object or array of control arguments. 256 | * @param object|array $setting Setting object or array of setting arguments. 257 | * @return void 258 | */ 259 | public function register_field( $name, $control, $setting ) { 260 | 261 | is_object( $control ) ? $this->register_control( $control ) : $this->register_control( $name, $control ); 262 | is_object( $setting ) ? $this->register_setting( $setting ) : $this->register_setting( $name, $setting ); 263 | } 264 | 265 | /** 266 | * Unregisters a section object. 267 | * 268 | * @since 1.0.0 269 | * @access public 270 | * @param string $name 271 | * @return void 272 | */ 273 | public function unregister_section( $name ) { 274 | 275 | if ( $this->section_exists( $name ) ) 276 | unset( $this->sections[ $name ] ); 277 | } 278 | 279 | /** 280 | * Unregisters a control object. 281 | * 282 | * @since 1.0.0 283 | * @access public 284 | * @param string $name 285 | * @return void 286 | */ 287 | public function unregister_control( $name ) { 288 | 289 | if ( $this->control_exists( $name ) ) 290 | unset( $this->controls[ $name ] ); 291 | } 292 | 293 | /** 294 | * Unregisters a setting object. 295 | * 296 | * @since 1.0.0 297 | * @access public 298 | * @param string $name 299 | * @return void 300 | */ 301 | public function unregister_setting( $name ) { 302 | 303 | if ( $this->setting_exists( $name ) ) 304 | unset( $this->settings[ $name ] ); 305 | } 306 | 307 | /** 308 | * Unregisters a control and setting object. 309 | * 310 | * @since 1.0.0 311 | * @access public 312 | * @param string $name 313 | * @return void 314 | */ 315 | public function unregister_field( $name ) { 316 | 317 | $this->unregister_control( $name ); 318 | $this->unregister_setting( $name ); 319 | } 320 | 321 | /** 322 | * Returns a section object. 323 | * 324 | * @since 1.0.0 325 | * @access public 326 | * @param string $name 327 | * @return object|bool 328 | */ 329 | public function get_section( $name ) { 330 | 331 | return $this->section_exists( $name ) ? $this->sections[ $name ] : false; 332 | } 333 | 334 | /** 335 | * Returns a control object. 336 | * 337 | * @since 1.0.0 338 | * @access public 339 | * @param string $name 340 | * @return object|bool 341 | */ 342 | public function get_control( $name ) { 343 | 344 | return $this->control_exists( $name ) ? $this->controls[ $name ] : false; 345 | } 346 | 347 | /** 348 | * Returns a setting object. 349 | * 350 | * @since 1.0.0 351 | * @access public 352 | * @param string $name 353 | * @return object|bool 354 | */ 355 | public function get_setting( $name ) { 356 | 357 | return $this->setting_exists( $name ) ? $this->settings[ $name ] : false; 358 | } 359 | 360 | /** 361 | * Returns an object that contains both the control and setting objects. 362 | * 363 | * @since 1.0.0 364 | * @access public 365 | * @param string $name 366 | * @return object|bool 367 | */ 368 | public function get_field( $name ) { 369 | 370 | $control = $this->get_control( $name ); 371 | $setting = $this->get_setting( $name ); 372 | 373 | $field = array( 'name' => $name, 'control' => $control, 'setting' => $setting ); 374 | 375 | return $control && $setting ? (object) $field : false; 376 | } 377 | 378 | /** 379 | * Checks if a section exists. 380 | * 381 | * @since 1.0.0 382 | * @access public 383 | * @param string $name 384 | * @return bool 385 | */ 386 | public function section_exists( $name ) { 387 | 388 | return isset( $this->sections[ $name ] ); 389 | } 390 | 391 | /** 392 | * Checks if a control exists. 393 | * 394 | * @since 1.0.0 395 | * @access public 396 | * @param string $name 397 | * @return bool 398 | */ 399 | public function control_exists( $name ) { 400 | 401 | return isset( $this->controls[ $name ] ); 402 | } 403 | 404 | /** 405 | * Checks if a setting exists. 406 | * 407 | * @since 1.0.0 408 | * @access public 409 | * @param string $name 410 | * @return bool 411 | */ 412 | public function setting_exists( $name ) { 413 | 414 | return isset( $this->settings[ $name ] ); 415 | } 416 | 417 | /** 418 | * Checks if a both a control and setting exist. 419 | * 420 | * @since 1.0.0 421 | * @access public 422 | * @param string $name 423 | * @return bool 424 | */ 425 | public function field_exists( $name ) { 426 | 427 | return $this->control_exists( $name ) && $this->setting_exists( $name ); 428 | } 429 | 430 | /** 431 | * Returns the json array. 432 | * 433 | * @since 1.0.0 434 | * @access public 435 | * @return array 436 | */ 437 | public function get_json() { 438 | $this->to_json(); 439 | 440 | return $this->json; 441 | } 442 | 443 | /** 444 | * Adds custom data to the JSON array. This data is passed to the Underscore template. 445 | * 446 | * @since 1.0.0 447 | * @access public 448 | * @return void 449 | */ 450 | public function to_json() { 451 | 452 | $sections_with_controls = array(); 453 | $blocked_sections = array(); 454 | 455 | $this->json['name'] = $this->name; 456 | $this->json['type'] = $this->type; 457 | 458 | // Get all sections that have controls. 459 | foreach ( $this->controls as $control ) 460 | $sections_with_controls[] = $control->section; 461 | 462 | $sections_with_controls = array_unique( $sections_with_controls ); 463 | 464 | // Get the JSON data for each section. 465 | foreach ( $this->sections as $section ) { 466 | 467 | $caps = $section->check_capabilities(); 468 | 469 | if ( $caps && in_array( $section->name, $sections_with_controls ) ) 470 | $this->json['sections'][] = $section->get_json(); 471 | 472 | if ( ! $caps ) 473 | $blocked_sections[] = $section->name; 474 | } 475 | 476 | // Get the JSON data for each control. 477 | foreach ( $this->controls as $control ) { 478 | 479 | if ( $control->check_capabilities() && ! in_array( $control->section, $blocked_sections ) ) 480 | $this->json['controls'][] = $control->get_json(); 481 | } 482 | } 483 | 484 | /** 485 | * Saves each of the settings for the manager. 486 | * 487 | * @since 1.0.0 488 | * @access public 489 | * @return void 490 | */ 491 | public function save( $post_id ) { 492 | 493 | if ( ! $this->post_id ) 494 | $this->post_id = $post_id; 495 | 496 | // Verify the nonce for this manager. 497 | if ( ! isset( $_POST["butterbean_{$this->name}"] ) || ! wp_verify_nonce( $_POST["butterbean_{$this->name}"], "butterbean_{$this->name}_nonce" ) ) 498 | return; 499 | 500 | // Loop through each setting and save it. 501 | foreach ( $this->settings as $setting ) 502 | $setting->save(); 503 | } 504 | 505 | /** 506 | * Checks if the control should be allowed at all. 507 | * 508 | * @since 1.0.0 509 | * @access public 510 | * @return bool 511 | */ 512 | public function check_capabilities() { 513 | 514 | if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) ) 515 | return false; 516 | 517 | if ( $this->post_type_supports && ! call_user_func_array( 'post_type_supports', array( get_post_type( $this->manager->post_id ), $this->post_type_supports ) ) ) 518 | return false; 519 | 520 | if ( $this->theme_supports && ! call_user_func_array( 'theme_supports', (array) $this->theme_supports ) ) 521 | return false; 522 | 523 | return true; 524 | } 525 | 526 | /** 527 | * Prints Underscore.js template. 528 | * 529 | * @since 1.0.0 530 | * @access public 531 | * @return void 532 | */ 533 | public function print_template() { ?> 534 | 535 | 538 | type ); 549 | } 550 | } 551 | -------------------------------------------------------------------------------- /inc/class-section.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 10 | * @link https://github.com/justintadlock/butterbean 11 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 12 | */ 13 | 14 | /** 15 | * Base section class. 16 | * 17 | * @since 1.0.0 18 | * @access public 19 | */ 20 | class ButterBean_Section { 21 | 22 | /** 23 | * Stores the project details manager object. 24 | * 25 | * @since 1.0.0 26 | * @access public 27 | * @var object 28 | */ 29 | public $manager; 30 | 31 | /** 32 | * Name/ID of the section. 33 | * 34 | * @since 1.0.0 35 | * @access public 36 | * @var string 37 | */ 38 | public $name = ''; 39 | 40 | /** 41 | * The type of section. 42 | * 43 | * @since 1.0.0 44 | * @access public 45 | * @var string 46 | */ 47 | public $type = 'default'; 48 | 49 | /** 50 | * Dashicons icon for the section. 51 | * 52 | * @since 1.0.0 53 | * @access public 54 | * @var string 55 | */ 56 | public $icon = 'dashicons-admin-generic'; 57 | 58 | /** 59 | * Label for the section. 60 | * 61 | * @since 1.0.0 62 | * @access public 63 | * @var string 64 | */ 65 | public $label = ''; 66 | 67 | /** 68 | * Description for the section. 69 | * 70 | * @since 1.0.0 71 | * @access public 72 | * @var string 73 | */ 74 | public $description = ''; 75 | 76 | /** 77 | * Priority (order) the section should be output. 78 | * 79 | * @since 1.0.0 80 | * @access public 81 | * @var int 82 | */ 83 | public $priority = 10; 84 | 85 | /** 86 | * The number of instances created. 87 | * 88 | * @since 1.0.0 89 | * @access protected 90 | * @var int 91 | */ 92 | protected static $instance_count = 0; 93 | 94 | /** 95 | * The instance of the current section. 96 | * 97 | * @since 1.0.0 98 | * @access public 99 | * @var int 100 | */ 101 | public $instance_number; 102 | 103 | /** 104 | * A callback function for deciding if a section is active. 105 | * 106 | * @since 1.0.0 107 | * @access public 108 | * @var callable 109 | */ 110 | public $active_callback = ''; 111 | 112 | /** 113 | * A user role capability required to show the section. 114 | * 115 | * @since 1.0.0 116 | * @access public 117 | * @var string|array 118 | */ 119 | public $capability = ''; 120 | 121 | /** 122 | * A feature that the current post type must support to show the section. 123 | * 124 | * @since 1.0.0 125 | * @access public 126 | * @var string 127 | */ 128 | public $post_type_supports = ''; 129 | 130 | /** 131 | * A feature that the current theme must support to show the section. 132 | * 133 | * @since 1.0.0 134 | * @access public 135 | * @var string|array 136 | */ 137 | public $theme_supports = ''; 138 | 139 | /** 140 | * Stores the JSON data for the manager. 141 | * 142 | * @since 1.0.0 143 | * @access public 144 | * @var array() 145 | */ 146 | public $json = array(); 147 | 148 | /** 149 | * Creates a new section object. 150 | * 151 | * @since 1.0.0 152 | * @access public 153 | * @param object $manager 154 | * @param string $section 155 | * @param array $args 156 | * @return void 157 | */ 158 | public function __construct( $manager, $name, $args = array() ) { 159 | 160 | foreach ( array_keys( get_object_vars( $this ) ) as $key ) { 161 | 162 | if ( isset( $args[ $key ] ) ) 163 | $this->$key = $args[ $key ]; 164 | } 165 | 166 | $this->manager = $manager; 167 | $this->name = $name; 168 | 169 | // Increment the instance count and set the instance number. 170 | self::$instance_count += 1; 171 | $this->instance_number = self::$instance_count; 172 | 173 | // Set the active callback function if not set. 174 | if ( ! $this->active_callback ) 175 | $this->active_callback = array( $this, 'active_callback' ); 176 | } 177 | 178 | /** 179 | * Enqueue scripts/styles for the section. 180 | * 181 | * @since 1.0.0 182 | * @access public 183 | * @return void 184 | */ 185 | public function enqueue() {} 186 | 187 | /** 188 | * Returns the json array. 189 | * 190 | * @since 1.0.0 191 | * @access public 192 | * @return array 193 | */ 194 | public function get_json() { 195 | $this->to_json(); 196 | 197 | return $this->json; 198 | } 199 | 200 | /** 201 | * Adds custom data to the json array. This data is passed to the Underscore template. 202 | * 203 | * @since 1.0.0 204 | * @access public 205 | * @return void 206 | */ 207 | public function to_json() { 208 | 209 | $this->json['manager'] = $this->manager->name; 210 | $this->json['name'] = $this->name; 211 | $this->json['type'] = $this->type; 212 | $this->json['icon'] = preg_match( '/dashicons-/', $this->icon ) ? sprintf( 'dashicons %s', sanitize_html_class( $this->icon ) ) : esc_attr( $this->icon ); 213 | $this->json['label'] = $this->label; 214 | $this->json['description'] = $this->description; 215 | $this->json['active'] = $this->is_active(); 216 | } 217 | 218 | /** 219 | * Returns whether the section is active. 220 | * 221 | * @since 1.0.0 222 | * @access public 223 | * @return bool 224 | */ 225 | public function is_active() { 226 | 227 | $is_active = call_user_func( $this->active_callback, $this ); 228 | 229 | if ( $is_active ) 230 | $is_active = $this->check_capabilities(); 231 | 232 | return apply_filters( 'butterbean_is_section_active', $is_active, $this ); 233 | } 234 | 235 | /** 236 | * Default active callback. 237 | * 238 | * @since 1.0.0 239 | * @access public 240 | * @return bool 241 | */ 242 | public function active_callback() { 243 | return true; 244 | } 245 | 246 | /** 247 | * Checks if the section should be allowed at all. 248 | * 249 | * @since 1.0.0 250 | * @access public 251 | * @return bool 252 | */ 253 | public function check_capabilities() { 254 | 255 | if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) ) 256 | return false; 257 | 258 | if ( $this->post_type_supports && ! call_user_func_array( 'post_type_supports', array( get_post_type( $this->manager->post_id ), $this->post_type_supports ) ) ) 259 | return false; 260 | 261 | if ( $this->theme_supports && ! call_user_func_array( 'theme_supports', (array) $this->theme_supports ) ) 262 | return false; 263 | 264 | return true; 265 | } 266 | 267 | /** 268 | * Prints Underscore.js template. 269 | * 270 | * @since 1.0.0 271 | * @access public 272 | * @return void 273 | */ 274 | public function print_template() { ?> 275 | 276 | 279 | type ); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /inc/class-setting.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 9 | * @link https://github.com/justintadlock/butterbean 10 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 11 | */ 12 | 13 | /** 14 | * Base setting class. 15 | * 16 | * @since 1.0.0 17 | * @access public 18 | */ 19 | class ButterBean_Setting { 20 | 21 | /** 22 | * The type of setting. 23 | * 24 | * @since 1.0.0 25 | * @access public 26 | * @var string 27 | */ 28 | public $type = 'default'; 29 | 30 | /** 31 | * Stores the manager object. 32 | * 33 | * @since 1.0.0 34 | * @access public 35 | * @var object 36 | */ 37 | public $manager; 38 | 39 | /** 40 | * Name/ID of the setting. 41 | * 42 | * @since 1.0.0 43 | * @access public 44 | * @var string 45 | */ 46 | public $name = ''; 47 | 48 | /** 49 | * Value of the setting. 50 | * 51 | * @since 1.0.0 52 | * @access public 53 | * @var string 54 | */ 55 | public $value = ''; 56 | 57 | /** 58 | * Default value of the setting. 59 | * 60 | * @since 1.0.0 61 | * @access public 62 | * @var string 63 | */ 64 | public $default = ''; 65 | 66 | /** 67 | * Sanitization/Validation callback function. 68 | * 69 | * @since 1.0.0 70 | * @access public 71 | * @var string 72 | */ 73 | public $sanitize_callback = ''; 74 | 75 | /** 76 | * A user role capability required to save the setting. 77 | * 78 | * @since 1.0.0 79 | * @access public 80 | * @var string|array 81 | */ 82 | public $capability = ''; 83 | 84 | /** 85 | * A feature that the current post type must support to save the setting. 86 | * 87 | * @since 1.0.0 88 | * @access public 89 | * @var string 90 | */ 91 | public $post_type_supports = ''; 92 | 93 | /** 94 | * A feature that the current theme must support to save the setting. 95 | * 96 | * @since 1.0.0 97 | * @access public 98 | * @var string|array 99 | */ 100 | public $theme_supports = ''; 101 | 102 | /** 103 | * Creates a new setting object. 104 | * 105 | * @since 1.0.0 106 | * @access public 107 | * @param object $manager 108 | * @param string $cap 109 | * @param array $args 110 | * @return void 111 | */ 112 | public function __construct( $manager, $name, $args = array() ) { 113 | 114 | foreach ( array_keys( get_object_vars( $this ) ) as $key ) { 115 | 116 | if ( isset( $args[ $key ] ) ) 117 | $this->$key = $args[ $key ]; 118 | } 119 | 120 | $this->manager = $manager; 121 | $this->name = $name; 122 | 123 | if ( $this->sanitize_callback ) 124 | add_filter( "butterbean_{$this->manager->name}_sanitize_{$this->name}", $this->sanitize_callback, 10, 2 ); 125 | } 126 | 127 | /** 128 | * Gets the value of the setting. 129 | * 130 | * @since 1.0.0 131 | * @access public 132 | * @return mixed 133 | */ 134 | public function get_value() { 135 | 136 | $value = get_post_meta( $this->manager->post_id, $this->name, true ); 137 | 138 | return ! $value && butterbean()->is_new_post ? $this->default : $value; 139 | } 140 | 141 | /** 142 | * Gets the posted value of the setting. 143 | * 144 | * @since 1.0.0 145 | * @access public 146 | * @return mixed 147 | */ 148 | public function get_posted_value() { 149 | 150 | $value = ''; 151 | 152 | if ( isset( $_POST[ $this->get_field_name() ] ) ) 153 | $value = $_POST[ $this->get_field_name() ]; 154 | 155 | return $this->sanitize( $value ); 156 | } 157 | 158 | /** 159 | * Retuns the correct field name for the setting. 160 | * 161 | * @since 1.0.0 162 | * @access public 163 | * @return string 164 | */ 165 | public function get_field_name() { 166 | 167 | return "butterbean_{$this->manager->name}_setting_{$this->name}"; 168 | } 169 | 170 | /** 171 | * Sanitizes the value of the setting. 172 | * 173 | * @since 1.0.0 174 | * @access public 175 | * @return mixed 176 | */ 177 | public function sanitize( $value ) { 178 | 179 | return apply_filters( "butterbean_{$this->manager->name}_sanitize_{$this->name}", $value, $this ); 180 | } 181 | 182 | /** 183 | * Saves the value of the setting. 184 | * 185 | * @since 1.0.0 186 | * @access public 187 | * @return void 188 | */ 189 | public function save() { 190 | 191 | if ( ! $this->check_capabilities() ) 192 | return; 193 | 194 | $old_value = $this->get_value(); 195 | $new_value = $this->get_posted_value(); 196 | 197 | // If we have don't have a new value but do have an old one, delete it. 198 | if ( ! $new_value && $old_value ) 199 | delete_post_meta( $this->manager->post_id, $this->name ); 200 | 201 | // If the new value doesn't match the old value, set it. 202 | else if ( $new_value !== $old_value ) 203 | update_post_meta( $this->manager->post_id, $this->name, $new_value ); 204 | } 205 | 206 | /** 207 | * Checks if the setting should be saved at all. 208 | * 209 | * @since 1.0.0 210 | * @access public 211 | * @return bool 212 | */ 213 | public function check_capabilities() { 214 | 215 | if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) ) 216 | return false; 217 | 218 | if ( $this->post_type_supports && ! call_user_func_array( 'post_type_supports', array( get_post_type( $this->manager->post_id ), $this->post_type_supports ) ) ) 219 | return false; 220 | 221 | if ( $this->theme_supports && ! call_user_func_array( 'theme_supports', (array) $this->theme_supports ) ) 222 | return false; 223 | 224 | return true; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /inc/controls/class-control-checkboxes.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 10 | * @link https://github.com/justintadlock/butterbean 11 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 12 | */ 13 | 14 | /** 15 | * Multiple checkboxes control class. 16 | * 17 | * @since 1.0.0 18 | * @access public 19 | */ 20 | class ButterBean_Control_CheckBoxes extends ButterBean_Control { 21 | 22 | /** 23 | * The type of control. 24 | * 25 | * @since 1.0.0 26 | * @access public 27 | * @var string 28 | */ 29 | public $type = 'checkboxes'; 30 | 31 | /** 32 | * Adds custom data to the json array. This data is passed to the Underscore template. 33 | * 34 | * @since 1.0.0 35 | * @access public 36 | * @return void 37 | */ 38 | public function to_json() { 39 | parent::to_json(); 40 | 41 | $this->json['value'] = (array) $this->get_value(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /inc/controls/class-control-color.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 10 | * @link https://github.com/justintadlock/butterbean 11 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 12 | */ 13 | 14 | /** 15 | * Color control class. 16 | * 17 | * @since 1.0.0 18 | * @access public 19 | */ 20 | class ButterBean_Control_Color extends ButterBean_Control { 21 | 22 | /** 23 | * The type of control. 24 | * 25 | * @since 1.0.0 26 | * @access public 27 | * @var string 28 | */ 29 | public $type = 'color'; 30 | 31 | /** 32 | * Custom options to pass to the color picker. Mostly, this is a wrapper for 33 | * `iris()`, which is bundled with core WP. However, if they change pickers 34 | * in the future, it may correspond to a different script. 35 | * 36 | * @link http://automattic.github.io/Iris/#options 37 | * @link https://make.wordpress.org/core/2012/11/30/new-color-picker-in-wp-3-5/ 38 | * @since 1.0.0 39 | * @access public 40 | * @var array 41 | */ 42 | public $options = array(); 43 | 44 | /** 45 | * Enqueue scripts/styles for the control. 46 | * 47 | * @since 1.0.0 48 | * @access public 49 | * @return void 50 | */ 51 | public function enqueue() { 52 | 53 | wp_enqueue_script( 'wp-color-picker' ); 54 | wp_enqueue_style( 'wp-color-picker' ); 55 | } 56 | 57 | /** 58 | * Gets the attributes for the control. 59 | * 60 | * @since 1.0.0 61 | * @access public 62 | * @return array 63 | */ 64 | public function get_attr() { 65 | $attr = parent::get_attr(); 66 | 67 | $setting = $this->get_setting(); 68 | 69 | $attr['class'] = 'butterbean-color-picker'; 70 | $attr['type'] = 'text'; 71 | $attr['maxlength'] = 7; 72 | $attr['data-default-color'] = $setting ? $setting->default : ''; 73 | 74 | return $attr; 75 | } 76 | 77 | /** 78 | * Get the value for the setting. 79 | * 80 | * @since 1.0.0 81 | * @access public 82 | * @param string $setting 83 | * @return mixed 84 | */ 85 | public function get_value( $setting = 'default' ) { 86 | 87 | $value = parent::get_value( $setting ); 88 | 89 | return ltrim( $value, '#' ); 90 | } 91 | 92 | /** 93 | * Adds custom data to the json array. This data is passed to the Underscore template. 94 | * 95 | * @since 1.0.0 96 | * @access public 97 | * @return void 98 | */ 99 | public function to_json() { 100 | parent::to_json(); 101 | 102 | $this->json['options'] = $this->options; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /inc/controls/class-control-datetime.php: -------------------------------------------------------------------------------- 1 | 13 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 14 | * @link https://github.com/justintadlock/butterbean 15 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 16 | */ 17 | 18 | /** 19 | * Datetime control class. 20 | * 21 | * @since 1.0.0 22 | * @access public 23 | */ 24 | class ButterBean_Control_Datetime extends ButterBean_Control { 25 | 26 | /** 27 | * The type of control. 28 | * 29 | * @since 1.0.0 30 | * @access public 31 | * @var string 32 | */ 33 | public $type = 'datetime'; 34 | 35 | /** 36 | * Whether to show the time. Note that settings, particularly the 37 | * `ButterBean_Setting_Date` class will store the time as `00:00:00` if 38 | * no time is provided. 39 | * 40 | * @since 1.0.0 41 | * @access public 42 | * @var bool 43 | */ 44 | public $show_time = true; 45 | 46 | /** 47 | * Adds custom data to the json array. This data is passed to the Underscore template. 48 | * 49 | * @since 1.0.0 50 | * @access public 51 | * @globl object $wp_locale 52 | * @return void 53 | */ 54 | public function to_json() { 55 | global $wp_locale; 56 | 57 | parent::to_json(); 58 | 59 | $this->json['show_time'] = $this->show_time; 60 | 61 | $field_name = $this->get_field_name(); 62 | 63 | // Get project start/end dates. 64 | $date = $this->get_value(); 65 | 66 | // Get the year, month, and day. 67 | $year = $date ? mysql2date( 'Y', $date, false ) : ''; 68 | $month = $date ? mysql2date( 'm', $date, false ) : ''; 69 | $day = $date ? mysql2date( 'd', $date, false ) : ''; 70 | 71 | // Get the hour, minute, and second. 72 | $hour = $date ? mysql2date( 'H', $date, false ) : ''; 73 | $minute = $date ? mysql2date( 'i', $date, false ) : ''; 74 | $second = $date ? mysql2date( 's', $date, false ) : ''; 75 | 76 | // Year 77 | $this->json['year'] = array( 78 | 'value' => esc_attr( $year ), 79 | 'label' => esc_html__( 'Year', 'butterbean' ), 80 | 'name' => esc_attr( "{$field_name}_year" ), 81 | 'attr' => sprintf( 'placeholder="%s" size="4" maxlength="4" autocomplete="off"', esc_attr( date_i18n( 'Y' ) ) ) 82 | ); 83 | 84 | // Month 85 | $this->json['month'] = array( 86 | 'value' => esc_attr( $month ), 87 | 'name' => esc_attr( "{$field_name}_month" ), 88 | 'label' => esc_html__( 'Month', 'butterbean' ), 89 | 'choices' => array( 90 | array( 91 | 'num' => '', 92 | 'label' => '' 93 | ) 94 | ) 95 | ); 96 | 97 | for ( $i = 1; $i < 13; $i = $i +1 ) { 98 | 99 | $monthnum = zeroise( $i, 2 ); 100 | $monthtext = $wp_locale->get_month_abbrev( $wp_locale->get_month( $i ) ); 101 | 102 | $this->json['month']['choices'][] = array( 103 | 'num' => $monthnum, 104 | 'label' => $monthtext 105 | ); 106 | } 107 | 108 | // Day 109 | $this->json['day'] = array( 110 | 'value' => esc_attr( $day ), 111 | 'name' => esc_attr( "{$field_name}_day" ), 112 | 'label' => esc_html__( 'Day', 'butterbean' ), 113 | 'attr' => sprintf( 'placeholder="%s" size="2" maxlength="2" autocomplete="off"', esc_attr( date_i18n( 'd' ) ) ) 114 | ); 115 | 116 | // Hour 117 | $this->json['hour'] = array( 118 | 'value' => esc_attr( $hour ), 119 | 'name' => esc_attr( "{$field_name}_hour" ), 120 | 'label' => esc_html__( 'Hour', 'butterbean' ), 121 | 'attr' => 'placeholder="00" size="2" maxlength="2" autocomplete="off"' 122 | ); 123 | 124 | // Minute 125 | $this->json['minute'] = array( 126 | 'value' => esc_attr( $minute ), 127 | 'name' => esc_attr( "{$field_name}_minute" ), 128 | 'label' => esc_html__( 'Minute', 'butterbean' ), 129 | 'attr' => 'placeholder="00" size="2" maxlength="2" autocomplete="off"' 130 | ); 131 | 132 | // Second 133 | $this->json['second'] = array( 134 | 'value' => esc_attr( $second ), 135 | 'name' => esc_attr( "{$field_name}_second" ), 136 | 'label' => esc_html__( 'Second', 'butterbean' ), 137 | 'attr' => 'placeholder="00" size="2" maxlength="2" autocomplete="off"' 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /inc/controls/class-control-excerpt.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 11 | * @link https://github.com/justintadlock/butterbean 12 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 13 | */ 14 | 15 | /** 16 | * Excerpt control class. 17 | * 18 | * @since 1.0.0 19 | * @access public 20 | */ 21 | class ButterBean_Control_Excerpt extends ButterBean_Control_Textarea { 22 | 23 | /** 24 | * The type of control. 25 | * 26 | * @since 1.0.0 27 | * @access public 28 | * @var string 29 | */ 30 | public $type = 'excerpt'; 31 | 32 | /** 33 | * Gets the attributes for the control. 34 | * 35 | * @since 1.0.0 36 | * @access public 37 | * @return array 38 | */ 39 | public function get_attr() { 40 | $attr = parent::get_attr(); 41 | 42 | $attr['id'] = 'post_excerpt'; 43 | 44 | return $attr; 45 | } 46 | 47 | /** 48 | * Returns the HTML field name for the control. 49 | * 50 | * @since 1.0.0 51 | * @access public 52 | * @param string $setting 53 | * @return string 54 | */ 55 | public function get_field_name( $setting = 'default' ) { 56 | return 'post_excerpt'; 57 | } 58 | 59 | /** 60 | * Get the value for the setting. 61 | * 62 | * @since 1.0.0 63 | * @access public 64 | * @param string $setting 65 | * @return mixed 66 | */ 67 | public function get_value( $setting = 'default' ) { 68 | 69 | return get_post( $this->manager->post_id )->post_excerpt; 70 | } 71 | 72 | /** 73 | * Gets the Underscore.js template. 74 | * 75 | * @since 1.0.0 76 | * @access public 77 | * @return void 78 | */ 79 | public function get_template() { 80 | butterbean_get_control_template( 'textarea' ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /inc/controls/class-control-image.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 10 | * @link https://github.com/justintadlock/butterbean 11 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 12 | */ 13 | 14 | /** 15 | * Image control class. 16 | * 17 | * @since 1.0.0 18 | * @access public 19 | */ 20 | class ButterBean_Control_Image extends ButterBean_Control { 21 | 22 | /** 23 | * The type of control. 24 | * 25 | * @since 1.0.0 26 | * @access public 27 | * @var string 28 | */ 29 | public $type = 'image'; 30 | 31 | /** 32 | * Array of text labels to use for the media upload frame. 33 | * 34 | * @since 1.0.0 35 | * @access public 36 | * @var string 37 | */ 38 | public $l10n = array(); 39 | 40 | /** 41 | * Image size to display. If the size isn't found for the image, 42 | * the full size of the image will be output. 43 | * 44 | * @since 1.0.0 45 | * @access public 46 | * @var string 47 | */ 48 | public $size = 'large'; 49 | 50 | /** 51 | * Creates a new control object. 52 | * 53 | * @since 1.0.0 54 | * @access public 55 | * @param object $manager 56 | * @param string $name 57 | * @param array $args 58 | * @return void 59 | */ 60 | public function __construct( $manager, $name, $args = array() ) { 61 | parent::__construct( $manager, $name, $args ); 62 | 63 | $this->l10n = wp_parse_args( 64 | $this->l10n, 65 | array( 66 | 'upload' => esc_html__( 'Add image', 'butterbean' ), 67 | 'set' => esc_html__( 'Set as image', 'butterbean' ), 68 | 'choose' => esc_html__( 'Choose image', 'butterbean' ), 69 | 'change' => esc_html__( 'Change image', 'butterbean' ), 70 | 'remove' => esc_html__( 'Remove image', 'butterbean' ), 71 | 'placeholder' => esc_html__( 'No image selected', 'butterbean' ) 72 | ) 73 | ); 74 | } 75 | 76 | /** 77 | * Enqueue scripts/styles for the control. 78 | * 79 | * @since 1.0.0 80 | * @access public 81 | * @return void 82 | */ 83 | public function enqueue() { 84 | 85 | wp_enqueue_script( 'media-views' ); 86 | } 87 | 88 | /** 89 | * Adds custom data to the json array. 90 | * 91 | * @since 1.0.0 92 | * @access public 93 | * @return void 94 | */ 95 | public function to_json() { 96 | parent::to_json(); 97 | 98 | $this->json['l10n'] = $this->l10n; 99 | $this->json['size'] = $this->size; 100 | 101 | $value = $this->get_value(); 102 | $image = $alt = ''; 103 | 104 | if ( $value ) { 105 | $image = wp_get_attachment_image_src( absint( $value ), $this->size ); 106 | $alt = get_post_meta( absint( $value ), '_wp_attachment_image_alt', true ); 107 | } 108 | 109 | $this->json['src'] = $image ? esc_url( $image[0] ) : ''; 110 | $this->json['alt'] = $alt ? esc_attr( $alt ) : ''; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /inc/controls/class-control-multi-avatars.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 11 | * @link https://github.com/justintadlock/butterbean 12 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 13 | */ 14 | 15 | /** 16 | * Multi-avatars control class. 17 | * 18 | * @since 1.0.0 19 | * @access public 20 | */ 21 | class ButterBean_Control_Multi_Avatars extends ButterBean_Control { 22 | 23 | /** 24 | * The type of control. 25 | * 26 | * @since 1.0.0 27 | * @access public 28 | * @var string 29 | */ 30 | public $type = 'multi-avatars'; 31 | 32 | /** 33 | * Adds custom data to the json array. This data is passed to the Underscore template. 34 | * 35 | * @since 1.0.0 36 | * @access public 37 | * @return void 38 | */ 39 | public function to_json() { 40 | parent::to_json(); 41 | 42 | $this->json['value'] = is_array( $this->get_value() ) ? array_map( 'absint', $this->get_value() ) : array(); 43 | $this->json['choices'] = array(); 44 | 45 | $users = get_users( array( 'role__in' => $this->get_roles() ) ); 46 | 47 | foreach ( $users as $user ) { 48 | $this->json['choices'][] = array( 49 | 'id' => $user->ID, 50 | 'name' => $user->display_name, 51 | 'avatar' => get_avatar( $user->ID, 70 ) 52 | ); 53 | } 54 | } 55 | 56 | /** 57 | * Returns an array of user roles that are allowed to edit, publish, or create 58 | * posts of the given post type. 59 | * 60 | * @since 1.0.0 61 | * @access public 62 | * @global object $wp_roles 63 | * @return array 64 | */ 65 | public function get_roles() { 66 | global $wp_roles; 67 | 68 | $roles = array(); 69 | $type = get_post_type_object( get_post_type( $this->manager->post_id ) ); 70 | 71 | // Get the post type object caps. 72 | $caps = array( $type->cap->edit_posts, $type->cap->publish_posts, $type->cap->create_posts ); 73 | $caps = array_unique( $caps ); 74 | 75 | // Loop through the available roles. 76 | foreach ( $wp_roles->roles as $name => $role ) { 77 | 78 | foreach ( $caps as $cap ) { 79 | 80 | // If the role is granted the cap, add it. 81 | if ( isset( $role['capabilities'][ $cap ] ) && true === $role['capabilities'][ $cap ] ) { 82 | $roles[] = $name; 83 | break; 84 | } 85 | } 86 | } 87 | 88 | return $roles; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /inc/controls/class-control-palette.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 9 | * @link https://github.com/justintadlock/butterbean 10 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 11 | */ 12 | 13 | /** 14 | * Color palette control class. 15 | * 16 | * @since 1.0.0 17 | * @access public 18 | */ 19 | class ButterBean_Control_Palette extends ButterBean_Control { 20 | 21 | /** 22 | * The type of control. 23 | * 24 | * @since 1.0.0 25 | * @access public 26 | * @var string 27 | */ 28 | public $type = 'palette'; 29 | 30 | /** 31 | * Adds custom data to the json array. This data is passed to the Underscore template. 32 | * 33 | * @since 1.0.0 34 | * @access public 35 | * @return void 36 | */ 37 | public function to_json() { 38 | parent::to_json(); 39 | 40 | $value = $this->get_value(); 41 | 42 | // Make sure the colors have a hash. 43 | foreach ( $this->choices as $choice => $palette ) { 44 | $this->choices[ $choice ]['colors'] = array_map( 'butterbean_maybe_hash_hex_color', $palette['colors'] ); 45 | 46 | $this->choices[ $choice ]['selected'] = $value && $choice === $value; 47 | } 48 | 49 | $this->json['choices'] = $this->choices; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /inc/controls/class-control-parent.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 11 | * @link https://github.com/justintadlock/butterbean 12 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 13 | */ 14 | 15 | /** 16 | * Post parent control class. 17 | * 18 | * @since 1.0.0 19 | * @access public 20 | */ 21 | class ButterBean_Control_Parent extends ButterBean_Control { 22 | 23 | /** 24 | * The type of control. 25 | * 26 | * @since 1.0.0 27 | * @access public 28 | * @var string 29 | */ 30 | public $type = 'parent'; 31 | 32 | /** 33 | * The post type to select posts from. 34 | * 35 | * @since 1.0.0 36 | * @access public 37 | * @var string 38 | */ 39 | public $post_type = ''; 40 | 41 | /** 42 | * Returns the HTML field name for the control. 43 | * 44 | * @since 1.0.0 45 | * @access public 46 | * @param string $setting 47 | * @return array 48 | */ 49 | public function get_field_name( $setting = 'default' ) { 50 | 51 | return 'post_parent'; 52 | } 53 | 54 | /** 55 | * Get the value for the setting. 56 | * 57 | * @since 1.0.0 58 | * @access public 59 | * @param string $setting 60 | * @return mixed 61 | */ 62 | public function get_value( $setting = 'default' ) { 63 | 64 | return get_post( $this->manager->post_id )->post_parent; 65 | } 66 | 67 | /** 68 | * Adds custom data to the json array. This data is passed to the Underscore template. 69 | * 70 | * @since 1.0.0 71 | * @access public 72 | * @return void 73 | */ 74 | public function to_json() { 75 | parent::to_json(); 76 | 77 | $_post = get_post( $this->manager->post_id ); 78 | 79 | $posts = get_posts( 80 | array( 81 | 'post_type' => $this->post_type ? $this->post_type : get_post_type( $this->manager->post_id ), 82 | 'post_status' => 'any', 83 | 'post__not_in' => array( $this->manager->post_id ), 84 | 'posts_per_page' => -1, 85 | 'post_parent' => 0, 86 | 'orderby' => 'title', 87 | 'order' => 'ASC', 88 | 'fields' => array( 'ID', 'post_title' ) 89 | ) 90 | ); 91 | 92 | $this->json['choices'] = array( array( 'value' => 0, 'label' => '' ) ); 93 | 94 | foreach ( $posts as $post ) 95 | $this->json['choices'][] = array( 'value' => $post->ID, 'label' => $post->post_title ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /inc/controls/class-control-radio-image.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 10 | * @link https://github.com/justintadlock/butterbean 11 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 12 | */ 13 | 14 | /** 15 | * Radio image control class. 16 | * 17 | * @since 1.0.0 18 | * @access public 19 | */ 20 | class ButterBean_Control_Radio_Image extends ButterBean_Control_Radio { 21 | 22 | /** 23 | * The type of control. 24 | * 25 | * @since 1.0.0 26 | * @access public 27 | * @var string 28 | */ 29 | public $type = 'radio-image'; 30 | 31 | /** 32 | * Adds custom data to the json array. This data is passed to the Underscore template. 33 | * 34 | * @since 1.0.0 35 | * @access public 36 | * @return void 37 | */ 38 | public function to_json() { 39 | parent::to_json(); 40 | 41 | foreach ( $this->choices as $value => $args ) 42 | $this->choices[ $value ]['url'] = esc_url( sprintf( $args['url'], get_template_directory_uri(), get_stylesheet_directory_uri() ) ); 43 | 44 | $this->json['choices'] = $this->choices; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /inc/controls/class-control-radio.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 9 | * @link https://github.com/justintadlock/butterbean 10 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 11 | */ 12 | 13 | /** 14 | * Radio control class. 15 | * 16 | * @since 1.0.0 17 | * @access public 18 | */ 19 | class ButterBean_Control_Radio extends ButterBean_Control { 20 | 21 | /** 22 | * The type of control. 23 | * 24 | * @since 1.0.0 25 | * @access public 26 | * @var string 27 | */ 28 | public $type = 'radio'; 29 | 30 | /** 31 | * Radio controls imply that a value should be set. Therefore, we will return 32 | * the default if there is no value. 33 | * 34 | * @since 1.0.0 35 | * @access public 36 | * @param string $setting 37 | * @return mixed 38 | */ 39 | public function get_value( $setting = 'default' ) { 40 | 41 | $value = parent::get_value( $setting ); 42 | $object = $this->get_setting( $setting ); 43 | 44 | return ! $value && $object ? $object->default : $value; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /inc/controls/class-control-select-group.php: -------------------------------------------------------------------------------- 1 | ` to be added. 5 | * 6 | * @package ButterBean 7 | * @author Justin Tadlock 8 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 9 | * @link https://github.com/justintadlock/butterbean 10 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 11 | */ 12 | 13 | /** 14 | * Select group control class. 15 | * 16 | * @since 1.0.0 17 | * @access public 18 | */ 19 | class ButterBean_Control_Select_Group extends ButterBean_Control { 20 | 21 | /** 22 | * The type of control. 23 | * 24 | * @since 1.0.0 25 | * @access public 26 | * @var string 27 | */ 28 | public $type = 'select-group'; 29 | 30 | /** 31 | * Adds custom data to the json array. 32 | * 33 | * @since 1.0.0 34 | * @access public 35 | * @return void 36 | */ 37 | public function to_json() { 38 | parent::to_json(); 39 | 40 | $choices = $group = array(); 41 | 42 | foreach ( $this->choices as $choice => $maybe_group ) { 43 | 44 | if ( is_array( $maybe_group ) ) 45 | $group[ $choice ] = $maybe_group; 46 | else 47 | $choices[ $choice ] = $maybe_group; 48 | } 49 | 50 | $this->json['choices'] = $choices; 51 | $this->json['group'] = $group; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /inc/controls/class-control-textarea.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 9 | * @link https://github.com/justintadlock/butterbean 10 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 11 | */ 12 | 13 | /** 14 | * Textarea control class. 15 | * 16 | * @since 1.0.0 17 | * @access public 18 | */ 19 | class ButterBean_Control_Textarea extends ButterBean_Control { 20 | 21 | /** 22 | * The type of control. 23 | * 24 | * @since 1.0.0 25 | * @access public 26 | * @var string 27 | */ 28 | public $type = 'textarea'; 29 | 30 | /** 31 | * Adds custom data to the json array. This data is passed to the Underscore template. 32 | * 33 | * @since 1.0.0 34 | * @access public 35 | * @return void 36 | */ 37 | public function to_json() { 38 | parent::to_json(); 39 | 40 | $this->json['value'] = esc_textarea( $this->get_value() ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /inc/functions-core.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 9 | * @link https://github.com/justintadlock/butterbean 10 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 11 | */ 12 | 13 | /** 14 | * Function for validating booleans before saving them as metadata. If the value is 15 | * `true`, we'll return a `1` to be stored as the meta value. Else, we return `false`. 16 | * 17 | * @since 1.0.0 18 | * @access public 19 | * @param mixed 20 | * @return bool|int 21 | */ 22 | function butterbean_validate_boolean( $value ) { 23 | 24 | return wp_validate_boolean( $value ) ? 1 : false; 25 | } 26 | 27 | /** 28 | * Pre-WP 4.6 function for sanitizing hex colors. 29 | * 30 | * @since 1.0.0 31 | * @access public 32 | * @param string $color 33 | * @return string 34 | */ 35 | function butterbean_sanitize_hex_color( $color ) { 36 | 37 | if ( function_exists( 'sanitize_hex_color' ) ) 38 | return sanitize_hex_color( $color ); 39 | 40 | return $color && preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ? $color : ''; 41 | } 42 | 43 | /** 44 | * Pre-WP 4.6 function for sanitizing hex colors without a hash. 45 | * 46 | * @since 1.0.0 47 | * @access public 48 | * @param string $color 49 | * @return string 50 | */ 51 | function butterbean_sanitize_hex_color_no_hash( $color ) { 52 | 53 | if ( function_exists( 'sanitize_hex_color_no_hash' ) ) 54 | return sanitize_hex_color_no_hash( $color ); 55 | 56 | $color = ltrim( $color, '#' ); 57 | 58 | if ( '' === $color ) 59 | return ''; 60 | 61 | return butterbean_sanitize_hex_color( '#' . $color ) ? $color : null; 62 | } 63 | 64 | /** 65 | * Pre-WP 4.6 function for sanitizing a color and adding a hash. 66 | * 67 | * @since 1.0.0 68 | * @access public 69 | * @param string $color 70 | * @return string 71 | */ 72 | function butterbean_maybe_hash_hex_color( $color ) { 73 | 74 | if ( function_exists( 'maybe_hash_hex_color' ) ) 75 | return maybe_hash_hex_color( $color ); 76 | 77 | if ( $unhashed = butterbean_sanitize_hex_color_no_hash( $color ) ) 78 | return '#' . $unhashed; 79 | 80 | return $color; 81 | } 82 | 83 | /** 84 | * Gets Underscore.js templates for managers. 85 | * 86 | * @since 1.0.0 87 | * @param string $slug 88 | * @return void 89 | */ 90 | function butterbean_get_manager_template( $slug = '' ) { 91 | butterbean_get_template( 'manager', $slug ); 92 | } 93 | 94 | /** 95 | * Gets Underscore.js templates for navs. 96 | * 97 | * @since 1.0.0 98 | * @param string $slug 99 | * @return void 100 | */ 101 | function butterbean_get_nav_template( $slug = '' ) { 102 | butterbean_get_template( 'nav', $slug ); 103 | } 104 | 105 | /** 106 | * Gets Underscore.js templates for sections. 107 | * 108 | * @since 1.0.0 109 | * @param string $slug 110 | * @return void 111 | */ 112 | function butterbean_get_section_template( $slug = '' ) { 113 | butterbean_get_template( 'section', $slug ); 114 | } 115 | 116 | /** 117 | * Gets Underscore.js templates for controls. 118 | * 119 | * @since 1.0.0 120 | * @param string $slug 121 | * @return void 122 | */ 123 | function butterbean_get_control_template( $slug = '' ) { 124 | butterbean_get_template( 'control', $slug ); 125 | } 126 | 127 | /** 128 | * Helper function for getting Underscore.js templates. 129 | * 130 | * @since 1.0.0 131 | * @param string $name 132 | * @param string $slug 133 | * @return void 134 | */ 135 | function butterbean_get_template( $name, $slug = '' ) { 136 | 137 | // Allow devs to hook in early to bypass template checking. 138 | $located = apply_filters( "butterbean_pre_{$name}_template", '', $slug ); 139 | 140 | // If there's no template, let's try to find one. 141 | if ( ! $located ) { 142 | 143 | $templates = array(); 144 | 145 | if ( $slug ) 146 | $templates[] = "{$name}-{$slug}.php"; 147 | 148 | $templates[] = "{$name}.php"; 149 | 150 | // Allow devs to filter the template hierarchy. 151 | $templates = apply_filters( "butterbean_{$name}_template_hierarchy", $templates, $slug ); 152 | 153 | // Loop through the templates and locate one. 154 | foreach ( $templates as $template ) { 155 | 156 | if ( file_exists( butterbean()->tmpl_path . $template ) ) { 157 | $located = butterbean()->tmpl_path . $template; 158 | break; 159 | } 160 | } 161 | } 162 | 163 | // Allow devs to filter the final template. 164 | $located = apply_filters( "butterbean_{$name}_template", $located, $slug ); 165 | 166 | // Load the template. 167 | if ( $located ) 168 | require( $located ); 169 | } 170 | -------------------------------------------------------------------------------- /inc/settings/class-setting-array.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 8 | * @link https://github.com/justintadlock/butterbean 9 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 10 | */ 11 | 12 | /** 13 | * Array setting class. 14 | * 15 | * @since 1.0.0 16 | * @access public 17 | */ 18 | class ButterBean_Setting_Array extends ButterBean_Setting { 19 | 20 | /** 21 | * The type of setting. 22 | * 23 | * @since 1.0.0 24 | * @access public 25 | * @var string 26 | */ 27 | public $type = 'array'; 28 | 29 | /** 30 | * Sanitizes the value of the setting. 31 | * 32 | * @since 1.0.0 33 | * @access public 34 | * @param array $value 35 | * @return array 36 | */ 37 | public function sanitize( $values ) { 38 | 39 | $multi_values = $values && ! is_array( $values ) ? explode( ',', $values ) : $values; 40 | 41 | return $multi_values ? array_map( array( $this, 'map' ), $multi_values ) : array(); 42 | } 43 | 44 | /** 45 | * Helper function for sanitizing each value of the array. 46 | * 47 | * @since 1.0.0 48 | * @access public 49 | * @param mixed $value 50 | * @return mixed 51 | */ 52 | public function map( $value ) { 53 | 54 | return apply_filters( "butterbean_{$this->manager->name}_sanitize_{$this->name}", $value, $this ); 55 | } 56 | 57 | /** 58 | * Saves the value of the setting. 59 | * 60 | * @since 1.0.0 61 | * @access public 62 | * @return void 63 | */ 64 | public function save() { 65 | 66 | if ( ! $this->check_capabilities() ) 67 | return; 68 | 69 | $old_values = $this->get_value(); 70 | $new_values = $this->get_posted_value(); 71 | 72 | // If there's an array of posted values, set them. 73 | if ( $new_values && is_array( $new_values ) && $new_values !== $old_values ) 74 | return update_post_meta( $this->manager->post_id, $this->name, $new_values ); 75 | 76 | // If no array of posted values but we have old values, delete them. 77 | else if ( $old_values && ! $new_values ) 78 | return delete_post_meta( $this->manager->post_id, $this->name ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /inc/settings/class-setting-datetime.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 10 | * @link https://github.com/justintadlock/butterbean 11 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 12 | */ 13 | 14 | /** 15 | * Date setting class. 16 | * 17 | * @since 1.0.0 18 | * @access public 19 | */ 20 | class ButterBean_Setting_Datetime extends ButterBean_Setting { 21 | 22 | /** 23 | * The type of setting. 24 | * 25 | * @since 1.0.0 26 | * @access public 27 | * @var string 28 | */ 29 | public $type = 'datetime'; 30 | 31 | /** 32 | * Gets the posted value of the setting. 33 | * 34 | * @since 1.0.0 35 | * @access public 36 | * @return mixed 37 | */ 38 | public function get_posted_value() { 39 | 40 | $field_name = $this->get_field_name(); 41 | 42 | // Get the posted date. 43 | $year = ! empty( $_POST[ "{$field_name}_year" ] ) ? zeroise( absint( $_POST[ "{$field_name}_year" ] ), 4 ) : ''; 44 | $month = ! empty( $_POST[ "{$field_name}_month" ] ) ? zeroise( absint( $_POST[ "{$field_name}_month" ] ), 2 ) : ''; 45 | $day = ! empty( $_POST[ "{$field_name}_day" ] ) ? zeroise( absint( $_POST[ "{$field_name}_day" ] ), 2 ) : ''; 46 | 47 | // Get the posted time. 48 | $hour = ! empty( $_POST[ "{$field_name}_hour" ] ) ? $this->validate_hour( $_POST[ "{$field_name}_hour" ] ) : '00'; 49 | $minute = ! empty( $_POST[ "{$field_name}_minute" ] ) ? $this->validate_minute( $_POST[ "{$field_name}_minute" ] ) : '00'; 50 | $second = ! empty( $_POST[ "{$field_name}_second" ] ) ? $this->validate_second( $_POST[ "{$field_name}_second" ] ) : '00'; 51 | 52 | $date = "{$year}-{$month}-{$day}"; 53 | $time = "{$hour}:{$minute}:{$second}"; 54 | 55 | if ( $year && $month && $day && wp_checkdate( absint( $month ), absint( $day ), absint( $year ), $date ) ) 56 | return "{$date} {$time}"; 57 | 58 | return ''; 59 | } 60 | 61 | /** 62 | * Validates the hour. 63 | * 64 | * @since 1.0.0 65 | * @access public 66 | * @param int|string $hour 67 | * @return string 68 | */ 69 | public function validate_hour( $hour ) { 70 | 71 | $hour = absint( $hour ); 72 | 73 | return $hour < 0 || $hour > 23 ? zeroise( $hour, 2 ) : '00'; 74 | } 75 | 76 | /** 77 | * Validates the minute. 78 | * 79 | * @since 1.0.0 80 | * @access public 81 | * @param int|string $minute 82 | * @return string 83 | */ 84 | public function validate_minute( $minute ) { 85 | 86 | $minute = absint( $minute ); 87 | 88 | return $minute < 0 || $minute > 59 ? zeroise( $minute, 2 ) : '00'; 89 | } 90 | 91 | /** 92 | * Validates the second. 93 | * 94 | * @since 1.0.0 95 | * @access public 96 | * @param int|string $second 97 | * @return string 98 | */ 99 | public function validate_second( $second ) { 100 | 101 | $second = absint( $second ); 102 | 103 | return $second < 0 || $second > 59 ? zeroise( $second, 2 ) : '00'; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /inc/settings/class-setting-multiple.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (c) 2015-2016, Justin Tadlock 8 | * @link https://github.com/justintadlock/butterbean 9 | * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 10 | */ 11 | 12 | /** 13 | * Multiple setting class. 14 | * 15 | * @since 1.0.0 16 | * @access public 17 | */ 18 | class ButterBean_Setting_Multiple extends ButterBean_Setting { 19 | 20 | /** 21 | * The type of setting. 22 | * 23 | * @since 1.0.0 24 | * @access public 25 | * @var string 26 | */ 27 | public $type = 'multiple'; 28 | 29 | /** 30 | * Gets the value of the setting. 31 | * 32 | * @since 1.0.0 33 | * @access public 34 | * @return mixed 35 | */ 36 | public function get_value() { 37 | 38 | return get_post_meta( $this->manager->post_id, $this->name ); 39 | } 40 | 41 | /** 42 | * Sanitizes the value of the setting. 43 | * 44 | * @since 1.0.0 45 | * @access public 46 | * @param array $value 47 | * @return array 48 | */ 49 | public function sanitize( $values ) { 50 | 51 | $multi_values = $values && ! is_array( $values ) ? explode( ',', $values ) : $values; 52 | 53 | return $multi_values ? array_map( array( $this, 'map' ), $multi_values ) : array(); 54 | } 55 | 56 | /** 57 | * Helper function for sanitizing each value of the array. 58 | * 59 | * @since 1.0.0 60 | * @access public 61 | * @param mixed $value 62 | * @return mixed 63 | */ 64 | public function map( $value ) { 65 | 66 | return apply_filters( "butterbean_{$this->manager->name}_sanitize_{$this->name}", $value, $this ); 67 | } 68 | 69 | /** 70 | * Saves the value of the setting. 71 | * 72 | * @since 1.0.0 73 | * @access public 74 | * @return void 75 | */ 76 | public function save() { 77 | 78 | if ( ! $this->check_capabilities() ) 79 | return; 80 | 81 | $old_values = $this->get_value(); 82 | $new_values = $this->get_posted_value(); 83 | 84 | // If there's an array of posted values, set them. 85 | if ( is_array( $new_values ) ) 86 | $this->set_values( $new_values, $old_values ); 87 | 88 | // If no array of posted values but we have old values, delete them. 89 | else if ( $old_values ) 90 | $this->delete_values(); 91 | } 92 | 93 | /** 94 | * Loops through new and old meta values and updates. 95 | * 96 | * @since 1.0.0 97 | * @access public 98 | * @param array $new_values 99 | * @param array $old_values 100 | * @return void 101 | */ 102 | public function set_values( $new_values, $old_values ) { 103 | 104 | foreach ( $new_values as $new ) { 105 | 106 | if ( ! in_array( $new, $old_values ) ) 107 | $this->add_value( $new ); 108 | } 109 | 110 | foreach ( $old_values as $old ) { 111 | 112 | if ( ! in_array( $old, $new_values ) ) 113 | $this->remove_value( $old ); 114 | } 115 | } 116 | 117 | /** 118 | * Deletes old meta values. 119 | * 120 | * @since 1.0.0 121 | * @access public 122 | * @return void 123 | */ 124 | public function delete_values() { 125 | 126 | return delete_post_meta( $this->manager->post_id, $this->name ); 127 | } 128 | 129 | /** 130 | * Adds a single meta value. 131 | * 132 | * @since 1.0.0 133 | * @access public 134 | * @param mixed $value 135 | * @return bool 136 | */ 137 | public function add_value( $value ) { 138 | 139 | return add_post_meta( $this->manager->post_id, $this->name, $value, false ); 140 | } 141 | 142 | /** 143 | * Deletes a single meta value. 144 | * 145 | * @since 1.0.0 146 | * @access public 147 | * @param mixed $value 148 | * @return bool 149 | */ 150 | public function remove_value( $value ) { 151 | 152 | return delete_post_meta( $this->manager->post_id, $this->name, $value ); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /js/butterbean.js: -------------------------------------------------------------------------------- 1 | window.butterbean = window.butterbean || {}; 2 | 3 | ( function() { 4 | 5 | // Bail if we don't have the JSON, which is passed in via `wp_localize_script()`. 6 | if ( _.isUndefined( butterbean_data ) ) { 7 | return; 8 | } 9 | 10 | /** 11 | * Our global object. The `butterbean` object is just a wrapper to house everything 12 | * in a single namespace. 13 | * 14 | * @since 1.0.0 15 | * @access public 16 | * @var object 17 | */ 18 | var api = butterbean = { 19 | 20 | /** 21 | * Houses the manager, section, and control views based on the `type`. 22 | * 23 | * @since 1.0.0 24 | * @access public 25 | * @var object 26 | */ 27 | views : { managers : {}, sections : {}, controls : {} }, 28 | 29 | /** 30 | * Houses the manager, section, and control templates based on the `type`. 31 | * 32 | * @since 1.0.0 33 | * @access public 34 | * @var object 35 | */ 36 | templates : { managers : {}, sections : {}, controls : {} } 37 | }; 38 | 39 | /** 40 | * Creates a new manager view. 41 | * 42 | * @since 1.0.0 43 | * @access public 44 | * @param string $type 45 | * @param object $args 46 | * @return void 47 | */ 48 | api.views.register_manager = function( type, args ) { 49 | 50 | if ( 'default' !== type ) 51 | this.managers[ type ] = this.managers.default.extend( args ); 52 | }; 53 | 54 | /** 55 | * Returns a manager view. 56 | * 57 | * @since 1.0.0 58 | * @access public 59 | * @param string $type 60 | * @return object 61 | */ 62 | api.views.get_manager = function( type ) { 63 | 64 | if ( this.manager_exists( type ) ) 65 | return this.managers[ type ]; 66 | 67 | return this.managers.default; 68 | }; 69 | 70 | /** 71 | * Removes a manager view. 72 | * 73 | * @since 1.0.0 74 | * @access public 75 | * @param string $type 76 | * @return void 77 | */ 78 | api.views.unregister_manager = function( type ) { 79 | 80 | if ( 'default' !== type && this.manager_exists( type ) ) 81 | delete this.managers[ type ]; 82 | }; 83 | 84 | /** 85 | * Checks if a manager view exists. 86 | * 87 | * @since 1.0.0 88 | * @access public 89 | * @param string $type 90 | * @return bool 91 | */ 92 | api.views.manager_exists = function( type ) { 93 | 94 | return this.managers.hasOwnProperty( type ); 95 | }; 96 | 97 | /** 98 | * Creates a new section view. 99 | * 100 | * @since 1.0.0 101 | * @access public 102 | * @param string $type 103 | * @param object $args 104 | * @return void 105 | */ 106 | api.views.register_section = function( type, args ) { 107 | 108 | if ( 'default' !== type ) 109 | this.sections[ type ] = this.sections.default.extend( args ); 110 | }; 111 | 112 | /** 113 | * Returns a section view. 114 | * 115 | * @since 1.0.0 116 | * @access public 117 | * @param string $type 118 | * @return object 119 | */ 120 | api.views.get_section = function( type ) { 121 | 122 | if ( this.section_exists( type ) ) 123 | return this.sections[ type ]; 124 | 125 | return this.sections.default; 126 | }; 127 | 128 | /** 129 | * Removes a section view. 130 | * 131 | * @since 1.0.0 132 | * @access public 133 | * @param string $type 134 | * @return void 135 | */ 136 | api.views.unregister_section = function( type ) { 137 | 138 | if ( 'default' !== type && this.section_exists( type ) ) 139 | delete this.sections[ type ]; 140 | }; 141 | 142 | /** 143 | * Checks if a section view exists. 144 | * 145 | * @since 1.0.0 146 | * @access public 147 | * @param string $type 148 | * @return bool 149 | */ 150 | api.views.section_exists = function( type ) { 151 | 152 | return this.sections.hasOwnProperty( type ); 153 | }; 154 | 155 | /** 156 | * Creates a new control view. 157 | * 158 | * @since 1.0.0 159 | * @access public 160 | * @param string $type 161 | * @param object $args 162 | * @return void 163 | */ 164 | api.views.register_control = function( type, args ) { 165 | 166 | if ( 'default' !== type ) 167 | this.controls[ type ] = this.controls.default.extend( args ); 168 | }; 169 | 170 | /** 171 | * Returns a control view. 172 | * 173 | * @since 1.0.0 174 | * @access public 175 | * @param string $type 176 | * @return object 177 | */ 178 | api.views.get_control = function( type ) { 179 | 180 | if ( this.control_exists( type ) ) 181 | return this.controls[ type ]; 182 | 183 | return this.controls.default; 184 | }; 185 | 186 | /** 187 | * Removes a control view. 188 | * 189 | * @since 1.0.0 190 | * @access public 191 | * @param string $type 192 | * @return void 193 | */ 194 | api.views.unregister_control = function( type ) { 195 | 196 | if ( 'default' !== type && this.control_exists( type ) ) 197 | delete this.controls[ type ]; 198 | }; 199 | 200 | /** 201 | * Checks if a control view exists. 202 | * 203 | * @since 1.0.0 204 | * @access public 205 | * @param string $type 206 | * @return bool 207 | */ 208 | api.views.control_exists = function( type ) { 209 | 210 | return this.controls.hasOwnProperty( type ); 211 | }; 212 | 213 | /** 214 | * Creates a new manager template. 215 | * 216 | * @since 1.0.0 217 | * @access public 218 | * @param string $type 219 | * @param object $args 220 | * @return void 221 | */ 222 | api.templates.register_manager = function( type ) { 223 | 224 | this.managers[ type ] = wp.template( 'butterbean-manager-' + type ); 225 | }; 226 | 227 | /** 228 | * Returns a manager template. 229 | * 230 | * @since 1.0.0 231 | * @access public 232 | * @param string $type 233 | * @return function 234 | */ 235 | api.templates.get_manager = function( type ) { 236 | 237 | return this.manager_exists( type ) ? this.managers[ type ] : false; 238 | }; 239 | 240 | /** 241 | * Removes a manager template. 242 | * 243 | * @since 1.0.0 244 | * @access public 245 | * @param string $type 246 | * @return void 247 | */ 248 | api.templates.unregister_manager = function( type ) { 249 | 250 | if ( this.manager_exists( type ) ) 251 | delete this.managers[ type ]; 252 | }; 253 | 254 | /** 255 | * Checks if a manager template exists. 256 | * 257 | * @since 1.0.0 258 | * @access public 259 | * @param string $type 260 | * @return bool 261 | */ 262 | api.templates.manager_exists = function( type ) { 263 | 264 | return this.managers.hasOwnProperty( type ); 265 | }; 266 | 267 | /** 268 | * Creates a new section template. 269 | * 270 | * @since 1.0.0 271 | * @access public 272 | * @param string $type 273 | * @param object $args 274 | * @return void 275 | */ 276 | api.templates.register_section = function( type ) { 277 | 278 | this.sections[ type ] = wp.template( 'butterbean-section-' + type ); 279 | }; 280 | 281 | /** 282 | * Returns a section template. 283 | * 284 | * @since 1.0.0 285 | * @access public 286 | * @param string $type 287 | * @return function 288 | */ 289 | api.templates.get_section = function( type ) { 290 | 291 | return this.section_exists( type ) ? this.sections[ type ] : false; 292 | }; 293 | 294 | /** 295 | * Removes a section template. 296 | * 297 | * @since 1.0.0 298 | * @access public 299 | * @param string $type 300 | * @return void 301 | */ 302 | api.templates.unregister_section = function( type ) { 303 | 304 | if ( this.section_exists( type ) ) 305 | delete this.sections[ type ]; 306 | }; 307 | 308 | /** 309 | * Checks if a section template exists. 310 | * 311 | * @since 1.0.0 312 | * @access public 313 | * @param string $type 314 | * @return bool 315 | */ 316 | api.templates.section_exists = function( type ) { 317 | 318 | return this.sections.hasOwnProperty( type ); 319 | }; 320 | 321 | /** 322 | * Creates a new control template. 323 | * 324 | * @since 1.0.0 325 | * @access public 326 | * @param string $type 327 | * @param object $args 328 | * @return void 329 | */ 330 | api.templates.register_control = function( type ) { 331 | 332 | this.controls[ type ] = wp.template( 'butterbean-control-' + type ); 333 | }; 334 | 335 | /** 336 | * Returns a control template. 337 | * 338 | * @since 1.0.0 339 | * @access public 340 | * @param string $type 341 | * @return function 342 | */ 343 | api.templates.get_control = function( type ) { 344 | 345 | return this.control_exists( type ) ? this.controls[ type ] : false; 346 | }; 347 | 348 | /** 349 | * Removes a control template. 350 | * 351 | * @since 1.0.0 352 | * @access public 353 | * @param string $type 354 | * @return void 355 | */ 356 | api.templates.unregister_control = function( type ) { 357 | 358 | if ( this.control_exists( type ) ) 359 | delete this.controls[ type ]; 360 | }; 361 | 362 | /** 363 | * Checks if a control template exists. 364 | * 365 | * @since 1.0.0 366 | * @access public 367 | * @param string $type 368 | * @return bool 369 | */ 370 | api.templates.control_exists = function( type ) { 371 | 372 | return this.controls.hasOwnProperty( type ); 373 | }; 374 | 375 | /** 376 | * Renders our managers, sections, and controls. 377 | * 378 | * @since 1.0.0 379 | * @access private 380 | * @return void 381 | */ 382 | api.render = function() { 383 | 384 | // Loop through each of the managers and render their api.views. 385 | _.each( butterbean_data.managers, function( data ) { 386 | 387 | // Create a new manager model with the JSON data for the manager. 388 | var manager = new Manager( data ); 389 | 390 | // Get the manager view callback. 391 | var callback = api.views.get_manager( data.type ); 392 | 393 | // Create a new manager view. 394 | var view = new callback( { model : manager } ); 395 | 396 | // Get the meta box element. 397 | var metabox = document.getElementById( 'butterbean-ui-' + manager.get( 'name' ) ); 398 | 399 | // Add the `.butterbean-ui` class to the meta box. 400 | metabox.className += ' butterbean-ui'; 401 | 402 | // Render the manager view. 403 | metabox.querySelector( '.inside' ).appendChild( view.render().el ); 404 | 405 | // Render the manager subviews. 406 | view.subview_render(); 407 | 408 | // Call the view's ready method. 409 | view.ready(); 410 | } ); 411 | }; 412 | 413 | /* === Templates === */ 414 | 415 | // Nav template. 416 | var nav_template = wp.template( 'butterbean-nav' ); 417 | 418 | /* === Models === */ 419 | 420 | // Manager model (each manager is housed within a meta box). 421 | var Manager = Backbone.Model.extend( { 422 | defaults : { 423 | name : '', 424 | type : '', 425 | sections : {}, 426 | controls : {} 427 | } 428 | } ); 429 | 430 | // Section model (each section belongs to a manager). 431 | var Section = Backbone.Model.extend( { 432 | defaults : { 433 | name : '', 434 | type : '', 435 | label : '', 436 | description : '', 437 | icon : '', 438 | manager : '', 439 | active : '', 440 | selected : false 441 | } 442 | } ); 443 | 444 | // Control model (each control belongs to a manager and section). 445 | var Control = Backbone.Model.extend( { 446 | defaults : { 447 | name : '', 448 | type : '', 449 | label : '', 450 | description : '', 451 | icon : '', 452 | value : '', 453 | choices : {}, 454 | attr : '', 455 | active : '', 456 | manager : '', 457 | section : '', 458 | setting : '' 459 | } 460 | } ); 461 | 462 | /* === Collections === */ 463 | 464 | /** 465 | * Stores our collection of section models. 466 | * 467 | * @since 1.0.0 468 | * @access private 469 | * @var object 470 | */ 471 | var Sections = Backbone.Collection.extend( { 472 | model : Section 473 | } ); 474 | 475 | /* === Views === */ 476 | 477 | /** 478 | * The default manager view. Other views can extend this using the 479 | * `butterbean.views.register_manager()` function. 480 | * 481 | * @since 1.0.0 482 | * @access public 483 | * @var object 484 | */ 485 | api.views.managers[ 'default' ] = Backbone.View.extend( { 486 | 487 | // Wrapper element for the manager view. 488 | tagName : 'div', 489 | 490 | // Adds some custom attributes to the wrapper. 491 | attributes : function() { 492 | return { 493 | 'id' : 'butterbean-manager-' + this.model.get( 'name' ), 494 | 'class' : 'butterbean-manager butterbean-manager-' + this.model.get( 'type' ) 495 | }; 496 | }, 497 | 498 | // Initializes the view. 499 | initialize : function() { 500 | 501 | var type = this.model.get( 'type' ); 502 | 503 | // If there's not yet a template for this manager type, create it. 504 | if ( ! api.templates.manager_exists( type ) ) 505 | api.templates.register_manager( type ); 506 | 507 | // Get the manager template. 508 | this.template = api.templates.get_manager( type ); 509 | }, 510 | 511 | // Renders the manager. 512 | render : function() { 513 | this.el.innerHTML = this.template( this.model.toJSON() ); 514 | return this; 515 | }, 516 | 517 | // Renders the manager's sections and controls. 518 | // Important! This may change drastically in the future, possibly even 519 | // taken out of the manager view altogether. It's for this reason that 520 | // it's not recommended to create custom views for managers right now. 521 | subview_render : function() { 522 | 523 | // Create a new section collection. 524 | var sections = new Sections(); 525 | 526 | // Loop through each section and add it to the collection. 527 | _.each( this.model.get( 'sections' ), function( data ) { 528 | 529 | sections.add( new Section( data ) ); 530 | } ); 531 | 532 | // Loop through each section in the collection and render its view. 533 | sections.forEach( function( section, i ) { 534 | 535 | // Create a new nav item view for the section. 536 | var nav_view = new Nav_View( { model : section } ); 537 | 538 | // Render the nav item view. 539 | document.querySelector( '#butterbean-ui-' + section.get( 'manager' ) + ' .butterbean-nav' ).appendChild( nav_view.render().el ); 540 | 541 | // Get the section view callback. 542 | var callback = api.views.get_section( section.attributes.type ); 543 | 544 | // Create a new section view. 545 | var view = new callback( { model : section } ); 546 | 547 | // Render the section view. 548 | document.querySelector( '#butterbean-ui-' + section.get( 'manager' ) + ' .butterbean-content' ).appendChild( view.render().el ); 549 | 550 | // Call the section view's ready method. 551 | view.ready(); 552 | 553 | // If the first model, set it to selected. 554 | section.set( 'selected', 0 === i ); 555 | }, this ); 556 | 557 | // Loop through each control for the manager and render its view. 558 | _.each( this.model.get( 'controls' ), function( data ) { 559 | 560 | // Create a new control model. 561 | var control = new Control( data ); 562 | 563 | // Get the control view callback. 564 | var callback = api.views.get_control( data.type ); 565 | 566 | // Create a new control view. 567 | var view = new callback( { model : control } ); 568 | 569 | // Render the view. 570 | document.getElementById( 'butterbean-' + control.get( 'manager' ) + '-section-' + control.get( 'section' ) ).appendChild( view.render().el ); 571 | 572 | // Call the view's ready method. 573 | view.ready(); 574 | } ); 575 | 576 | return this; 577 | }, 578 | 579 | // Function that is executed *after* the view has been rendered. 580 | // This is meant to be overwritten in sub-views. 581 | ready : function() {} 582 | } ); 583 | 584 | /** 585 | * The default section view. Other views can extend this using the 586 | * `butterbean.views.register_section()` function. 587 | * 588 | * @since 1.0.0 589 | * @access public 590 | * @var object 591 | */ 592 | api.views.sections[ 'default' ] = Backbone.View.extend( { 593 | 594 | // Wrapper element for the section. 595 | tagName : 'div', 596 | 597 | // Adds custom attributes for the section wrapper. 598 | attributes : function() { 599 | return { 600 | 'id' : 'butterbean-' + this.model.get( 'manager' ) + '-section-' + this.model.get( 'name' ), 601 | 'class' : 'butterbean-section butterbean-section-' + this.model.get( 'type' ), 602 | 'aria-hidden' : ! this.model.get( 'selected' ) 603 | }; 604 | }, 605 | 606 | // Initializes the view. 607 | initialize : function() { 608 | 609 | // Add an event for when the model changes. 610 | this.model.on( 'change', this.onchange, this ); 611 | 612 | // Get the section type. 613 | var type = this.model.get( 'type' ); 614 | 615 | // If there's no template for this section type, create it. 616 | if ( ! api.templates.section_exists( type ) ) 617 | api.templates.register_section( type ); 618 | 619 | // Gets the section template. 620 | this.template = api.templates.get_section( type ); 621 | }, 622 | 623 | // Renders the section. 624 | render : function() { 625 | 626 | // Only render template if model is active. 627 | if ( this.model.get( 'active' ) ) 628 | this.el.innerHTML = this.template( this.model.toJSON() ); 629 | 630 | return this; 631 | }, 632 | 633 | // Executed when the model changes. 634 | onchange : function() { 635 | 636 | // Set the view's `aria-hidden` attribute based on whether the model is selected. 637 | this.el.setAttribute( 'aria-hidden', ! this.model.get( 'selected' ) ); 638 | }, 639 | 640 | // Function that is executed *after* the view has been rendered. 641 | // This is meant to be overwritten in sub-views. 642 | ready : function() {} 643 | } ); 644 | 645 | /** 646 | * The nav item view for each section. 647 | * 648 | * @since 1.0.0 649 | * @access public 650 | * @var object 651 | */ 652 | var Nav_View = Backbone.View.extend( { 653 | 654 | // Sets the template used. 655 | template : nav_template, 656 | 657 | // Wrapper element for the nav item. 658 | tagName : 'li', 659 | 660 | // Sets some custom attributes for the nav item wrapper. 661 | attributes : function() { 662 | return { 663 | 'aria-selected' : this.model.get( 'selected' ) 664 | }; 665 | }, 666 | 667 | // Initializes the nav item view. 668 | initialize : function() { 669 | this.model.on( 'change', this.render, this ); 670 | this.model.on( 'change', this.onchange, this ); 671 | }, 672 | 673 | // Renders the nav item. 674 | render : function() { 675 | 676 | // Only render template if model is active. 677 | if ( this.model.get( 'active' ) ) 678 | this.el.innerHTML = this.template( this.model.toJSON() ); 679 | 680 | return this; 681 | }, 682 | 683 | // Custom events. 684 | events : { 685 | 'click a' : 'onselect' 686 | }, 687 | 688 | // Executed when the section model changes. 689 | onchange : function() { 690 | 691 | // Set the `aria-selected` attibute based on the model selected state. 692 | this.el.setAttribute( 'aria-selected', this.model.get( 'selected' ) ); 693 | }, 694 | 695 | // Executed when the link for the nav item is clicked. 696 | onselect : function( event ) { 697 | event.preventDefault(); 698 | 699 | // Loop through each of the models in the collection and set them to inactive. 700 | _.each( this.model.collection.models, function( m ) { 701 | 702 | m.set( 'selected', false ); 703 | }, this ); 704 | 705 | // Set this view's model to selected. 706 | this.model.set( 'selected', true ); 707 | } 708 | } ); 709 | 710 | /** 711 | * The default control view. Other views can extend this using the 712 | * `butterbean.views.register_control()` function. 713 | * 714 | * @since 1.0.0 715 | * @access public 716 | * @var object 717 | */ 718 | api.views.controls[ 'default' ] = Backbone.View.extend( { 719 | 720 | // Wrapper element for the control. 721 | tagName : 'div', 722 | 723 | // Custom attributes for the control wrapper. 724 | attributes : function() { 725 | return { 726 | 'id' : 'butterbean-control-' + this.model.get( 'name' ), 727 | 'class' : 'butterbean-control butterbean-control-' + this.model.get( 'type' ) 728 | }; 729 | }, 730 | 731 | // Initiazlies the control view. 732 | initialize : function() { 733 | var type = this.model.get( 'type' ); 734 | 735 | // Only add a new control template if we have a different control type. 736 | if ( ! api.templates.control_exists( type ) ) 737 | api.templates.register_control( type ); 738 | 739 | // Get the control template. 740 | this.template = api.templates.get_control( type ); 741 | 742 | // Bind changes so that the view is re-rendered when the model changes. 743 | _.bindAll( this, 'render' ); 744 | this.model.bind( 'change', this.render ); 745 | }, 746 | 747 | // Renders the control template. 748 | render : function() { 749 | 750 | // Only render template if model is active. 751 | if ( this.model.get( 'active' ) ) 752 | this.el.innerHTML = this.template( this.model.toJSON() ); 753 | 754 | return this; 755 | }, 756 | 757 | // Function that is executed *after* the view has been rendered. 758 | // This is meant to be overwritten in sub-views. 759 | ready : function() {} 760 | } ); 761 | 762 | /** 763 | * Adds the color control view. 764 | * 765 | * @since 1.0.0 766 | */ 767 | api.views.register_control( 'color', { 768 | 769 | // Calls the core WP color picker for the control's input. 770 | ready : function() { 771 | 772 | var options = this.model.attributes.options; 773 | 774 | jQuery( this.$el ).find( '.butterbean-color-picker' ).wpColorPicker( options ); 775 | } 776 | } ); 777 | 778 | /** 779 | * Adds the color palette view. 780 | * 781 | * @since 1.0.0 782 | */ 783 | api.views.register_control( 'palette', { 784 | 785 | // Adds custom events. 786 | events : { 787 | 'change input' : 'onselect' 788 | }, 789 | 790 | // Executed when one of the color palette's value has changed. 791 | // These are radio inputs. 792 | onselect : function() { 793 | 794 | // Get the value of the input. 795 | var value = document.querySelector( '#' + this.el.id + ' input:checked' ).getAttribute( 'value' ); 796 | 797 | // Get all choices. 798 | var choices = this.model.get( 'choices' ); 799 | 800 | // Loop through choices and change the selected value. 801 | _.each( choices, function( choice, key ) { 802 | choice.selected = key === value; 803 | } ); 804 | 805 | // Because `choices` is an array, it's not recognized as a change. So, we 806 | // have to manually trigger a change here so that the view gets re-rendered. 807 | this.model.set( 'choices', choices ).trigger( 'change', this.model ); 808 | } 809 | } ); 810 | 811 | /** 812 | * Adds the image control view. 813 | * 814 | * @since 1.0.0 815 | */ 816 | api.views.register_control( 'image', { 817 | 818 | // Adds custom events. 819 | events : { 820 | 'click .butterbean-add-media' : 'showmodal', 821 | 'click .butterbean-change-media' : 'showmodal', 822 | 'click .butterbean-remove-media' : 'removemedia' 823 | }, 824 | 825 | // Executed when the show modal button is clicked. 826 | showmodal : function() { 827 | 828 | 829 | // If we already have a media modal, open it. 830 | if ( ! _.isUndefined( this.media_modal ) ) { 831 | 832 | this.media_modal.open(); 833 | return; 834 | } 835 | 836 | // Create a new media modal. 837 | this.media_modal = wp.media( { 838 | frame : 'select', 839 | multiple : false, 840 | editing : true, 841 | title : this.model.get( 'l10n' ).choose, 842 | library : { type : 'image' }, 843 | button : { text: this.model.get( 'l10n' ).set } 844 | } ); 845 | 846 | // Runs when an image is selected in the media modal. 847 | this.media_modal.on( 'select', function() { 848 | 849 | // Gets the JSON data for the first selection. 850 | var media = this.media_modal.state().get( 'selection' ).first().toJSON(); 851 | 852 | // Size of image to display. 853 | var size = this.model.attributes.size; 854 | 855 | // Updates the model for the view. 856 | this.model.set( { 857 | src : media.sizes[ size ] ? media.sizes[ size ]['url'] : media.url, 858 | alt : media.alt, 859 | value : media.id 860 | } ); 861 | }, this ); 862 | 863 | // Opens the media modal. 864 | this.media_modal.open(); 865 | }, 866 | 867 | // Executed when the remove media button is clicked. 868 | removemedia : function() { 869 | 870 | // Updates the model for the view. 871 | this.model.set( { src : '', alt : '', value : '' } ); 872 | } 873 | } ); 874 | 875 | }() ); 876 | -------------------------------------------------------------------------------- /js/butterbean.min.js: -------------------------------------------------------------------------------- 1 | window.butterbean=window.butterbean||{},function(){if(!_.isUndefined(butterbean_data)){var e=butterbean={views:{managers:{},sections:{},controls:{}},templates:{managers:{},sections:{},controls:{}}};e.views.register_manager=function(e,t){"default"!==e&&(this.managers[e]=this.managers.default.extend(t))},e.views.get_manager=function(e){return this.manager_exists(e)?this.managers[e]:this.managers.default},e.views.unregister_manager=function(e){"default"!==e&&this.manager_exists(e)&&delete this.managers[e]},e.views.manager_exists=function(e){return this.managers.hasOwnProperty(e)},e.views.register_section=function(e,t){"default"!==e&&(this.sections[e]=this.sections.default.extend(t))},e.views.get_section=function(e){return this.section_exists(e)?this.sections[e]:this.sections.default},e.views.unregister_section=function(e){"default"!==e&&this.section_exists(e)&&delete this.sections[e]},e.views.section_exists=function(e){return this.sections.hasOwnProperty(e)},e.views.register_control=function(e,t){"default"!==e&&(this.controls[e]=this.controls.default.extend(t))},e.views.get_control=function(e){return this.control_exists(e)?this.controls[e]:this.controls.default},e.views.unregister_control=function(e){"default"!==e&&this.control_exists(e)&&delete this.controls[e]},e.views.control_exists=function(e){return this.controls.hasOwnProperty(e)},e.templates.register_manager=function(e){this.managers[e]=wp.template("butterbean-manager-"+e)},e.templates.get_manager=function(e){return this.manager_exists(e)?this.managers[e]:!1},e.templates.unregister_manager=function(e){this.manager_exists(e)&&delete this.managers[e]},e.templates.manager_exists=function(e){return this.managers.hasOwnProperty(e)},e.templates.register_section=function(e){this.sections[e]=wp.template("butterbean-section-"+e)},e.templates.get_section=function(e){return this.section_exists(e)?this.sections[e]:!1},e.templates.unregister_section=function(e){this.section_exists(e)&&delete this.sections[e]},e.templates.section_exists=function(e){return this.sections.hasOwnProperty(e)},e.templates.register_control=function(e){this.controls[e]=wp.template("butterbean-control-"+e)},e.templates.get_control=function(e){return this.control_exists(e)?this.controls[e]:!1},e.templates.unregister_control=function(e){this.control_exists(e)&&delete this.controls[e]},e.templates.control_exists=function(e){return this.controls.hasOwnProperty(e)},e.render=function(){_.each(butterbean_data.managers,function(t){var i=new n(t),s=e.views.get_manager(t.type),a=new s({model:i}),o=document.getElementById("butterbean-ui-"+i.get("name"));o.className+=" butterbean-ui",o.querySelector(".inside").appendChild(a.render().el),a.subview_render(),a.ready()})};var t=wp.template("butterbean-nav"),n=Backbone.Model.extend({defaults:{name:"",type:"",sections:{},controls:{}}}),i=Backbone.Model.extend({defaults:{name:"",type:"",label:"",description:"",icon:"",manager:"",active:"",selected:!1}}),s=Backbone.Model.extend({defaults:{name:"",type:"",label:"",description:"",icon:"",value:"",choices:{},attr:"",active:"",manager:"",section:"",setting:""}}),a=Backbone.Collection.extend({model:i});e.views.managers["default"]=Backbone.View.extend({tagName:"div",attributes:function(){return{id:"butterbean-manager-"+this.model.get("name"),"class":"butterbean-manager butterbean-manager-"+this.model.get("type")}},initialize:function(){var t=this.model.get("type");e.templates.manager_exists(t)||e.templates.register_manager(t),this.template=e.templates.get_manager(t)},render:function(){return this.el.innerHTML=this.template(this.model.toJSON()),this},subview_render:function(){var t=new a;return _.each(this.model.get("sections"),function(e){t.add(new i(e))}),t.forEach(function(t,n){var i=new o({model:t});document.querySelector("#butterbean-ui-"+t.get("manager")+" .butterbean-nav").appendChild(i.render().el);var s=e.views.get_section(t.attributes.type),a=new s({model:t});document.querySelector("#butterbean-ui-"+t.get("manager")+" .butterbean-content").appendChild(a.render().el),a.ready(),t.set("selected",0===n)},this),_.each(this.model.get("controls"),function(t){var n=new s(t),i=e.views.get_control(t.type),a=new i({model:n});document.getElementById("butterbean-"+n.get("manager")+"-section-"+n.get("section")).appendChild(a.render().el),a.ready()}),this},ready:function(){}}),e.views.sections["default"]=Backbone.View.extend({tagName:"div",attributes:function(){return{id:"butterbean-"+this.model.get("manager")+"-section-"+this.model.get("name"),"class":"butterbean-section butterbean-section-"+this.model.get("type"),"aria-hidden":!this.model.get("selected")}},initialize:function(){this.model.on("change",this.onchange,this);var t=this.model.get("type");e.templates.section_exists(t)||e.templates.register_section(t),this.template=e.templates.get_section(t)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.template(this.model.toJSON())),this},onchange:function(){this.el.setAttribute("aria-hidden",!this.model.get("selected"))},ready:function(){}});var o=Backbone.View.extend({template:t,tagName:"li",attributes:function(){return{"aria-selected":this.model.get("selected")}},initialize:function(){this.model.on("change",this.render,this),this.model.on("change",this.onchange,this)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.template(this.model.toJSON())),this},events:{"click a":"onselect"},onchange:function(){this.el.setAttribute("aria-selected",this.model.get("selected"))},onselect:function(e){e.preventDefault(),_.each(this.model.collection.models,function(e){e.set("selected",!1)},this),this.model.set("selected",!0)}});e.views.controls["default"]=Backbone.View.extend({tagName:"div",attributes:function(){return{id:"butterbean-control-"+this.model.get("name"),"class":"butterbean-control butterbean-control-"+this.model.get("type")}},initialize:function(){var t=this.model.get("type");e.templates.control_exists(t)||e.templates.register_control(t),this.template=e.templates.get_control(t),_.bindAll(this,"render"),this.model.bind("change",this.render)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.template(this.model.toJSON())),this},ready:function(){}}),e.views.register_control("color",{ready:function(){var e=this.model.attributes.options;jQuery(this.$el).find(".butterbean-color-picker").wpColorPicker(e)}}),e.views.register_control("palette",{events:{"change input":"onselect"},onselect:function(){var e=document.querySelector("#"+this.el.id+" input:checked").getAttribute("value"),t=this.model.get("choices");_.each(t,function(t,n){t.selected=n===e}),this.model.set("choices",t).trigger("change",this.model)}}),e.views.register_control("image",{events:{"click .butterbean-add-media":"showmodal","click .butterbean-change-media":"showmodal","click .butterbean-remove-media":"removemedia"},showmodal:function(){return _.isUndefined(this.media_modal)?(this.media_modal=wp.media({frame:"select",multiple:!1,editing:!0,title:this.model.get("l10n").choose,library:{type:"image"},button:{text:this.model.get("l10n").set}}),this.media_modal.on("select",function(){var e=this.media_modal.state().get("selection").first().toJSON(),t=this.model.attributes.size;this.model.set({src:e.sizes[t]?e.sizes[t].url:e.url,alt:e.alt,value:e.id})},this),this.media_modal.open(),void 0):(this.media_modal.open(),void 0)},removemedia:function(){this.model.set({src:"",alt:"",value:""})}})}}(); -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ButterBean 2 | 3 | ButterBean is a neat little post meta box framework built on [Backbone.js](http://backbonejs.org) and [Underscore.js](http://underscorejs.org). You can run it as a standalone plugin or drop it into your own plugins. 4 | 5 | The idea behind ButterBean came about because I often build custom post types that need quite a bit of custom metadata attached to the posts. Separating this into multiple meta boxes wasn't fun or user friendly. So, I decided to go with a single tabbed meta box instead. 6 | 7 | And, that's what ButterBean is. It's essentially a meta box with tabs for lots of content. 8 | 9 | ## Just the interface 10 | 11 | A lot of meta box frameworks try to do everything. They handle backend output, frontend output, and everything else you can think of. ButterBean is meant to be an interface only. Because every project's needs are vastly different, it doesn't make sense to stick you with a bunch of things you don't need. This means that the code can stay relatively lean and flexible, which makes it perfect for bundling in your plugins. 12 | 13 | So, don't go looking for functions for outputting metadata on the front end from ButterBean. It doesn't have any. Use the core WordPress functionality or build your own wrapper functions. 14 | 15 | ## Documentation 16 | 17 | This is a quick guide. If you're familiar with the WordPress Customization API, you should probably pick this up fairly quickly. A lot of the same concepts are used here. 18 | 19 | ### Installation 20 | 21 | Drop the `butterbean` folder into your plugin. That's the simple part. 22 | 23 | The script will auto-load itself on the correct admin hooks. You just need to load it like so: 24 | 25 | ``` 26 | add_action( 'plugins_loaded', 'th_load' ); 27 | 28 | function th_load() { 29 | 30 | require_once( 'path/to/butterbean/butterbean.php' ); 31 | } 32 | ``` 33 | 34 | ### Registration 35 | 36 | There's a built-in action hook called `butterbean_register`. You're going to use that to register everything. So, you need to set up a callback function for that. 37 | 38 | ``` 39 | add_action( 'butterbean_register', 'th_register', 10, 2 ); 40 | 41 | function th_register( $butterbean, $post_type ) { 42 | 43 | // Register managers, sections, controls, and settings here. 44 | } 45 | ``` 46 | 47 | #### Registering a manager 48 | 49 | A **manager** is a group of sections, controls, and settings. It's displayed as a single meta box. There can be multiple managers per screen (don't try multiples yet). 50 | 51 | ``` 52 | $butterbean->register_manager( 53 | 'example', 54 | array( 55 | 'label' => esc_html__( 'Example', 'your-textdomain' ), 56 | 'post_type' => 'your_post_type', 57 | 'context' => 'normal', 58 | 'priority' => 'high' 59 | ) 60 | ); 61 | 62 | $manager = $butterbean->get_manager( 'example' ); 63 | ``` 64 | 65 | #### Registering a section 66 | 67 | A **section** is a group of controls within a manager. They are presented as "tabbed" sections in the UI. 68 | 69 | ``` 70 | $manager->register_section( 71 | 'section_1', 72 | array( 73 | 'label' => esc_html__( 'Section 1', 'your-textdomain' ), 74 | 'icon' => 'dashicons-admin-generic' 75 | ) 76 | ); 77 | ``` 78 | 79 | #### Registering a control 80 | 81 | A **control** is essentially a form field. It's the field(s) that a user enters data into. Each control belongs to a section. Each control should also be tied to a setting (below). 82 | 83 | ``` 84 | $manager->register_control( 85 | 'abc_xyz', // Same as setting name. 86 | array( 87 | 'type' => 'text', 88 | 'section' => 'section_1', 89 | 'label' => esc_html__( 'Control ABC', 'your-textdomain' ), 90 | 'attr' => array( 'class' => 'widefat' ) 91 | ) 92 | ); 93 | ``` 94 | 95 | #### Registering a setting 96 | 97 | A **setting** is nothing more than some post metadata and how it gets stored. A setting belongs to a specific control. 98 | 99 | ``` 100 | $manager->register_setting( 101 | 'abc_xyz', // Same as control name. 102 | array( 103 | 'sanitize_callback' => 'wp_filter_nohtml_kses' 104 | ) 105 | ); 106 | ``` 107 | 108 | ### JavaScript API 109 | 110 | ButterBean was built using [Backbone](http://backbonejs.org) for handling models, collections, and views. It uses [Underscore](http://underscorejs.org) for rendering templates for the views. All output is handled via JavaScript rather than PHP so that we can do cool stuff on the fly without having to reload the page. This is particularly useful when you start building more complex controls. 111 | 112 | You'll never need to touch JavaScript until you need to build a control that relies on JavaScript. 113 | 114 | #### The butterbean object 115 | 116 | `butterbean` is the global object that houses everything you ever want to touch on the JavaScript side of things. It's located in the `js/butterbean.js` file. This file is well-documented, so you'll want to dive into it for doing more advanced stuff. 117 | 118 | `butterbean.views.register_control()` is what most people will end up using. It's a function for registering a custom control view. New views can be created for each `type` of control. 119 | 120 | Here's a quick example of registering a view for a color control where we need to call the core WordPress `wpColorPicker()` function. It uses the `ready()` function, which is fired after the HTML has been rendered for the view. 121 | 122 | ``` 123 | ( function() { 124 | 125 | butterbean.views.register_control( 'color', { 126 | 127 | // Calls the core WP color picker for the control's input. 128 | ready : function() { 129 | 130 | var options = this.model.attributes.options; 131 | 132 | jQuery( this.$el ).find( '.butterbean-color-picker' ).wpColorPicker( options ); 133 | } 134 | } ); 135 | }() ); 136 | ``` 137 | 138 | ## Professional Support 139 | 140 | If you need professional plugin support from me, the plugin author, you can access the support forums at [Theme Hybrid](http://themehybrid.com/board/topics), which is a professional WordPress help/support site where I handle support for all my plugins and themes for a community of 70,000+ users (and growing). 141 | 142 | ## Copyright and License 143 | 144 | Various ideas from different projects have made their way into ButterBean. A few of the projects that had an important impact on the direction of this project are: 145 | 146 | * Architecturally, the PHP code was modeled after the core WordPress Customization API. - [GPL 2+](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) 147 | * The design concept of the default tabbed interface was taken from [WooCommerce](http://www.woothemes.com/woocommerce/). © WooThemes - [GPL 3+](http://www.gnu.org/licenses/gpl.html) 148 | * Code ideas for the media frame were borrowed from [WP Term Images](https://wordpress.org/plugins/wp-term-images/). © John James Jacoby - [GPL 2+](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) 149 | 150 | This project is licensed under the [GNU GPL](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html), version 2 or later. 151 | 152 | 2015-2016 © [Justin Tadlock](http://justintadlock.com). 153 | -------------------------------------------------------------------------------- /tmpl/control-checkbox.php: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /tmpl/control-checkboxes.php: -------------------------------------------------------------------------------- 1 | <# if ( data.label ) { #> 2 | {{ data.label }} 3 | <# } #> 4 | 5 | <# if ( data.description ) { #> 6 | {{{ data.description }}} 7 | <# } #> 8 | 9 |
    10 | 11 | <# _.each( data.choices, function( label, choice ) { #> 12 | 13 |
  • 14 | 18 |
  • 19 | 20 | <# } ) #> 21 | 22 |
23 | -------------------------------------------------------------------------------- /tmpl/control-color.php: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /tmpl/control-datetime.php: -------------------------------------------------------------------------------- 1 | <# if ( data.label ) { #> 2 | {{ data.label }} 3 | <# } #> 4 | 5 | <# if ( data.description ) { #> 6 | {{{ data.description }}} 7 | <# } #> 8 | 9 | 10 | {{ data.month.label }} 11 | 16 | '; 17 | 18 | $day = ''; 22 | 23 | $year = ''; 27 | 28 | $hour = ''; 32 | 33 | $minute = ''; 37 | 38 | $second = ''; ?> 42 | 43 | <# if ( data.show_time ) { #> 44 | 45 | 48 | 49 | <# } else { #> 50 | 51 | 54 | 55 | <# } #> 56 | -------------------------------------------------------------------------------- /tmpl/control-image.php: -------------------------------------------------------------------------------- 1 | <# if ( data.label ) { #> 2 | {{ data.label }} 3 | <# } #> 4 | 5 | <# if ( data.description ) { #> 6 | {{{ data.description }}} 7 | <# } #> 8 | 9 | 10 | 11 | <# if ( data.src ) { #> 12 | {{ data.alt }} 13 | <# } else { #> 14 |
{{ data.l10n.placeholder }}
15 | <# } #> 16 | 17 |

18 | <# if ( data.src ) { #> 19 | 20 | 21 | <# } else { #> 22 | 23 | <# } #> 24 |

25 | -------------------------------------------------------------------------------- /tmpl/control-multi-avatars.php: -------------------------------------------------------------------------------- 1 | <# if ( data.label ) { #> 2 | {{ data.label }} 3 | <# } #> 4 | 5 | <# if ( data.description ) { #> 6 | {{{ data.description }}} 7 | <# } #> 8 | 9 |
10 | 11 | <# _.each( data.choices, function( user ) { #> 12 | 13 | 20 | 21 | <# } ) #> 22 | 23 |
24 | -------------------------------------------------------------------------------- /tmpl/control-palette.php: -------------------------------------------------------------------------------- 1 | <# if ( data.label ) { #> 2 | {{ data.label }} 3 | <# } #> 4 | 5 | <# if ( data.description ) { #> 6 | {{{ data.description }}} 7 | <# } #> 8 | 9 | <# _.each( data.choices, function( palette, choice ) { #> 10 | 23 | <# } ) #> 24 | -------------------------------------------------------------------------------- /tmpl/control-parent.php: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /tmpl/control-radio-image.php: -------------------------------------------------------------------------------- 1 | <# if ( data.label ) { #> 2 | {{ data.label }} 3 | <# } #> 4 | 5 | <# if ( data.description ) { #> 6 | {{{ data.description }}} 7 | <# } #> 8 | 9 | <# _.each( data.choices, function( args, choice ) { #> 10 | 11 | 16 | 17 | <# } ) #> 18 | -------------------------------------------------------------------------------- /tmpl/control-radio.php: -------------------------------------------------------------------------------- 1 | <# if ( data.label ) { #> 2 | {{ data.label }} 3 | <# } #> 4 | 5 | <# if ( data.description ) { #> 6 | {{{ data.description }}} 7 | <# } #> 8 | 9 |
    10 | 11 | <# _.each( data.choices, function( label, choice ) { #> 12 | 13 |
  • 14 | 18 |
  • 19 | 20 | <# } ) #> 21 | 22 |
23 | -------------------------------------------------------------------------------- /tmpl/control-select-group.php: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /tmpl/control-select.php: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /tmpl/control-textarea.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmpl/control.php: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /tmpl/manager.php: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | -------------------------------------------------------------------------------- /tmpl/nav.php: -------------------------------------------------------------------------------- 1 | {{ data.label }} 2 | -------------------------------------------------------------------------------- /tmpl/section.php: -------------------------------------------------------------------------------- 1 | <# if ( data.description ) { #> 2 | {{{ data.description }}} 3 | <# } #> 4 | --------------------------------------------------------------------------------