class-redux-extension-metaboxes.php (51268B)
1 <?php 2 /** 3 * Redux Metabox Extension Class 4 * 5 * @package Redux Extentions 6 * @author Dovy Paukstys <dovy@reduxframework.com> & Kevin Provance <kevin.provance@gmail.com> 7 * @class Redux_Extension_Metaboxes 8 * 9 * @version 4.0.0 10 */ 11 12 defined( 'ABSPATH' ) || exit; 13 14 // Don't duplicate me! 15 if ( ! class_exists( 'Redux_Extension_Metaboxes', false ) ) { 16 17 /** 18 * Main Redux_Extension_Metaboxes class 19 * 20 * @since 1.0.0 21 */ 22 class Redux_Extension_Metaboxes extends Redux_Extension_Abstract { 23 24 /** 25 * Extension version. 26 * 27 * @var string 28 */ 29 public static $version = '4.2.0'; 30 31 /** 32 * Extension friendly name. 33 * 34 * @var string 35 */ 36 public $ext_name = 'Metaboxes'; 37 38 /** 39 * Boxes array. 40 * 41 * @var array 42 */ 43 public $boxes = array(); 44 45 /** 46 * Post types array. 47 * 48 * @var array 49 */ 50 public $post_types = array(); 51 52 /** 53 * Post type. 54 * 55 * @var string 56 */ 57 public $post_type; 58 59 /** 60 * Sections array. 61 * 62 * @var array 63 */ 64 public $sections = array(); 65 66 /** 67 * CSS output array. 68 * 69 * @var array 70 */ 71 public $output = array(); 72 73 /** 74 * ReduxFramework object pointer. 75 * 76 * @var object 77 */ 78 public $parent = null; 79 80 /** 81 * Options array. 82 * 83 * @var array 84 */ 85 public $options = array(); 86 87 /** 88 * Parent options array. 89 * 90 * @var array 91 */ 92 public $parent_options = array(); 93 94 /** 95 * Parent defaults array. 96 * 97 * @var array 98 */ 99 public $parent_defaults = array(); 100 101 /** 102 * Post type fields array. 103 * 104 * @var array 105 */ 106 public $post_type_fields = array(); 107 108 /** 109 * Options defaults array. 110 * 111 * @var array 112 */ 113 public $options_defaults = array(); 114 115 /** 116 * Replace array. 117 * 118 * @var array 119 */ 120 public $to_replace = array(); 121 122 /** 123 * Extension URI. 124 * 125 * @var string|void 126 */ 127 public $extension_url; 128 129 /** 130 * Extension Directory. 131 * 132 * @var string 133 */ 134 public $extension_dir; 135 136 /** 137 * Meta data array. 138 * 139 * @var array 140 */ 141 public $meta = array(); 142 143 /** 144 * Post ID. 145 * 146 * @var int 147 */ 148 public $post_id = 0; 149 150 /** 151 * Base URI. 152 * 153 * @var string 154 */ 155 public $base_url; 156 157 /** 158 * WP_Links array. 159 * 160 * @var array 161 */ 162 public $wp_links = array(); 163 164 /** 165 * ReduxFramework_extension_metaboxes constructor. 166 * 167 * @param object $parent ReduxFramework object. 168 */ 169 public function __construct( $parent ) { 170 global $pagenow; 171 172 $this->parent = $parent; 173 174 $this->parent->extensions['metaboxes'] = $this; 175 176 if ( empty( self::$extension_dir ) ) { 177 $this->extension_dir = trailingslashit( str_replace( '\\', '/', dirname( __FILE__ ) ) ); 178 $this->extension_url = site_url( str_replace( trailingslashit( str_replace( '\\', '/', ABSPATH ) ), '', $this->extension_dir ) ); 179 } 180 181 // Only run metaboxes on the pages/posts, not the front-end. 182 // The DOING_AJAX check allows for redux_post_meta to work inside 183 // AJAX calls. - kp. 184 if ( 'post-new.php' !== $pagenow && 'post.php' !== $pagenow ) { 185 if ( is_admin() ) { 186 if ( defined( 'DOING_AJAX' ) && ! DOING_AJAX ) { 187 return; 188 } 189 190 return; 191 } 192 } 193 194 if ( 'wp-cron.php' === $pagenow || 'wp-comments-post.php' === $pagenow ) { 195 return; 196 } 197 198 // Must not update the DB when just updating metaboxes. Sheesh. 199 if ( is_admin() && ( 'post-new.php' === $pagenow || 'post.php' === $pagenow ) ) { 200 $this->parent->never_save_to_db = true; 201 } 202 203 // phpcs:ignore Generic.Strings.UnnecessaryStringConcat 204 add_action( 'add_' . 'meta_' . 'boxes', array( $this, 'add' ) ); 205 add_action( 'save_post', array( $this, 'meta_boxes_save' ), 1, 2 ); 206 add_action( 'pre_post_update', array( $this, 'pre_post_update' ) ); 207 add_action( 'admin_notices', array( $this, 'meta_boxes_show_errors' ), 0 ); 208 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ), 20 ); 209 210 // Global variable overrides for within loops. 211 add_action( 'the_post', array( $this, 'loop_start' ), 0 ); 212 add_action( 'loop_end', array( $this, 'loop_end' ), 0 ); 213 214 $this->init(); 215 } 216 217 /** 218 * Added class names to metabox DIV. 219 * 220 * @param array $classes Class array. 221 * 222 * @return array 223 */ 224 public function add_box_classes( array $classes ): array { 225 $classes[] = 'redux-metabox'; 226 $classes[] = 'redux-' . $this->parent->args['opt_name']; 227 228 if ( '' !== $this->parent->args['class'] ) { 229 $classes[] = $this->parent->args['class']; 230 } 231 232 return $classes; 233 } 234 235 /** 236 * Class init. 237 */ 238 public function init() { 239 global $pagenow; 240 241 // phpcs:ignore WordPress.NamingConventions.ValidHookName 242 $this->boxes = apply_filters( 'redux/metaboxes/' . $this->parent->args['opt_name'] . '/boxes', $this->boxes, $this->parent->args['opt_name'] ); 243 244 if ( empty( $this->boxes ) && class_exists( 'Redux_Metaboxes' ) ) { 245 $this->boxes = Redux_Metaboxes::construct_boxes( $this->parent->args['opt_name'] ); 246 } 247 248 if ( empty( $this->boxes ) || ! is_array( $this->boxes ) ) { 249 return; 250 } 251 252 if ( isset( Redux_Core::$server['HTTP_HOST'] ) && isset( Redux_Core::$server['REQUEST_URI'] ) ) { 253 $this->base_url = ( is_ssl() ? 'https://' : 'http://' ) . sanitize_text_field( wp_unslash( Redux_Core::$server['HTTP_HOST'] ) ) . sanitize_text_field( wp_unslash( Redux_Core::$server['REQUEST_URI'] ) ); // Safe & Reliable. 254 $this->post_id = $this->url_to_postid( ( is_ssl() ? 'https://' : 'http://' ) . sanitize_text_field( wp_unslash( Redux_Core::$server['HTTP_HOST'] ) ) . sanitize_text_field( wp_unslash( Redux_Core::$server['REQUEST_URI'] ) ) ); 255 } 256 257 if ( is_admin() && isset( $_GET['post_type'] ) && ! empty( $_GET['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification 258 $this->post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) ); // phpcs:ignore WordPress.Security.NonceVerification 259 } else { 260 $this->post_type = get_post_type( $this->post_id ); 261 } 262 263 foreach ( $this->boxes as $bk => $box ) { 264 265 // If the post ids for this box are set, we're limiting to the current post id. 266 if ( isset( $box['post_ids'] ) && ! empty( $box['post_ids'] ) ) { 267 if ( ! is_array( $box['post_ids'] ) ) { 268 $box['post_ids'] = array( $box['post_ids'] ); 269 } 270 if ( ! in_array( $this->post_id, $box['post_ids'], true ) ) { 271 continue; 272 } 273 } 274 275 if ( ! empty( $box['sections'] ) ) { 276 $this->sections = $box['sections']; 277 278 array_merge( $this->parent->sections, $box['sections'] ); 279 280 $this->post_types = wp_parse_args( $this->post_types, $box['post_types'] ); 281 282 // Checking to overide the parent variables. 283 $add_field = false; 284 285 foreach ( $box['post_types'] as $type ) { 286 if ( $this->post_type === $type ) { 287 $add_field = true; 288 } 289 } 290 291 // Replacing all the fields. 292 if ( $add_field || ( ( is_admin() && ( 'post-new.php' === $pagenow || 'post.php' === $pagenow ) ) || ( ! is_admin() ) ) ) { 293 $run_hooks = true; 294 295 $box_id = 'redux-' . $this->parent->args['opt_name'] . '-metabox-' . $box['id']; 296 297 // phpcs:ignore WordPress.NamingConventions.ValidHookName 298 do_action( 'redux/' . $this->parent->args['opt_name'] . '/extensions/metabox/init', $this, $box ); 299 300 if ( isset( $box['page_template'] ) && 'page' === $this->post_type ) { 301 if ( ! is_array( $box['page_template'] ) ) { 302 $box['page_template'] = array( $box['page_template'] ); 303 } 304 305 $this->wp_links[ $box_id ]['page_template'] = isset( $this->wp_links[ $box_id ]['page_template'] ) ? wp_parse_args( $this->wp_links[ $box_id ]['page_template'], $box['page_template'] ) : $box['page_template']; 306 } 307 308 if ( isset( $box['post_format'] ) && ( in_array( $this->post_type, $this->post_types, true ) || '' === $this->post_type ) ) { 309 if ( ! is_array( $box['post_format'] ) ) { 310 $box['post_format'] = array( $box['post_format'] ); 311 } 312 313 $this->wp_links[ $box_id ]['post_format'] = isset( $this->wp_links[ $box_id ]['post_format'] ) ? wp_parse_args( $this->wp_links[ $box_id ]['post_format'], $box['post_format'] ) : $box['post_format']; 314 } 315 316 $this->meta[ $this->post_id ] = $this->get_meta( $this->post_id ); 317 318 foreach ( $box['sections'] as $sk => $section ) { 319 if ( isset( $section['fields'] ) && ! empty( $section['fields'] ) ) { 320 foreach ( $section['fields'] as $fk => $field ) { 321 if ( ! isset( $field['class'] ) ) { 322 $field['class'] = ''; 323 324 $this->boxes[ $bk ]['sections'][ $sk ]['fields'][ $fk ] = $field; 325 } 326 327 if ( $add_field || ( ( is_admin() && ( 'post-new.php' === $pagenow || 'post.php' === $pagenow ) ) || ( ! is_admin() ) ) ) { 328 if ( empty( $field['id'] ) ) { 329 continue; 330 } 331 332 if ( isset( $field['default'] ) ) { 333 $this->options_defaults[ $field['id'] ] = $field['default']; 334 } else { 335 $this->options_defaults[ $field['id'] ] = $this->field_default( $field ); 336 } 337 338 foreach ( $box['post_types'] as $type ) { 339 $this->post_type_fields[ $type ][ $field['id'] ] = 1; 340 } 341 342 if ( isset( $field['output'] ) && ! empty( $field['output'] ) ) { 343 $this->output[ $field['id'] ] = isset( $this->output[ $field['id'] ] ) ? array_merge( $field['output'], $this->output[ $field['id'] ] ) : $field['output']; 344 } 345 346 // Detect what field types are being used. 347 if ( ! isset( $this->parent->fields[ $field['type'] ][ $field['id'] ] ) ) { 348 $this->parent->fields[ $field['type'] ][ $field['id'] ] = 1; 349 } else { 350 $this->parent->fields[ $field['type'] ] = array( $field['id'] => 1 ); 351 } 352 353 if ( isset( $this->options_defaults[ $field['id'] ] ) ) { 354 $this->to_replace[ $field['id'] ] = $field; 355 } 356 } 357 358 if ( ! isset( $this->parent->options[ $field['id'] ] ) ) { 359 $this->parent->sections[ ( count( $this->parent->sections ) - 1 ) ]['fields'][] = $field; 360 } 361 362 if ( ! isset( $this->meta[ $this->post_id ][ $field['id'] ] ) ) { 363 $this->meta[ $this->post_id ][ $field['id'] ] = $this->options_defaults[ $field['id'] ]; 364 } 365 366 // Only override if it exists, and it's not the default. 367 // phpcs:ignore Squiz.PHP.CommentedOutCode 368 // if ( isset( $this->meta[ $this->post_id ][ $field['id'] ] ) && isset( $field['default'] ) && $this->meta[ $this->post_id ][ $field['id'] ] === $field['default'] ) { 369 // unset($this->meta[$this->post_id][$field['id']]); 370 // } . 371 } 372 } 373 } 374 } 375 } 376 } 377 378 if ( isset( $run_hooks ) && true === $run_hooks ) { 379 $this->parent_options = ''; 380 381 if ( ! empty( $this->to_replace ) ) { 382 foreach ( $this->to_replace as $id => $field ) { 383 384 // phpcs:ignore WordPress.NamingConventions.ValidHookName 385 add_filter( "redux/options/{$this->parent->args['opt_name']}/field/$id/register", array( $this, 'replace_field' ) ); 386 } 387 } 388 389 add_filter( "redux/options/{$this->parent->args['opt_name']}/options", array( $this, 'override_options' ) ); 390 add_filter( "redux/field/{$this->parent->args['opt_name']}/_can_output_css", array( $this, 'override_can_output_css' ) ); 391 add_filter( "redux/field/{$this->parent->args['opt_name']}/output_css", array( $this, 'output_css' ) ); 392 } 393 } 394 395 /** 396 * Replace Field. 397 * 398 * @param array $field Field array. 399 * 400 * @return mixed 401 */ 402 public function replace_field( array $field ) { 403 if ( isset( $this->to_replace[ $field['id'] ] ) ) { 404 $field = $this->to_replace[ $field['id'] ]; 405 } 406 407 return $field; 408 } 409 410 /** 411 * Override CSS output. 412 * 413 * @param array $field Field array. 414 * 415 * @return array 416 */ 417 public function override_can_output_css( array $field ): array { 418 if ( isset( $this->output[ $field['id'] ] ) ) { 419 $field['force_output'] = true; 420 } 421 422 return $field; 423 } 424 425 /** 426 * Output CSS. 427 * 428 * @param array $field Field array. 429 * 430 * @return array 431 */ 432 public function output_css( array $field ): array { 433 if ( isset( $this->output[ $field['id'] ] ) ) { 434 $field['output'] = $this->output[ $field['id'] ]; 435 } 436 437 return $field; 438 } 439 440 /** 441 * Make sure the defaults are the defaults 442 * 443 * @param array $options Options array. 444 * 445 * @return array 446 */ 447 public function override_options( array $options ): array { 448 $this->parent->_default_values(); 449 $this->parent_defaults = $this->parent->options_defaults; 450 451 $meta = $this->get_meta( $this->post_id ); 452 $data = wp_parse_args( $meta, $this->options_defaults ); 453 454 foreach ( $data as $key => $value ) { 455 if ( isset( $meta[ $key ] ) && '' !== $meta[ $key ] ) { 456 $data[ $key ] = $meta[ $key ]; 457 continue; 458 } 459 460 if ( isset( $options[ $key ] ) ) { 461 if ( isset( $options[ $key ] ) ) { 462 $data[ $key ] = $options[ $key ]; 463 } 464 } 465 } 466 467 $this->parent->options_defaults = wp_parse_args( $this->options_defaults, $this->parent->options_defaults ); 468 469 return wp_parse_args( $data, $options ); 470 } 471 472 /** 473 * Loop start. 474 * 475 * @param mixed $the_post WP_Post object. 476 * 477 * @return array|void 478 */ 479 public function loop_start( $the_post = array() ) { 480 if ( is_admin() ) { 481 return $the_post; 482 } 483 484 if ( isset( $the_post ) && is_array( $the_post ) ) { 485 global $post; 486 $the_post = $post; 487 } 488 489 if ( isset( $GLOBALS[ $this->parent->args['global_variable'] . '-loop' ] ) ) { 490 $GLOBALS[ $this->parent->args['global_variable'] ] = $GLOBALS[ $this->parent->args['global_variable'] . '-loop' ]; 491 unset( $GLOBALS[ $this->parent->args['global_variable'] . '-loop' ] ); 492 } 493 494 // Override these values if they differ from the admin panel defaults. ;) . 495 if ( in_array( $the_post->post_type, $this->post_types, true ) ) { 496 $meta = $this->get_meta( $the_post->ID ); 497 if ( empty( $meta ) ) { 498 return; 499 } 500 501 // Backup the args. 502 $GLOBALS[ $this->parent->args['global_variable'] . '-loop' ] = $GLOBALS[ $this->parent->args['global_variable'] ]; 503 $GLOBALS[ $this->parent->args['global_variable'] ] = wp_parse_args( $meta, $GLOBALS[ $this->parent->args['global_variable'] . '-loop' ] ); 504 } 505 } 506 507 /** 508 * Loop end. 509 */ 510 public function loop_end() { 511 if ( isset( $GLOBALS[ $this->parent->args['global_variable'] . '-loop' ] ) ) { 512 $GLOBALS[ $this->parent->args['global_variable'] ] = $GLOBALS[ $this->parent->args['global_variable'] . '-loop' ]; 513 514 unset( $GLOBALS[ $this->parent->args['global_variable'] . '-loop' ] ); 515 } 516 } 517 518 /** 519 * Enqueue fields. 520 */ 521 public function enqueue() { 522 global $pagenow; 523 524 $types = array(); 525 526 // Enqueue CSS. 527 foreach ( $this->boxes as $key => $box ) { 528 if ( empty( $box['sections'] ) ) { 529 continue; 530 } 531 532 if ( isset( $box['post_types'] ) ) { 533 $types = array_merge( $box['post_types'], $types ); 534 } 535 536 if ( isset( $box['post_types'] ) && ! empty( $box['post_types'] ) ) { 537 if ( ! is_array( $box['post_types'] ) ) { 538 $box['post_types'] = array( $box['post_types'] ); 539 $this->boxes[ $key ]['post_types'] = $box['post_types']; 540 } 541 } 542 } 543 544 if ( 'post-new.php' === $pagenow || 'post.php' === $pagenow ) { 545 global $post; 546 547 if ( in_array( $post->post_type, $types, true ) ) { 548 $this->parent->transients = get_transient( $this->parent->args['opt_name'] . '-transients-metaboxes' ); 549 $this->parent->transients_check = $this->parent->transients; 550 551 if ( isset( $this->parent->transients['notices'] ) ) { 552 $this->notices = $this->parent->transients['notices']; 553 $this->parent->transients['last_save_mode'] = 'metaboxes'; 554 } 555 556 delete_transient( $this->parent->args['opt_name'] . '-transients-metaboxes' ); 557 $this->parent->_enqueue(); 558 559 // phpcs:ignore WordPress.NamingConventions.ValidHookName 560 do_action( "redux/metaboxes/{$this->parent->args['opt_name']}/enqueue" ); 561 562 // phpcs:ignore WordPress.NamingConventions.ValidHookName 563 do_action( "redux/{$this->parent->args['opt_name']}/extensions/metaboxes/enqueue" ); 564 565 /** 566 * Redux metaboxes CSS 567 * filter 'redux/page/{opt_name}/enqueue/redux-extension-metaboxes-css' 568 * 569 * @param string bundled stylesheet src 570 */ 571 wp_enqueue_style( 572 'redux-extension-metaboxes-js', 573 apply_filters( "redux/metaboxes/{$this->parent->args['opt_name']}/enqueue/redux-extension-metaboxes-css", $this->extension_url . 'redux-extension-metaboxes.css' ), // phpcs:ignore: WordPress.NamingConventions.ValidHookName 574 array(), 575 self::$version 576 ); 577 578 /** 579 * Redux metaboxes JS 580 * filter 'redux/page/{opt_name}/enqueue/redux-extension-metaboxes-js 581 * 582 * @param string bundled javscript 583 */ 584 wp_enqueue_script( 585 'redux-extension-metaboxes-js', 586 apply_filters( "redux/metaboxes/{$this->parent->args['opt_name']}/enqueue/redux-extension-metaboxes-js", $this->extension_url . 'redux-extension-metaboxes' . Redux_Functions::isMin() . '.js' ), // phpcs:ignore: WordPress.NamingConventions.ValidHookName 587 array( 'jquery', 'redux-js' ), 588 self::$version, 589 true 590 ); 591 592 // Values used by the javascript. 593 wp_localize_script( 'redux-extension-metaboxes-js', 'reduxMetaboxes', $this->wp_links ); 594 } 595 } 596 } 597 598 /* Post URLs to IDs function, supports custom post types - borrowed and modified from url_to_postid() in wp-includes/rewrite.php */ 599 600 // Taken from http://betterwp.net/wordpress-tips/url_to_postid-for-custom-post-types/ 601 // Customized to work with non-rewrite URLs 602 // Modified by Dovy Paukstys (@dovy) of Redux Framework. 603 604 /** 605 * URL to PostID. 606 * 607 * @param string $url URL. 608 * 609 * @return int|mixed|void 610 */ 611 private function url_to_postid( string $url ) { 612 global $wp_rewrite, $pagenow; 613 614 if ( ! empty( $this->post_id ) ) { 615 return $this->post_id; 616 } 617 618 if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification 619 $post = (int) sanitize_text_field( wp_unslash( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification 620 621 if ( ! empty( $post ) && is_numeric( $post ) ) { 622 return $post; 623 } 624 } 625 626 if ( 'post-new.php' === $pagenow || 'wp-login.php' === $pagenow ) { 627 return; 628 } 629 630 if ( is_admin() && 'post.php' === $pagenow && isset( $_POST['post_ID'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification 631 $post_id = sanitize_text_field( wp_unslash( $_POST['post_ID'] ) ); // phpcs:ignore WordPress.Security.NonceVerification 632 633 if ( ! empty( $post_id ) && is_numeric( $post_id ) ) { 634 return $post_id; 635 } 636 } 637 638 $post_id = url_to_postid( $url ); 639 640 if ( isset( $post_id ) && '' !== (string) $post_id && 0 !== $post_id ) { 641 return $post_id; 642 } 643 644 // First, check to see if there is a 'p=N' or 'page_id=N' to match against. 645 if ( preg_match( '#[?&](p|page_id|attachment_id)=(\d+)#', $url, $values ) ) { 646 $id = absint( $values[2] ); 647 if ( $id ) { 648 return $id; 649 } 650 } 651 652 // Check to see if we are using rewrite rules. 653 if ( isset( $wp_rewrite ) ) { 654 $rewrite = $wp_rewrite->wp_rewrite_rules(); 655 } 656 657 // Not using rewrite rules, and 'p=N' and 'page_id=N' methods failed, so we're out of options. 658 if ( empty( $rewrite ) ) { 659 if ( isset( $_GET ) && ! empty( $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification 660 661 /************************************************************************ 662 * ADDED: Trys checks URL for ?posttype=postname 663 ************************************************************************ */ 664 665 // Assign $url to $temp_url just incase. :) . 666 $temp_url = $url; 667 668 // Get rid of the #anchor. 669 $url_split = explode( '#', $temp_url ); 670 $temp_url = $url_split[0]; 671 672 // Get rid of URL ?query=string. 673 $url_query = explode( '&', $temp_url ); 674 $temp_url = $url_query[0]; 675 676 // Get rid of ? mark. 677 $url_query = explode( '?', $temp_url ); 678 679 if ( isset( $url_query[1] ) && ! empty( $url_query[1] ) && strpos( $url_query[1], '=' ) ) { 680 $url_query = explode( '=', $url_query[1] ); 681 682 if ( isset( $url_query[0] ) && isset( $url_query[1] ) ) { 683 $args = array( 684 'name' => $url_query[1], 685 'post_type' => $url_query[0], 686 'showposts' => 1, 687 ); 688 689 if ( get_posts( $args ) === $post ) { 690 return $post[0]->ID; 691 } 692 } 693 } 694 695 foreach ( $GLOBALS['wp_post_types'] as $key => $value ) { 696 if ( isset( $_GET[ $key ] ) && ! empty( $_GET[ $key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification 697 $args = array( 698 'name' => sanitize_text_field( wp_unslash( $_GET[ $key ] ) ), // phpcs:ignore WordPress.Security.NonceVerification 699 'post_type' => $key, 700 'showposts' => 1, 701 ); 702 703 if ( get_posts( $args ) === $post ) { 704 return $post[0]->ID; 705 } 706 } 707 } 708 } 709 } 710 711 // Get rid of the #anchor. 712 $url_split = explode( '#', $url ); 713 $url = $url_split[0]; 714 715 // Get rid of URL ?query=string. 716 $url_query = explode( '?', $url ); 717 $url = $url_query[0]; 718 719 // Set the correct URL scheme. 720 $scheme = wp_parse_url( home_url(), PHP_URL_SCHEME ); 721 $url = set_url_scheme( $url, $scheme ); 722 723 // Add 'www.' if it is absent and should be there. 724 if ( false !== strpos( home_url(), '://www.' ) && false === strpos( $url, '://www.' ) ) { 725 $url = str_replace( '://', '://www.', $url ); 726 } 727 728 // Strip 'www.' if it is present and shouldn't be. 729 if ( false === strpos( home_url(), '://www.' ) ) { 730 $url = str_replace( '://www.', '://', $url ); 731 } 732 733 // Strip 'index.php/' if we're not using path info permalinks. 734 if ( isset( $wp_rewrite ) && ! $wp_rewrite->using_index_permalinks() ) { 735 $url = str_replace( 'index.php/', '', $url ); 736 } 737 738 if ( false !== strpos( $url, home_url() ) ) { 739 // Chop off http://domain.com. 740 $url = str_replace( home_url(), '', $url ); 741 } else { 742 // Chop off /path/to/blog. 743 $home_path = wp_parse_url( home_url() ); 744 $home_path = $home_path['path'] ?? ''; 745 $url = str_replace( $home_path, '', $url ); 746 } 747 748 // Trim leading and lagging slashes. 749 $url = trim( $url, '/' ); 750 751 $request = $url; 752 753 if ( empty( $request ) && ( ! isset( $_GET ) || empty( $_GET ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification 754 return get_option( 'page_on_front' ); 755 } 756 757 // Look for matches. 758 $request_match = $request; 759 760 foreach ( (array) $rewrite as $match => $query ) { 761 // If the requesting file is the anchor of the match, prepend it 762 // to the path info. 763 if ( ! empty( $url ) && ( $url !== $request ) && ( strpos( $match, $url ) === 0 ) ) { 764 $request_match = $url . '/' . $request; 765 } 766 767 if ( preg_match( "!^$match!", $request_match, $matches ) ) { 768 global $wp; 769 770 // Got a match. 771 // Trim the query of everything up to the '?'. 772 $query = preg_replace( '!^.+\?!', '', $query ); 773 774 // Substitute the substring matches into the query. 775 $query = addslashes( WP_MatchesMapRegex::apply( $query, $matches ) ); 776 777 // Filter out non-public query vars. 778 parse_str( $query, $query_vars ); 779 780 $query = array(); 781 782 foreach ( (array) $query_vars as $key => $value ) { 783 if ( in_array( $key, $wp->public_query_vars, true ) ) { 784 $query[ $key ] = $value; 785 } 786 } 787 788 /************************************************************************ 789 * ADDED: $GLOBALS['wp_post_types'] doesn't seem to have custom postypes 790 * Trying below to find posttypes in $rewrite rules 791 ************************************************************************ */ 792 793 // PostType Array. 794 $custom_post_type = false; 795 $post_types = array(); 796 797 foreach ( $rewrite as $key => $value ) { 798 if ( preg_match( '/post_type=([^&]+)/i', $value, $matched ) ) { 799 if ( isset( $matched[1] ) && ! in_array( $matched[1], $post_types, true ) ) { 800 $post_types[] = $matched[1]; 801 } 802 } 803 } 804 805 foreach ( (array) $query_vars as $key => $value ) { 806 if ( in_array( $key, $post_types, true ) ) { 807 $custom_post_type = true; 808 809 $query['post_type'] = $key; 810 $query['postname'] = $value; 811 } 812 } 813 814 /************************************************************************ 815 * END ADD 816 ************************************************************************ */ 817 818 // Taken from class-wp.php. 819 foreach ( $GLOBALS['wp_post_types'] as $post_type => $t ) { 820 if ( isset( $t->query_var ) ) { 821 $post_type_query_vars[ $t->query_var ] = $post_type; 822 } 823 } 824 825 foreach ( $wp->public_query_vars as $wpvar ) { 826 if ( isset( $wp->extra_query_vars[ $wpvar ] ) ) { 827 $query[ $wpvar ] = $wp->extra_query_vars[ $wpvar ]; 828 } elseif ( isset( $_POST[ $wpvar ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification 829 $query[ $wpvar ] = sanitize_text_field( wp_unslash( $_POST[ $wpvar ] ) ); // phpcs:ignore WordPress.Security.NonceVerification 830 } elseif ( isset( $_GET[ $wpvar ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification 831 $query[ $wpvar ] = sanitize_text_field( wp_unslash( $_GET[ $wpvar ] ) ); // phpcs:ignore WordPress.Security.NonceVerification 832 } elseif ( isset( $query_vars[ $wpvar ] ) ) { 833 $query[ $wpvar ] = $query_vars[ $wpvar ]; 834 } 835 836 if ( ! empty( $query[ $wpvar ] ) ) { 837 if ( ! is_array( $query[ $wpvar ] ) ) { 838 $query[ $wpvar ] = (string) $query[ $wpvar ]; 839 } else { 840 foreach ( $query[ $wpvar ] as $vkey => $v ) { 841 if ( ! is_object( $v ) ) { 842 $query[ $wpvar ][ $vkey ] = (string) $v; 843 } 844 } 845 } 846 847 if ( isset( $post_type_query_vars[ $wpvar ] ) ) { 848 $query['post_type'] = $post_type_query_vars[ $wpvar ]; 849 $query['name'] = $query[ $wpvar ]; 850 } 851 } 852 } 853 854 // Do the query. 855 if ( isset( $query['pagename'] ) && ! empty( $query['pagename'] ) ) { 856 $args = array( 857 'name' => $query['pagename'], 858 'post_type' => 'page', 859 'showposts' => 1, 860 ); 861 862 if ( get_posts( $args ) === $post ) { 863 return $post[0]->ID; 864 } 865 } 866 867 if ( ( ! isset( $query['page'] ) || empty( $query['page'] ) ) && ( ! isset( $query['pagename'] ) || empty( $query['pagename'] ) ) ) { 868 return 0; 869 } 870 871 $query = new WP_Query( $query ); 872 873 if ( ! empty( $query->posts ) && $query->is_singular ) { 874 return $query->post->ID; 875 } else { 876 877 // WooCommerce override. 878 if ( isset( $query->query['post_type'] ) && 'product' === $query->query['post_type'] && class_exists( 'WooCommerce' ) ) { 879 return get_option( 'woocommerce_shop_page_id' ); 880 } 881 882 return 0; 883 } 884 } 885 } 886 887 return 0; 888 } 889 890 /** 891 * Get default values. 892 */ 893 public function default_values() { 894 if ( ! empty( $this->boxes ) && empty( $this->options_defaults ) ) { 895 foreach ( $this->boxes as $key => $box ) { 896 if ( empty( $box['sections'] ) ) { 897 continue; 898 } 899 900 // fill the cache. 901 foreach ( $box['sections'] as $sk => $section ) { 902 if ( ! isset( $section['id'] ) ) { 903 if ( ! is_numeric( $sk ) || ! isset( $section['title'] ) ) { 904 $section['id'] = $sk; 905 } else { 906 $section['id'] = sanitize_text_field( $section['title'], $sk ); 907 } 908 $this->boxes[ $key ]['sections'][ $sk ] = $section; 909 } 910 if ( isset( $section['fields'] ) ) { 911 foreach ( $section['fields'] as $k => $field ) { 912 913 if ( empty( $field['id'] ) && empty( $field['type'] ) ) { 914 continue; 915 } 916 917 if ( in_array( $field['type'], array( 'ace_editor' ), true ) && isset( $field['options'] ) ) { 918 $this->boxes[ $key ]['sections'][ $sk ]['fields'][ $k ]['args'] = $field['options']; 919 unset( $this->boxes[ $key ]['sections'][ $sk ]['fields'][ $k ]['options'] ); 920 } 921 922 if ( 'section' === $field['type'] && isset( $field['indent'] ) && ( true === $field['indent'] || 'true' === $field['indent'] ) ) { 923 $field['class'] = $field['class'] ?? ''; 924 $field['class'] .= 'redux-section-indent-start'; 925 926 $this->boxes[ $key ]['sections'][ $sk ]['fields'][ $k ] = $field; 927 } 928 929 $this->parent->field_default_values( $field ); 930 } 931 } 932 } 933 } 934 } 935 936 if ( empty( $this->meta[ $this->post_id ] ) ) { 937 $this->meta[ $this->post_id ] = $this->get_meta( $this->post_id ); 938 } 939 } 940 941 /** 942 * Add Meta Boxes 943 */ 944 public function add() { 945 if ( empty( $this->boxes ) || ! is_array( $this->boxes ) ) { 946 return; 947 } 948 949 foreach ( $this->boxes as $key => $box ) { 950 if ( empty( $box['sections'] ) ) { 951 continue; 952 } 953 954 // Save users from themselves. 955 if ( isset( $box['position'] ) && ! in_array( strtolower( $box['position'] ), array( 'normal', 'advanced', 'side' ), true ) ) { 956 unset( $box['position'] ); 957 } 958 959 if ( isset( $box['priority'] ) && ! in_array( strtolower( $box['priority'] ), array( 'high', 'core', 'default', 'low' ), true ) ) { 960 unset( $box['priority'] ); 961 } 962 963 $defaults = array( 964 'id' => $key . '-' . $this->parent->args['opt_name'], 965 'post_types' => array( 'page', 'post' ), 966 'position' => 'normal', 967 'priority' => 'high', 968 ); 969 970 $box = wp_parse_args( $box, $defaults ); 971 if ( isset( $box['post_types'] ) && ! empty( $box['post_types'] ) ) { 972 foreach ( $box['post_types'] as $posttype ) { 973 if ( isset( $box['title'] ) ) { 974 $title = $box['title']; 975 } else { 976 if ( isset( $box['sections'] ) && 1 === count( $box['sections'] ) && isset( $box['sections'][0]['fields'] ) && 1 === count( $box['sections'][0]['fields'] ) && isset( $box['sections'][0]['fields'][0]['title'] ) ) { 977 978 // If only one field in this box. 979 $title = $box['sections'][0]['fields'][0]['title']; 980 } else { 981 $title = ucfirst( $posttype ) . ' ' . __( 'Options', 'redux-framework' ); 982 } 983 } 984 985 $args = array( 986 'position' => $box['position'], 987 'sections' => $box['sections'], 988 'key' => $key, 989 ); 990 991 // Override the parent args on a metabox level. 992 if ( ! isset( $this->orig_args ) || empty( $this->orig_args ) ) { 993 $this->orig_args = $this->parent->args; 994 } 995 996 if ( isset( $box['args'] ) ) { 997 $this->parent->args = wp_parse_args( $box['args'], $this->orig_args ); 998 } elseif ( $this->parent->args !== $this->orig_args ) { 999 $this->parent->args = $this->orig_args; 1000 } 1001 1002 add_filter( 'postbox_classes_' . $posttype . '_redux-' . $this->parent->args['opt_name'] . '-metabox-' . $box['id'], array( $this, 'add_box_classes' ) ); 1003 1004 // phpcs:ignore WordPress.NamingConventions.ValidHookName 1005 do_action( 'redux/' . $this->parent->args['opt_name'] . '/extensions/metabox/add', $this, $box, $posttype ); 1006 1007 if ( isset( $box['post_format'] ) ) { 1008 add_filter( 'postbox_classes_' . $posttype . '_redux-' . $this->parent->args['opt_name'] . '-metabox-' . $box['id'], array( $this, 'add_box_hide_class' ) ); 1009 } 1010 1011 // phpcs:ignore Generic.Strings.UnnecessaryStringConcat 1012 call_user_func( 'add' . '_meta' . '_box', 'redux-' . $this->parent->args['opt_name'] . '-metabox-' . $box['id'], $title, array( $this, 'generate_boxes' ), $posttype, $box['position'], $box['priority'], $args ); 1013 } 1014 } 1015 } 1016 } 1017 1018 /** 1019 * Add hidden class to metabox DIV. 1020 * 1021 * @param array $classes Class array. 1022 * 1023 * @return array 1024 */ 1025 public function add_box_hide_class( array $classes ): array { 1026 $classes[] = 'hide'; 1027 1028 return $classes; 1029 } 1030 1031 /** 1032 * Field Defaults. 1033 * 1034 * @param mixed $field_id ID. 1035 * 1036 * @return mixed|string 1037 */ 1038 private function field_default( $field_id ) { 1039 if ( ! isset( $this->parent->options_defaults ) ) { 1040 $this->parent->options_defaults = $this->parent->_default_values(); 1041 } 1042 1043 if ( ! isset( $this->parent->options ) || empty( $this->parent->options ) ) { 1044 $this->parent->get_options(); 1045 } 1046 1047 $this->options = $this->parent->options; 1048 1049 if ( isset( $this->parent->options[ $field_id['id'] ] ) && isset( $this->parent->options_defaults[ $field_id['id'] ] ) && $this->parent->options[ $field_id['id'] ] !== $this->parent->options_defaults[ $field_id['id'] ] ) { 1050 return $this->parent->options[ $field_id['id'] ]; 1051 } else { 1052 if ( empty( $this->options_defaults ) ) { 1053 $this->default_values(); // fill cache. 1054 } 1055 1056 $data = ''; 1057 if ( ! empty( $this->options_defaults ) ) { 1058 $data = $this->options_defaults[ $field_id['id'] ] ?? ''; 1059 } 1060 1061 if ( empty( $data ) && isset( $this->parent->options_defaults[ $field_id['id'] ] ) ) { 1062 $data = $this->parent->options_defaults[ $field_id['id'] ] ?? ''; 1063 } 1064 1065 return $data; 1066 } 1067 1068 } 1069 1070 /** 1071 * Function to get and cache the post meta. 1072 * 1073 * @param mixed $id ID. 1074 * 1075 * @return array 1076 */ 1077 private function get_meta( $id ): array { 1078 if ( ! isset( $this->meta[ $id ] ) ) { 1079 $this->meta[ $id ] = array(); 1080 $o_data = get_post_meta( $id ); 1081 1082 // phpcs:ignore WordPress.NamingConventions.ValidHookName 1083 $o_data = apply_filters( "redux/metaboxes/{$this->parent->args['opt_name']}/get_meta", $o_data ); 1084 1085 if ( ! empty( $o_data ) ) { 1086 foreach ( $o_data as $key => $value ) { 1087 if ( 1 === count( $value ) ) { 1088 $this->meta[ $id ][ $key ] = maybe_unserialize( $value[0] ); 1089 } else { 1090 $new_value = array_map( 'maybe_unserialize', $value ); 1091 1092 if ( is_array( $new_value ) ) { 1093 $this->meta[ $id ][ $key ] = $new_value[0]; 1094 } else { 1095 $this->meta[ $id ][ $key ] = $new_value; 1096 } 1097 } 1098 } 1099 } 1100 1101 if ( isset( $this->meta[ $id ][ $this->parent->args['opt_name'] ] ) ) { 1102 $data = maybe_unserialize( $this->meta[ $id ][ $this->parent->args['opt_name'] ] ); 1103 1104 foreach ( $data as $key => $value ) { 1105 $this->meta[ $id ][ $key ] = $value; 1106 update_post_meta( $id, $key, $value ); 1107 } 1108 1109 unset( $this->meta[ $id ][ $this->parent->args['opt_name'] ] ); 1110 1111 delete_post_meta( $id, $this->parent->args['opt_name'] ); 1112 } 1113 } 1114 1115 return $this->meta[ $id ]; 1116 } 1117 1118 /** 1119 * Get values. 1120 * 1121 * @param mixed $the_post Post oject/id. 1122 * @param string $meta_key Meta key. 1123 * @param mixed $def_val Def value. 1124 * 1125 * @return array|mixed|string 1126 */ 1127 public function get_values( $the_post, string $meta_key = '', $def_val = '' ) { 1128 1129 // Override these values if they differ from the admin panel defaults. ;) . 1130 if ( isset( $the_post->post_type ) && in_array( $the_post->post_type, $this->post_types, true ) ) { 1131 if ( isset( $this->post_type_values[ $the_post->post_type ] ) ) { 1132 $meta = $this->post_type_fields[ $the_post->post_type ]; 1133 } else { 1134 $defaults = array(); 1135 if ( ! empty( $this->post_type_fields[ $the_post->post_type ] ) ) { 1136 foreach ( $this->post_type_fields[ $the_post->post_type ] as $key => $null ) { 1137 if ( isset( $this->options_defaults[ $key ] ) ) { 1138 $defaults[ $key ] = $this->options_defaults[ $key ]; 1139 } 1140 } 1141 } 1142 1143 $meta = wp_parse_args( $this->get_meta( $the_post->ID ), $defaults ); 1144 1145 $this->post_type_fields[ $the_post->post_type ] = $meta; 1146 } 1147 1148 if ( ! empty( $meta_key ) ) { 1149 if ( ! isset( $meta[ $meta_key ] ) ) { 1150 $meta[ $meta_key ] = $def_val; 1151 } 1152 1153 return $meta[ $meta_key ]; 1154 } else { 1155 return $meta; 1156 } 1157 } 1158 1159 return $def_val; 1160 } 1161 1162 /** 1163 * Generate Boxes. 1164 * 1165 * @param mixed $post ID. 1166 * @param array $metabox Metabox array. 1167 */ 1168 public function generate_boxes( $post, array $metabox ) { 1169 global $wpdb; 1170 1171 if ( isset( $metabox['args']['permissions'] ) && ! empty( $metabox['args']['permissions'] ) && ! Redux_Helpers::current_user_can( $metabox['args']['permissions'] ) ) { 1172 return; 1173 } 1174 1175 $sections = $metabox['args']['sections']; 1176 1177 wp_nonce_field( 'redux_metaboxes_meta_nonce', 'redux_metaboxes_meta_nonce' ); 1178 1179 wp_dequeue_script( 'json-view-js' ); 1180 1181 $sidebar = true; 1182 1183 if ( 'side' === $metabox['args']['position'] || 1 === count( $sections ) || ( isset( $metabox['args']['sidebar'] ) && false === $metabox['args']['sidebar'] ) ) { 1184 $sidebar = false; // Show the section dividers or not. 1185 } 1186 1187 ?> 1188 <input 1189 type="hidden" 1190 id="currentSection" 1191 name="<?php echo esc_attr( $this->parent->args['opt_name'] ); ?>[redux-metabox-section]" value=""/> 1192 <div 1193 data-index="<?php echo esc_attr( $metabox['args']['key'] ); ?>" 1194 data-opt-name="<?php echo esc_attr( $this->parent->args['opt_name'] ); ?>" 1195 class="redux-container<?php echo esc_attr( ( $sidebar ) ? ' redux-has-sections' : ' redux-no-sections' ); ?> redux-box-<?php echo esc_attr( $metabox['args']['position'] ); ?>"> 1196 <div class="redux-notices"> 1197 <?php if ( 'side' !== $metabox['args']['position'] || ( isset( $metabox['args']['sidebar'] ) && false !== $metabox['args']['sidebar'] ) ) { ?> 1198 <div class="saved_notice admin-notice notice-blue" style="display:none;"> 1199 <?php // phpcs:ignore WordPress.NamingConventions.ValidHookName ?> 1200 <strong><?php echo esc_html( apply_filters( "redux-imported-text-{$this->parent->args['opt_name']}", esc_html__( 'Settings Imported!', 'redux-framework' ) ) ); ?></strong> 1201 </div> 1202 <div class="redux-save-warn notice-yellow"> 1203 <?php // phpcs:ignore WordPress.NamingConventions.ValidHookName ?> 1204 <strong><?php echo esc_html( apply_filters( "redux-changed-text-{$this->parent->args['opt_name']}", esc_html__( 'Settings have changed, you should save them!', 'redux-framework' ) ) ); ?></strong> 1205 </div> 1206 <?php } ?> 1207 <div class="redux-field-errors notice-red"> 1208 <strong> <span></span> <?php echo esc_html__( 'error(s) were found!', 'redux-framework' ); ?> 1209 </strong> 1210 </div> 1211 <div class="redux-field-warnings notice-yellow"> 1212 <strong> <span></span> <?php echo esc_html__( 'warning(s) were found!', 'redux-framework' ); ?> 1213 </strong> 1214 </div> 1215 </div> 1216 <?php 1217 echo '<a href="javascript:void(0);" class="expand_options hide" style="display:none;">' . esc_html__( 'Expand', 'redux-framework' ) . '</a>'; 1218 if ( $sidebar ) { 1219 ?> 1220 <div class="redux-sidebar"> 1221 <ul class="redux-group-menu"> 1222 <?php 1223 1224 foreach ( $sections as $s_key => $section ) { 1225 if ( isset( $section['permissions'] ) && ! empty( $section['permissions'] ) && ! Redux_Helpers::current_user_can( $section['permissions'] ) ) { 1226 continue; 1227 } 1228 1229 echo $this->parent->section_menu( $s_key, $section, '_box_' . $metabox['id'], $sections ); // phpcs:ignore WordPress.Security.EscapeOutput 1230 } 1231 ?> 1232 </ul> 1233 </div> 1234 <?php } ?> 1235 1236 <div class="redux-main"> 1237 <?php 1238 $update_localize = false; 1239 1240 foreach ( $sections as $s_key => $section ) { 1241 if ( isset( $section['permissions'] ) && ! empty( $section['permissions'] ) && ! Redux_Helpers::current_user_can( $section['permissions'] ) ) { 1242 continue; 1243 } 1244 if ( isset( $section['fields'] ) && ! empty( $section['fields'] ) ) { 1245 if ( isset( $section['args'] ) ) { 1246 $this->parent->args = wp_parse_args( $section['args'], $this->orig_args ); 1247 } elseif ( $this->parent->args !== $this->orig_args ) { 1248 $this->parent->args = $this->orig_args; 1249 } 1250 1251 $hide = $sidebar ? '' : ' display-group'; 1252 $section['class'] = isset( $section['class'] ) ? ' ' . $section['class'] : ''; 1253 echo '<div id="' . esc_attr( $s_key ) . '_box_' . esc_attr( $metabox['id'] ) . '_section_group" class="redux-group-tab' . esc_attr( $section['class'] ) . ' redux_metabox_panel' . esc_attr( $hide ) . '">'; 1254 1255 if ( isset( $section['title'] ) && ! empty( $section['title'] ) ) { 1256 echo '<h3 class="redux-section-title">' . wp_kses_post( $section['title'] ) . '</h3>'; 1257 } 1258 1259 if ( isset( $section['desc'] ) && ! empty( $section['desc'] ) ) { 1260 echo '<div class="redux-section-desc">' . wp_kses_post( $section['desc'] ) . '</div>'; 1261 } 1262 1263 echo '<table class="form-table"><tbody>'; 1264 foreach ( $section['fields'] as $f_key => $field ) { 1265 if ( isset( $field['permissions'] ) && ! empty( $field['permissions'] ) && ! Redux_Helpers::current_user_can( $field['permissions'] ) ) { 1266 continue; 1267 } 1268 $field['name'] = $this->parent->args['opt_name'] . '[' . $field['id'] . ']'; 1269 1270 $is_hidden = false; 1271 $ex_style = ''; 1272 if ( isset( $field['hidden'] ) && $field['hidden'] ) { 1273 $is_hidden = true; 1274 $ex_style = ' style="border-bottom: none;"'; 1275 } 1276 1277 echo '<tr valign="top"' . $ex_style . '>'; // phpcs:ignore WordPress.Security.EscapeOutput 1278 1279 $th = $this->parent->get_header_html( $field ); 1280 1281 if ( $is_hidden ) { 1282 $str_pos = strpos( $th, 'redux_field_th' ); 1283 1284 if ( $str_pos > - 1 ) { 1285 $th = str_replace( 'redux_field_th', 'redux_field_th hide', $th ); 1286 } 1287 } 1288 1289 if ( $sidebar ) { 1290 if ( ! ( isset( $metabox['args']['sections'] ) && 1 === count( $metabox['args']['sections'] ) && isset( $metabox['args']['sections'][0]['fields'] ) && 1 === count( $metabox['args']['sections'][0]['fields'] ) ) && isset( $field['title'] ) ) { 1291 echo '<th scope="row">'; 1292 if ( ! empty( $th ) ) { 1293 echo $th; // phpcs:ignore WordPress.Security.EscapeOutput 1294 } 1295 echo '</th>'; 1296 echo '<td>'; 1297 } 1298 } else { 1299 echo '<td>' . $th . ''; // phpcs:ignore WordPress.Security.EscapeOutput 1300 } 1301 1302 if ( 'section' === $field['type'] && ( 'true' === $field['indent'] || true === $field['indent'] ) ) { 1303 $field['class'] = $field['class'] ?? ''; 1304 $field['class'] .= 'redux-section-indent-start'; 1305 } 1306 1307 if ( ! isset( $this->meta[ $this->post_id ][ $field['id'] ] ) ) { 1308 $this->meta[ $this->post_id ][ $field['id'] ] = ''; 1309 } 1310 1311 $this->parent->render_class->field_input( $field, $this->meta[ $this->post_id ][ $field['id'] ], true ); 1312 echo '</td></tr>'; 1313 } 1314 echo '</tbody></table>'; 1315 echo '</div>'; 1316 } 1317 } 1318 ?> 1319 </div> 1320 <div class="clear"></div> 1321 </div> 1322 <?php 1323 } 1324 1325 /** 1326 * Save meta boxes 1327 * Runs when a post is saved and does an action which the write panel save scripts can hook into. 1328 * 1329 * @access public 1330 * 1331 * @param mixed $post_id Post ID. 1332 * @param mixed $post Post. 1333 * 1334 * @return mixed 1335 */ 1336 public function meta_boxes_save( $post_id, $post ) { 1337 if ( isset( $_POST['vc_inline'] ) && sanitize_text_field( wp_unslash( $_POST['vc_inline'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification 1338 return $post_id; 1339 } 1340 1341 if ( isset( $_POST['post_ID'] ) && strval( $post_id ) !== $_POST['post_ID'] ) { // phpcs:ignore WordPress.Security.NonceVerification 1342 return $post_id; 1343 } 1344 1345 // Check if our nonce is set. 1346 if ( ! isset( $_POST['redux_metaboxes_meta_nonce'] ) || ! isset( $_POST[ $this->parent->args['opt_name'] ] ) ) { 1347 return $post_id; 1348 } 1349 1350 $meta = $this->get_meta( $post_id ); 1351 1352 // Verify that the nonce is valid. 1353 // Validate fields (if needed). 1354 if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['redux_metaboxes_meta_nonce'] ) ), 'redux_metaboxes_meta_nonce' ) ) { 1355 return $post_id; 1356 } 1357 1358 // If this is an autosave, our form has not been submitted, so we don't want to do anything. 1359 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { 1360 return $post_id; 1361 } 1362 1363 // Check the user's permissions, even allowing custom capabilities. 1364 $obj = get_post_type_object( $post->post_type ); 1365 if ( ! current_user_can( $obj->cap->edit_post, $post_id ) ) { 1366 return $post_id; 1367 } 1368 1369 // Import. 1370 if ( isset( $_POST[ $this->parent->args['opt_name'] ]['import_code'] ) && ! empty( $_POST[ $this->parent->args['opt_name'] ]['import_code'] ) ) { 1371 $import = json_decode( sanitize_text_field( wp_unslash( $_POST[ $this->parent->args['opt_name'] ]['import_code'] ) ), true ); 1372 unset( $_POST[ $this->parent->args['opt_name'] ]['import_code'] ); 1373 1374 foreach ( Redux_Helpers::sanitize_array( wp_unslash( $_POST[ $this->parent->args['opt_name'] ] ) ) as $key => $value ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput 1375 if ( ! isset( $import[ $key ] ) ) { 1376 $import[ $key ] = $value; 1377 } 1378 } 1379 1380 $_POST[ $this->parent->args['opt_name'] ] = $import; 1381 } 1382 1383 $to_save = array(); 1384 $to_compare = array(); 1385 $to_delete = array(); 1386 $dont_save = true; 1387 1388 if ( isset( $this->parent->args['metaboxes_save_defaults'] ) && $this->parent->args['metaboxes_save_defaults'] ) { 1389 $dont_save = false; 1390 } 1391 1392 foreach ( Redux_Helpers::sanitize_array( wp_unslash( $_POST[ $this->parent->args['opt_name'] ] ) ) as $key => $value ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput 1393 1394 // Have to remove the escaping for array comparison. 1395 if ( is_array( $value ) ) { 1396 foreach ( $value as $k => $v ) { 1397 if ( ! is_array( $v ) ) { 1398 $value[ $k ] = wp_unslash( $v ); 1399 } 1400 } 1401 } 1402 1403 $save = true; 1404 1405 // parent_options. 1406 if ( ! $dont_save && isset( $this->options_defaults[ $key ] ) && $value === $this->options_defaults[ $key ] ) { 1407 $save = false; 1408 } 1409 1410 if ( $save && isset( $this->parent_options[ $key ] ) && $this->parent_options[ $key ] !== $value ) { 1411 $save = false; 1412 } 1413 1414 if ( $save ) { 1415 $to_save[ $key ] = $value; 1416 $to_compare[ $key ] = $this->parent->options[ $key ] ?? ''; 1417 } else { 1418 $to_delete[ $key ] = $value; 1419 } 1420 } 1421 1422 // phpcs:ignore WordPress.NamingConventions.ValidHookName 1423 $to_save = apply_filters( 'redux/metaboxes/save/before_validate', $to_save, $to_compare, $this->sections ); 1424 1425 $validate = $this->parent->_validate_values( $to_save, $to_compare, $this->sections ); 1426 1427 // Validate fields (if needed). 1428 foreach ( $to_save as $key => $value ) { 1429 if ( isset( $validate[ $key ] ) && $value !== $validate[ $key ] ) { 1430 if ( isset( $this->parent->options[ $key ] ) && $validate[ $key ] === $this->parent->options[ $key ] ) { 1431 unset( $to_save[ $key ] ); 1432 } else { 1433 $to_save[ $key ] = $validate[ $key ]; 1434 } 1435 } 1436 } 1437 1438 if ( ! empty( $this->parent->errors ) || ! empty( $this->parent->warnings ) ) { 1439 $this->parent->transients['notices'] = ( isset( $this->parent->transients['notices'] ) && is_array( $this->parent->transients['notices'] ) ) ? $this->parent->transients['notices'] : array(); 1440 1441 if ( ! isset( $this->parent->transients['notices']['errors'] ) || $this->parent->transients['notices']['errors'] !== $this->parent->errors ) { 1442 $this->parent->transients['notices']['errors'] = $this->parent->errors; 1443 $update_transients = true; 1444 } 1445 1446 if ( ! isset( $this->parent->transients['notices']['warnings'] ) || $this->parent->transients['notices']['warnings'] !== $this->parent->warnings ) { 1447 $this->parent->transients['notices']['warnings'] = $this->parent->warnings; 1448 $update_transients = true; 1449 } 1450 1451 if ( isset( $update_transients ) ) { 1452 $this->parent->transients['notices']['override'] = 1; 1453 set_transient( $this->parent->args['opt_name'] . '-transients-metaboxes', $this->parent->transients ); 1454 } 1455 } 1456 1457 if ( isset( $_POST['post_type'] ) ) { 1458 $check = $this->post_type_fields[ sanitize_text_field( wp_unslash( $_POST['post_type'] ) ) ]; 1459 } 1460 1461 // phpcs:ignore WordPress.NamingConventions.ValidHookName 1462 $to_save = apply_filters( 'redux/metaboxes/save', $to_save, $to_compare, $this->sections ); 1463 1464 foreach ( $to_save as $key => $value ) { 1465 $prev_value = $this->meta[ $post_id ][ $key ] ?? ''; 1466 1467 if ( isset( $check[ $key ] ) ) { 1468 unset( $check[ $key ] ); 1469 } 1470 1471 update_post_meta( $post_id, $key, $value, $prev_value ); 1472 } 1473 1474 foreach ( $to_delete as $key => $value ) { 1475 if ( isset( $check[ $key ] ) ) { 1476 unset( $check[ $key ] ); 1477 } 1478 1479 $prev_value = $this->meta[ $post_id ][ $key ] ?? ''; 1480 delete_post_meta( $post_id, $key, $prev_value ); 1481 } 1482 1483 foreach ( $check as $key => $value ) { 1484 delete_post_meta( $post_id, $key ); 1485 } 1486 } 1487 1488 /** 1489 * Some functions, like the term recount, require the visibility to be set prior. Lets save that here. 1490 * 1491 * @access public 1492 * 1493 * @param mixed $post_id Post ID. 1494 * 1495 * @return void 1496 */ 1497 public function pre_post_update( $post_id ) { 1498 if ( isset( $_POST['_visibility'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification 1499 update_post_meta( $post_id, '_visibility', sanitize_text_field( wp_unslash( $_POST['_visibility'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification 1500 } 1501 1502 if ( isset( $_POST['_stock_status'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification 1503 update_post_meta( $post_id, '_stock_status', sanitize_text_field( wp_unslash( $_POST['_stock_status'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification 1504 } 1505 } 1506 1507 /** 1508 * Show any stored error messages. 1509 * 1510 * @access public 1511 * @return void 1512 */ 1513 public function meta_boxes_show_errors() { 1514 if ( isset( $this->notices['errors'] ) && ! empty( $this->notices['errors'] ) ) { 1515 echo '<div id="redux_metaboxes_errors" class="error fade">'; 1516 echo '<p><strong><span></span> ' . count( $this->notices['errors'] ) . ' ' . esc_html__( 'error(s) were found!', 'redux-framework' ) . '</strong></p>'; 1517 echo '</div>'; 1518 } 1519 1520 if ( isset( $this->notices['warnings'] ) && ! empty( $this->notices['warnings'] ) ) { 1521 echo '<div id="redux_metaboxes_warnings" class="error fade" style="border-left-color: #E8E20C;">'; 1522 echo '<p><strong><span></span> ' . count( $this->notices['warnings'] ) . ' ' . esc_html__( 'warnings(s) were found!', 'redux-framework' ) . '</strong></p>'; 1523 echo '</div>'; 1524 } 1525 } 1526 } 1527 } 1528 1529 if ( ! function_exists( 'redux_metaboxes_loop_start' ) ) { 1530 /** 1531 * Start loop. 1532 * 1533 * @param string $opt_name Panel opt_name. 1534 * @param array $the_post Post object. 1535 */ 1536 function redux_metaboxes_loop_start( string $opt_name, array $the_post = array() ) { 1537 $redux = ReduxFrameworkInstances::get_instance( $opt_name ); 1538 $metaboxes = $redux->extensions['metaboxes']; 1539 1540 $metaboxes->loop_start( $the_post ); 1541 } 1542 } 1543 1544 if ( ! function_exists( 'redux_metaboxes_loop_end' ) ) { 1545 /** 1546 * End loop. 1547 * 1548 * @param string $opt_name Panel opt_name. 1549 * @param array $the_post Post object. 1550 */ 1551 function redux_metaboxes_loop_end( string $opt_name, array $the_post = array() ) { 1552 $redux = ReduxFrameworkInstances::get_instance( $opt_name ); 1553 $metaboxes = $redux->extensions['metaboxes']; 1554 1555 $metaboxes->loop_end(); 1556 } 1557 } 1558 1559 if ( ! function_exists( 'redux_post_meta' ) ) { 1560 /** 1561 * Retrieve post meta values/settings. 1562 * 1563 * @param string $opt_name Panel opt_name. 1564 * @param mixed $the_post Post ID. 1565 * @param string $meta_key Meta key. 1566 * @param mixed $def_val Default value. 1567 * 1568 * @return string|void 1569 */ 1570 function redux_post_meta( string $opt_name = '', $the_post = array(), string $meta_key = '', $def_val = '' ) { 1571 return Redux::get_post_meta( $opt_name, $the_post, $meta_key, $def_val ); 1572 } 1573 }