| 1 |
<?php |
| 2 |
/** |
| 3 |
* Handles hooking CMB2 forms/metaboxes into the post/attachement/user screens |
| 4 |
* and handles hooking in and saving those fields. |
| 5 |
* |
| 6 |
* @since 2.0.0 |
| 7 |
* |
| 8 |
* @category WordPress_Plugin |
| 9 |
* @package CMB2 |
| 10 |
* @author CMB2 team |
| 11 |
* @license GPL-2.0+ |
| 12 |
* @link https://cmb2.io |
| 13 |
*/ |
| 14 |
class CMB2_Options_Hookup extends CMB2_hookup { |
| 15 |
|
| 16 |
/** |
| 17 |
* The object type we are performing the hookup for |
| 18 |
* |
| 19 |
* @var string |
| 20 |
* @since 2.0.9 |
| 21 |
*/ |
| 22 |
protected $object_type = 'options-page'; |
| 23 |
|
| 24 |
/** |
| 25 |
* Options page key. |
| 26 |
* |
| 27 |
* @var string |
| 28 |
* @since 2.2.5 |
| 29 |
*/ |
| 30 |
protected $option_key = ''; |
| 31 |
|
| 32 |
/** |
| 33 |
* Constructor |
| 34 |
* |
| 35 |
* @since 2.0.0 |
| 36 |
* @param CMB2 $cmb The CMB2 object to hookup. |
| 37 |
* @param string $option_key Option key to use. |
| 38 |
*/ |
| 39 |
public function __construct( CMB2 $cmb, $option_key ) { |
| 40 |
$this->cmb = $cmb; |
| 41 |
$this->option_key = $option_key; |
| 42 |
} |
| 43 |
|
| 44 |
public function hooks() { |
| 45 |
if ( empty( $this->option_key ) ) { |
| 46 |
return; |
| 47 |
} |
| 48 |
|
| 49 |
if ( ! $this->cmb->prop( 'autoload', true ) ) { |
| 50 |
// Disable option autoload if requested. |
| 51 |
add_filter( "cmb2_should_autoload_{$this->option_key}", '__return_false' ); |
| 52 |
} |
| 53 |
|
| 54 |
/** |
| 55 |
* For WP < 4.7. Ensure the register_setting function exists. |
| 56 |
*/ |
| 57 |
if ( ! CMB2_Utils::wp_at_least( '4.7' ) && ! function_exists( 'register_setting' ) ) { |
| 58 |
require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
| 59 |
} |
| 60 |
|
| 61 |
// Register setting to cmb2 group. |
| 62 |
register_setting( 'cmb2', $this->option_key ); |
| 63 |
|
| 64 |
// Handle saving the data. |
| 65 |
add_action( 'admin_post_' . $this->option_key, array( $this, 'save_options' ) ); |
| 66 |
|
| 67 |
// Optionally network_admin_menu. |
| 68 |
$hook = $this->cmb->prop( 'admin_menu_hook' ); |
| 69 |
|
| 70 |
// Hook in to add our menu. |
| 71 |
add_action( $hook, array( $this, 'options_page_menu_hooks' ) ); |
| 72 |
|
| 73 |
// If in the network admin, need to use get/update_site_option. |
| 74 |
if ( 'network_admin_menu' === $hook ) { |
| 75 |
// Override CMB's getter. |
| 76 |
add_filter( "cmb2_override_option_get_{$this->option_key}", array( $this, 'network_get_override' ), 10, 2 ); |
| 77 |
// Override CMB's setter. |
| 78 |
add_filter( "cmb2_override_option_save_{$this->option_key}", array( $this, 'network_update_override' ), 10, 2 ); |
| 79 |
} |
| 80 |
} |
| 81 |
|
| 82 |
/** |
| 83 |
* Hook up our admin menu item and admin page. |
| 84 |
* |
| 85 |
* @since 2.2.5 |
| 86 |
* |
| 87 |
* @return void |
| 88 |
*/ |
| 89 |
public function options_page_menu_hooks() { |
| 90 |
$parent_slug = $this->cmb->prop( 'parent_slug' ); |
| 91 |
$title = $this->cmb->prop( 'title' ); |
| 92 |
$menu_title = $this->cmb->prop( 'menu_title', $title ); |
| 93 |
$capability = $this->cmb->prop( 'capability' ); |
| 94 |
$callback = array( $this, 'options_page_output' ); |
| 95 |
|
| 96 |
if ( $parent_slug ) { |
| 97 |
$page_hook = add_submenu_page( |
| 98 |
$parent_slug, |
| 99 |
$title, |
| 100 |
$menu_title, |
| 101 |
$capability, |
| 102 |
$this->option_key, |
| 103 |
$callback |
| 104 |
); |
| 105 |
} else { |
| 106 |
$page_hook = add_menu_page( |
| 107 |
$title, |
| 108 |
$menu_title, |
| 109 |
$capability, |
| 110 |
$this->option_key, |
| 111 |
$callback, |
| 112 |
$this->cmb->prop( 'icon_url' ), |
| 113 |
$this->cmb->prop( 'position' ) |
| 114 |
); |
| 115 |
} |
| 116 |
|
| 117 |
if ( $this->cmb->prop( 'cmb_styles' ) ) { |
| 118 |
// Include CMB CSS in the head to avoid FOUC. |
| 119 |
add_action( "admin_print_styles-{$page_hook}", array( 'CMB2_hookup', 'enqueue_cmb_css' ) ); |
| 120 |
} |
| 121 |
|
| 122 |
$this->maybe_register_message(); |
| 123 |
} |
| 124 |
|
| 125 |
/** |
| 126 |
* If there is a message callback, let it determine how to register the message, |
| 127 |
* else add a settings message if on this settings page. |
| 128 |
* |
| 129 |
* @since 2.2.6 |
| 130 |
* |
| 131 |
* @return void |
| 132 |
*/ |
| 133 |
public function maybe_register_message() { |
| 134 |
$is_options_page = self::is_page( $this->option_key ); |
| 135 |
$should_notify = ! $this->cmb->prop( 'disable_settings_errors' ) && isset( $_GET['settings-updated'] ) && $is_options_page; |
| 136 |
$is_updated = $should_notify && 'true' === $_GET['settings-updated']; |
| 137 |
$setting = "{$this->option_key}-notices"; |
| 138 |
$code = ''; |
| 139 |
$message = __( 'Nothing to update.', 'cmb2' ); |
| 140 |
$type = 'notice-warning'; |
| 141 |
|
| 142 |
if ( $is_updated ) { |
| 143 |
$message = __( 'Settings updated.', 'cmb2' ); |
| 144 |
$type = 'updated'; |
| 145 |
} |
| 146 |
|
| 147 |
// Check if parameter has registered a callback. |
| 148 |
if ( $cb = $this->cmb->maybe_callback( 'message_cb' ) ) { |
| 149 |
|
| 150 |
/** |
| 151 |
* The 'message_cb' callback will receive the following parameters. |
| 152 |
* Unless there are other reasons for notifications, the callback should only |
| 153 |
* `add_settings_error()` if `$args['should_notify']` is truthy. |
| 154 |
* |
| 155 |
* @param CMB2 $cmb The CMB2 object. |
| 156 |
* @param array $args { |
| 157 |
* An array of message arguments |
| 158 |
* |
| 159 |
* @type bool $is_options_page Whether current page is this options page. |
| 160 |
* @type bool $should_notify Whether options were saved and we should be notified. |
| 161 |
* @type bool $is_updated Whether options were updated with save (or stayed the same). |
| 162 |
* @type string $setting For add_settings_error(), Slug title of the setting to which |
| 163 |
* this error applies. |
| 164 |
* @type string $code For add_settings_error(), Slug-name to identify the error. |
| 165 |
* Used as part of 'id' attribute in HTML output. |
| 166 |
* @type string $message For add_settings_error(), The formatted message text to display |
| 167 |
* to the user (will be shown inside styled `<div>` and `<p>` tags). |
| 168 |
* Will be 'Settings updated.' if $is_updated is true, else 'Nothing to update.' |
| 169 |
* @type string $type For add_settings_error(), Message type, controls HTML class. |
| 170 |
* Accepts 'error', 'updated', '', 'notice-warning', etc. |
| 171 |
* Will be 'updated' if $is_updated is true, else 'notice-warning'. |
| 172 |
* } |
| 173 |
*/ |
| 174 |
$args = compact( 'is_options_page', 'should_notify', 'is_updated', 'setting', 'code', 'message', 'type' ); |
| 175 |
|
| 176 |
$this->cmb->do_callback( $cb, $args ); |
| 177 |
|
| 178 |
} elseif ( $should_notify ) { |
| 179 |
|
| 180 |
add_settings_error( $setting, $code, $message, $type ); |
| 181 |
} |
| 182 |
} |
| 183 |
|
| 184 |
/** |
| 185 |
* Display options-page output. To override, set 'display_cb' box property. |
| 186 |
* |
| 187 |
* @since 2.2.5 |
| 188 |
*/ |
| 189 |
public function options_page_output() { |
| 190 |
$this->maybe_output_settings_notices(); |
| 191 |
|
| 192 |
$callback = $this->cmb->prop( 'display_cb' ); |
| 193 |
if ( is_callable( $callback ) ) { |
| 194 |
return call_user_func( $callback, $this ); |
| 195 |
} |
| 196 |
|
| 197 |
$tabs = $this->get_tab_group_tabs(); |
| 198 |
?> |
| 199 |
<div class="wrap cmb2-options-page option-<?php echo $this->option_key; ?>"> |
| 200 |
<?php if ( $this->cmb->prop( 'title' ) ) : ?> |
| 201 |
<h2><?php echo wp_kses_post( $this->cmb->prop( 'title' ) ); ?></h2> |
| 202 |
<?php endif; ?> |
| 203 |
<?php if ( ! empty( $tabs ) ) : ?> |
| 204 |
<h2 class="nav-tab-wrapper"> |
| 205 |
<?php foreach ( $tabs as $option_key => $tab_title ) : ?> |
| 206 |
<a class="nav-tab<?php if ( self::is_page( $option_key ) ) : ?> nav-tab-active<?php endif; ?>" href="<?php menu_page_url( $option_key ); ?>"><?php echo wp_kses_post( $tab_title ); ?></a> |
| 207 |
<?php endforeach; ?> |
| 208 |
</h2> |
| 209 |
<?php endif; ?> |
| 210 |
<form class="cmb-form" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="POST" id="<?php echo $this->cmb->cmb_id; ?>" enctype="multipart/form-data" encoding="multipart/form-data"> |
| 211 |
<input type="hidden" name="action" value="<?php echo esc_attr( $this->option_key ); ?>"> |
| 212 |
<?php $this->options_page_metabox(); ?> |
| 213 |
<?php submit_button( esc_attr( $this->cmb->prop( 'save_button' ) ), 'primary', 'submit-cmb' ); ?> |
| 214 |
</form> |
| 215 |
</div> |
| 216 |
<?php |
| 217 |
} |
| 218 |
|
| 219 |
/** |
| 220 |
* Outputs the settings notices if a) not a sub-page of 'options-general.php' |
| 221 |
* (because settings_errors() already called in wp-admin/options-head.php), |
| 222 |
* and b) the 'disable_settings_errors' prop is not set or truthy. |
| 223 |
* |
| 224 |
* @since 2.2.5 |
| 225 |
* @return void |
| 226 |
*/ |
| 227 |
public function maybe_output_settings_notices() { |
| 228 |
global $parent_file; |
| 229 |
|
| 230 |
// The settings sub-pages will already have settings_errors() called in wp-admin/options-head.php. |
| 231 |
if ( 'options-general.php' !== $parent_file ) { |
| 232 |
settings_errors( "{$this->option_key}-notices" ); |
| 233 |
} |
| 234 |
} |
| 235 |
|
| 236 |
/** |
| 237 |
* Gets navigation tabs array for CMB2 options pages which share the |
| 238 |
* same tab_group property. |
| 239 |
* |
| 240 |
* @since 2.4.0 |
| 241 |
* @return array Array of tab information ($option_key => $tab_title) |
| 242 |
*/ |
| 243 |
public function get_tab_group_tabs() { |
| 244 |
$tab_group = $this->cmb->prop( 'tab_group' ); |
| 245 |
$tabs = array(); |
| 246 |
|
| 247 |
if ( $tab_group ) { |
| 248 |
$boxes = CMB2_Boxes::get_by( 'tab_group', $tab_group ); |
| 249 |
|
| 250 |
foreach ( $boxes as $cmb_id => $cmb ) { |
| 251 |
$option_key = $cmb->options_page_keys(); |
| 252 |
|
| 253 |
// Must have an option key, must be an options page box. |
| 254 |
if ( ! isset( $option_key[0] ) || 'options-page' !== $cmb->mb_object_type() ) { |
| 255 |
continue; |
| 256 |
} |
| 257 |
|
| 258 |
$tabs[ $option_key[0] ] = $cmb->prop( 'tab_title', $cmb->prop( 'title' ) ); |
| 259 |
} |
| 260 |
} |
| 261 |
|
| 262 |
return $tabs; |
| 263 |
} |
| 264 |
|
| 265 |
/** |
| 266 |
* Display metaboxes for an options-page object. |
| 267 |
* |
| 268 |
* @since 2.2.5 |
| 269 |
*/ |
| 270 |
public function options_page_metabox() { |
| 271 |
$this->show_form_for_type( 'options-page' ); |
| 272 |
} |
| 273 |
|
| 274 |
/** |
| 275 |
* Save data from options page, then redirects back. |
| 276 |
* |
| 277 |
* @since 2.2.5 |
| 278 |
* @return void |
| 279 |
*/ |
| 280 |
public function save_options() { |
| 281 |
$url = wp_get_referer(); |
| 282 |
if ( ! $url ) { |
| 283 |
$url = admin_url(); |
| 284 |
} |
| 285 |
|
| 286 |
if ( |
| 287 |
$this->can_save( 'options-page' ) |
| 288 |
// check params. |
| 289 |
&& isset( $_POST['submit-cmb'], $_POST['action'] ) |
| 290 |
&& $this->option_key === $_POST['action'] |
| 291 |
) { |
| 292 |
|
| 293 |
$updated = $this->cmb |
| 294 |
->save_fields( $this->option_key, $this->cmb->object_type(), $_POST ) |
| 295 |
->was_updated(); // Will be false if no values were changed/updated. |
| 296 |
|
| 297 |
$url = add_query_arg( 'settings-updated', $updated ? 'true' : 'false', $url ); |
| 298 |
} |
| 299 |
|
| 300 |
wp_safe_redirect( esc_url_raw( $url ), 303 /* WP_Http::SEE_OTHER */ ); |
| 301 |
exit; |
| 302 |
} |
| 303 |
|
| 304 |
/** |
| 305 |
* Replaces get_option with get_site_option. |
| 306 |
* |
| 307 |
* @since 2.2.5 |
| 308 |
* |
| 309 |
* @param mixed $test Not used. |
| 310 |
* @param mixed $default Default value to use. |
| 311 |
* @return mixed Value set for the network option. |
| 312 |
*/ |
| 313 |
public function network_get_override( $test, $default = false ) { |
| 314 |
return get_site_option( $this->option_key, $default ); |
| 315 |
} |
| 316 |
|
| 317 |
/** |
| 318 |
* Replaces update_option with update_site_option. |
| 319 |
* |
| 320 |
* @since 2.2.5 |
| 321 |
* |
| 322 |
* @param mixed $test Not used. |
| 323 |
* @param mixed $option_value Value to use. |
| 324 |
* @return bool Success/Failure |
| 325 |
*/ |
| 326 |
public function network_update_override( $test, $option_value ) { |
| 327 |
return update_site_option( $this->option_key, $option_value ); |
| 328 |
} |
| 329 |
|
| 330 |
/** |
| 331 |
* Determines if given page slug matches the 'page' GET query variable. |
| 332 |
* |
| 333 |
* @since 2.4.0 |
| 334 |
* |
| 335 |
* @param string $page Page slug. |
| 336 |
* @return boolean |
| 337 |
*/ |
| 338 |
public static function is_page( $page ) { |
| 339 |
return isset( $_GET['page'] ) && $page === $_GET['page']; |
| 340 |
} |
| 341 |
|
| 342 |
/** |
| 343 |
* Magic getter for our object. |
| 344 |
* |
| 345 |
* @param string $field Property to retrieve. |
| 346 |
* |
| 347 |
* @throws Exception Throws an exception if the field is invalid. |
| 348 |
* @return mixed |
| 349 |
*/ |
| 350 |
public function __get( $field ) { |
| 351 |
switch ( $field ) { |
| 352 |
case 'object_type': |
| 353 |
case 'option_key': |
| 354 |
case 'cmb': |
| 355 |
return $this->{$field}; |
| 356 |
default: |
| 357 |
throw new Exception( sprintf( esc_html__( 'Invalid %1$s property: %2$s', 'cmb2' ), __CLASS__, $field ) ); |
| 358 |
} |
| 359 |
} |
| 360 |
} |
| 361 |
|