class-redux-page-render.php (38349B)
1 <?php 2 /** 3 * Redux Page Render Class 4 * 5 * @class Redux_Page_Render 6 * @version 3.0.0 7 * @package Redux Framework/Classes 8 */ 9 10 defined( 'ABSPATH' ) || exit; 11 12 if ( ! class_exists( 'Redux_Page_Render', false ) ) { 13 14 /** 15 * Class Redux_Page_Render 16 */ 17 class Redux_Page_Render extends Redux_Class { 18 19 /** 20 * Flag to show or hide hints in panel. 21 * 22 * @var bool 23 * @access private 24 */ 25 private $show_hints = false; 26 27 /** 28 * Creates page's hook suffix. 29 * 30 * @var false|string 31 * @access private 32 */ 33 private $page = ''; 34 35 /** 36 * Redux_Page_Render constructor. 37 * 38 * @param object $parent ReduxFramework pointer. 39 */ 40 public function __construct( $parent ) { 41 parent::__construct( $parent ); 42 43 // phpcs:ignore Generic.Strings.UnnecessaryStringConcat 44 add_action( 'admin' . '_bar' . '_menu', array( $this, 'add_menu' ), $parent->args['admin_bar_priority'] ); 45 46 // Options page. 47 add_action( 'admin_menu', array( $this, 'options_page' ) ); 48 49 // Add a network menu. 50 if ( 'network' === $parent->args['database'] && $parent->args['network_admin'] ) { 51 add_action( 'network_admin_menu', array( $this, 'options_page' ) ); 52 } 53 } 54 55 /** 56 * Class Options Page Function, creates main options page. 57 * 58 * @since 1.0.0 59 * @access public 60 * @return void 61 */ 62 public function options_page() { 63 $core = $this->core(); 64 // phpcs:ignore Generic.CodeAnalysis.EmptyStatement 65 if ( 'hidden' === $core->args['menu_type'] ) { 66 // No menu to add! 67 } elseif ( 'submenu' === $core->args['menu_type'] ) { 68 $this->submenu( $core ); 69 } else { 70 // Theme-Check notice is displayed for WP.org theme devs, informing them to NOT use this. 71 $this->page = call_user_func( 72 'add_menu_page', 73 $core->args['page_title'], 74 $core->args['menu_title'], 75 $core->args['page_permissions'], 76 $core->args['page_slug'], 77 array( 78 $this, 79 'generate_panel', 80 ), 81 $core->args['menu_icon'], 82 $core->args['page_priority'] 83 ); 84 85 if ( true === $core->args['allow_sub_menu'] ) { 86 foreach ( $core->sections as $k => $section ) { 87 $can_be_subsection = $k > 0 && ( ! isset( $core->sections[ ( $k ) ]['type'] ) || 'divide' !== $core->sections[ ( $k ) ]['type'] ); 88 89 if ( ! isset( $section['title'] ) || ( $can_be_subsection && ( isset( $section['subsection'] ) && true === $section['subsection'] ) ) ) { 90 continue; 91 } 92 93 if ( isset( $section['submenu'] ) && false === $section['submenu'] ) { 94 continue; 95 } 96 97 if ( isset( $section['customizer_only'] ) && true === $section['customizer_only'] ) { 98 continue; 99 } 100 101 if ( isset( $section['hidden'] ) && true === $section['hidden'] ) { 102 continue; 103 } 104 105 if ( isset( $section['permissions'] ) && ! Redux_Helpers::current_user_can( $section['permissions'] ) ) { 106 continue; 107 } 108 109 // ONLY for non-wp.org themes OR plugins. Theme-Check alert shown if used and IS theme. 110 call_user_func( 111 'add_submenu_page', 112 $core->args['page_slug'], 113 $section['title'], 114 $section['title'], 115 $core->args['page_permissions'], 116 $core->args['page_slug'] . '&tab=' . $k, 117 '__return_null' 118 ); 119 } 120 121 // Remove parent submenu item instead of adding null item. 122 remove_submenu_page( $core->args['page_slug'], $core->args['page_slug'] ); 123 } 124 } 125 126 add_action( "load-$this->page", array( $this, 'load_page' ) ); 127 } 128 129 /** 130 * Show page help 131 * 132 * @since 1.0.0 133 * @access public 134 * @return void 135 */ 136 public function load_page() { 137 $core = $this->core(); 138 139 // Do admin head action for this page. 140 add_action( 'admin_head', array( $this, 'admin_head' ) ); 141 142 // Do admin footer text hook. 143 add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ) ); 144 145 $screen = get_current_screen(); 146 147 if ( is_array( $core->args['help_tabs'] ) ) { 148 foreach ( $core->args['help_tabs'] as $tab ) { 149 $screen->add_help_tab( $tab ); 150 } 151 } 152 153 // If hint argument is set, display hint tab. 154 if ( true === $this->show_hints ) { 155 global $current_user; 156 157 // Users enable/disable hint choice. 158 $hint_status = get_user_meta( $current_user->ID, 'ignore_hints' ) ? get_user_meta( $current_user->ID, 'ignore_hints', true ) : 'true'; 159 160 // current page parameters. 161 if ( isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification 162 $cur_page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification 163 } 164 165 $cur_tab = '0'; 166 if ( isset( $_GET['tab'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification 167 $cur_tab = sanitize_text_field( wp_unslash( $_GET['tab'] ) ); // phpcs:ignore WordPress.Security.NonceVerification 168 } 169 170 // Default url values for enabling hints. 171 $dismiss = 'true'; 172 $s = esc_html__( 'Enable', 'redux-framework' ); 173 174 // Values for disabling hints. 175 if ( 'true' === $hint_status ) { 176 $dismiss = 'false'; 177 $s = esc_html__( 'Disable', 'redux-framework' ); 178 } 179 180 // Make URL. 181 $nonce = wp_create_nonce( 'redux_hint_toggle' ); 182 $url = '<a class="redux_hint_status" href="?nonce=' . $nonce . '&dismiss=' . $dismiss . '&id=hints&page=' . esc_attr( $cur_page ) . '&tab=' . esc_attr( $cur_tab ) . '">' . $s . ' hints</a>'; 183 184 $event = esc_html__( 'moving the mouse over', 'redux-framework' ); 185 if ( 'click' === $core->args['hints']['tip_effect']['show']['event'] ) { 186 $event = esc_html__( 'clicking', 'redux-framework' ); 187 } 188 189 // Construct message. 190 // translators: %1$s: Mousr action. %2$s: Hint status. 191 $msg = sprintf( esc_html__( 'Hints are tooltips that popup when %1$s the hint icon, offering addition information about the field in which they appear. They can be %2$s by using the link below.', 'redux-framework' ), $event, Redux_Core::strtolower( $s ) ) . '<br/><br/>' . $url; 192 193 // Construct hint tab. 194 $tab = array( 195 'id' => 'redux-hint-tab', 196 'title' => esc_html__( 'Hints', 'redux-framework' ), 197 'content' => '<p>' . $msg . '</p>', 198 ); 199 200 $screen->add_help_tab( $tab ); 201 } 202 203 // Sidebar text. 204 if ( '' !== $core->args['help_sidebar'] ) { 205 206 // Specify users text from arguments. 207 $screen->set_help_sidebar( $core->args['help_sidebar'] ); 208 } else { 209 // If sidebar text is empty and hints are active, display text 210 // about hints. 211 if ( true === $this->show_hints ) { 212 $screen->set_help_sidebar( '<p><strong>Redux Framework</strong><br/><br/>' . esc_html__( 'Hint Tooltip Preferences', 'redux-framework' ) . '</p>' ); 213 } 214 } 215 216 /** 217 * Action 'redux/page/{opt_name}/load' 218 * 219 * @param object $screen WP_Screen 220 */ 221 222 // phpcs:ignore WordPress.NamingConventions.ValidHookName 223 do_action( "redux/page/{$core->args['opt_name']}/load", $screen ); 224 } 225 226 /** 227 * Class Add Sub Menu Function, creates options submenu in WordPress admin area. 228 * 229 * @param object $core ReduxFrameword core pointer. 230 * 231 * @since 3.1.9 232 * @access private 233 * @return void 234 */ 235 private function submenu( $core ) { 236 global $submenu; 237 238 $page_parent = $core->args['page_parent']; 239 $page_title = $core->args['page_title']; 240 $menu_title = $core->args['menu_title']; 241 $page_permissions = $core->args['page_permissions']; 242 $page_slug = $core->args['page_slug']; 243 244 // Just in case. One never knows. 245 $page_parent = Redux_Core::strtolower( $page_parent ); 246 247 $test = array( 248 'index.php' => 'dashboard', 249 'edit.php' => 'posts', 250 'upload.php' => 'media', 251 'link-manager.php' => 'links', 252 'edit.php?post_type=page' => 'pages', 253 'edit-comments.php' => 'comments', 254 'themes.php' => 'theme', 255 'plugins.php' => 'plugins', 256 'users.php' => 'users', 257 'tools.php' => 'management', 258 'options-general.php' => 'options', 259 ); 260 261 if ( isset( $test[ $page_parent ] ) ) { 262 $function = 'add_' . $test[ $page_parent ] . '_page'; 263 $this->page = $function( 264 $page_title, 265 $menu_title, 266 $page_permissions, 267 $page_slug, 268 array( $this, 'generate_panel' ) 269 ); 270 } else { 271 // Network settings and Post type menus. These do not have 272 // wrappers and need to be appened to using add_submenu_page. 273 // Okay, since we've left the post type menu appending 274 // as default, we need to validate it, so anything that 275 // isn't post_type=<post_type> doesn't get through and mess 276 // things up. 277 $add_menu = false; 278 if ( 'settings.php' !== $page_parent ) { 279 // Establish the needle. 280 $needle = '?post_type='; 281 282 // Check if it exists in the page_parent (how I miss instr). 283 $needle_pos = strrpos( $page_parent, $needle ); 284 285 // It's there, so... 286 if ( $needle_pos > 0 ) { 287 288 // Get the post type. 289 $post_type = substr( $page_parent, $needle_pos + strlen( $needle ) ); 290 291 // Ensure it exists. 292 if ( post_type_exists( $post_type ) ) { 293 // Set flag to add the menu page. 294 $add_menu = true; 295 } 296 // custom menu. 297 } elseif ( isset( $submenu[ $core->args['page_parent'] ] ) ) { 298 $add_menu = true; 299 } else { 300 global $menu; 301 302 foreach ( $menu as $menupriority => $menuitem ) { 303 $needle_menu_slug = isset( $menuitem ) ? $menuitem[2] : false; 304 if ( false !== $needle_menu_slug ) { 305 306 // check if the current needle menu equals page_parent. 307 if ( 0 === strcasecmp( $needle_menu_slug, $page_parent ) ) { 308 309 // found an empty parent menu. 310 $add_menu = true; 311 } 312 } 313 } 314 } 315 } else { 316 // The page_parent was settings.php, so set menu add 317 // flag to true. 318 $add_menu = true; 319 } 320 // Add the submenu if it's permitted. 321 if ( true === $add_menu ) { 322 // ONLY for non-wp.org themes OR plugins. Theme-Check alert shown if used and IS theme. 323 $this->page = call_user_func( 324 'add_submenu_page', 325 $page_parent, 326 $page_title, 327 $menu_title, 328 $page_permissions, 329 $page_slug, 330 array( 331 $this, 332 'generate_panel', 333 ) 334 ); 335 } 336 } 337 } 338 339 /** 340 * Output the option panel. 341 */ 342 public function generate_panel() { 343 $core = $this->core(); 344 345 $panel = new Redux_Panel( $core ); 346 $panel->init(); 347 $core->transient_class->set(); 348 } 349 350 /** 351 * Section HTML OUTPUT. 352 * 353 * @param array $section Sections array. 354 * 355 * @return void 356 * @since 1.0.0 357 * @access public 358 */ 359 public function section_desc( array $section ) { 360 $core = $this->core(); 361 362 $id = rtrim( $section['id'], '_section' ); 363 $id = str_replace( $core->args['opt_name'], '', $id ); 364 365 if ( isset( $core->sections[ $id ]['desc'] ) && ! empty( $core->sections[ $id ]['desc'] ) ) { 366 echo '<div class="redux-section-desc">' . wp_kses_post( $core->sections[ $id ]['desc'] ) . '</div>'; 367 } 368 } 369 370 /** 371 * Field HTML OUTPUT. 372 * Gets option from options array, then calls the specific field type class - allows extending by other devs 373 * 374 * @param array $field Field array. 375 * @param string|array|null $v Values. 376 * 377 * @return void 378 * @since 1.0.0 379 */ 380 public function field_input( array $field, $v = null ) { 381 $core = $this->core(); 382 383 if ( isset( $field['callback'] ) && ( is_callable( $field['callback'] ) || ( is_string( $field['callback'] ) && function_exists( $field['callback'] ) ) ) ) { 384 385 $value = ( isset( $core->options[ $field['id'] ] ) ) ? $core->options[ $field['id'] ] : ''; 386 387 /** 388 * Action 'redux/field/{opt_name}/{field.type}/callback/before' 389 * 390 * @param array $field field data 391 * @param string $value field.id 392 */ 393 // phpcs:ignore WordPress.NamingConventions.ValidHookName 394 do_action_ref_array( 395 // phpcs:ignore WordPress.NamingConventions.ValidHookName 396 "redux/field/{$core->args['opt_name']}/{$field['type']}/callback/before", 397 array( 398 &$field, 399 &$value, 400 ) 401 ); 402 403 /** 404 * Action 'redux/field/{opt_name}/callback/before' 405 * 406 * @param array $field field data 407 * @param string $value field.id 408 */ 409 // phpcs:ignore WordPress.NamingConventions.ValidHookName 410 do_action_ref_array( 411 // phpcs:ignore WordPress.NamingConventions.ValidHookName 412 "redux/field/{$core->args['opt_name']}/callback/before", 413 array( 414 &$field, 415 &$value, 416 ) 417 ); 418 419 call_user_func( $field['callback'], $field, $value ); 420 421 /** 422 * Action 'redux/field/{opt_name}/{field.type}/callback/after' 423 * 424 * @param array $field field data 425 * @param string $value field.id 426 */ 427 // phpcs:ignore WordPress.NamingConventions.ValidHookName 428 do_action_ref_array( 429 // phpcs:ignore WordPress.NamingConventions.ValidHookName 430 "redux/field/{$core->args['opt_name']}/{$field['type']}/callback/after", 431 array( 432 &$field, 433 &$value, 434 ) 435 ); 436 437 /** 438 * Action 'redux/field/{opt_name}/callback/after' 439 * 440 * @param array $field field data 441 * @param string $value field.id 442 */ 443 // phpcs:ignore WordPress.NamingConventions.ValidHookName 444 do_action_ref_array( 445 // phpcs:ignore WordPress.NamingConventions.ValidHookName 446 "redux/field/{$core->args['opt_name']}/callback/after", 447 array( 448 &$field, 449 &$value, 450 ) 451 ); 452 453 return; 454 } 455 456 if ( isset( $field['type'] ) ) { 457 // If the field is set not to display in the panel. 458 $display = true; 459 460 if ( isset( $_GET['page'] ) && $core->args['page_slug'] === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification 461 if ( isset( $field['panel'] ) && false === $field['panel'] ) { 462 $display = false; 463 } 464 } 465 466 if ( ! $display ) { 467 return; 468 } 469 470 /** 471 * Filter 'redux/{opt_name}/field/class/{field.type}' 472 * 473 * @param string field class file path 474 * @param array $field field data 475 */ 476 $field_type = str_replace( '_', '-', $field['type'] ); 477 $core_path = Redux_Core::$dir . "inc/fields/{$field['type']}/class-redux-$field_type.php"; 478 479 // Shim for v3 extension class names. 480 if ( ! file_exists( $core_path ) ) { 481 $core_path = Redux_Core::$dir . "inc/fields/{$field['type']}/field_{$field['type']}.php"; 482 } 483 if ( Redux_Core::$pro_loaded ) { 484 $pro_path = ''; 485 486 if ( class_exists( 'Redux_Pro' ) ) { 487 $pro_path = Redux_Pro::$dir . "core/inc/fields/{$field['type']}/class-redux-pro-$field_type.php"; 488 } 489 490 if ( file_exists( $pro_path ) ) { 491 $filter_path = $pro_path; 492 } else { 493 $filter_path = $core_path; 494 } 495 } else { 496 $filter_path = $core_path; 497 } 498 499 // phpcs:ignore WordPress.NamingConventions.ValidHookName 500 $class_file = apply_filters( "redux/{$core->args['opt_name']}/field/class/{$field['type']}", $filter_path, $field ); 501 502 if ( $class_file ) { 503 $field_classes = array( 'Redux_' . $field['type'], 'ReduxFramework_' . $field['type'] ); 504 505 $field_class = Redux_Functions::class_exists_ex( $field_classes ); 506 507 if ( ! class_exists( $field_class ) ) { 508 if ( file_exists( $class_file ) ) { 509 require_once $class_file; 510 $field_class = Redux_Functions::class_exists_ex( $field_classes ); 511 } else { 512 // translators: %1$s is the field ID, %2$s is the field type. 513 echo sprintf( esc_html__( 'Field %1$s could not be displayed. Field type %2$s was not found.', 'redux-framework' ), '<code>' . esc_attr( $field['id'] ) . '</code>', '<code>' . esc_attr( $field['type'] ) . '</code>' ); 514 } 515 } 516 } 517 518 if ( class_exists( $field_class ) ) { 519 $value = $core->options[ $field['id'] ] ?? ''; 520 521 if ( null !== $v ) { 522 $value = $v; 523 } 524 525 /** 526 * Action 'redux/field/{opt_name}/{field.type}/render/before' 527 * 528 * @param array $field field data 529 * @param string $value field id 530 */ 531 // phpcs:ignore WordPress.NamingConventions.ValidHookName 532 do_action_ref_array( 533 // phpcs:ignore WordPress.NamingConventions.ValidHookName 534 "redux/field/{$core->args['opt_name']}/{$field['type']}/render/before", 535 array( 536 &$field, 537 &$value, 538 ) 539 ); 540 541 /** 542 * Action 'redux/field/{$this->args['opt_name']}/render/before' 543 * 544 * @param array $field field data 545 * @param string $value field id 546 */ 547 // phpcs:ignore WordPress.NamingConventions.ValidHookName 548 do_action_ref_array( 549 // phpcs:ignore WordPress.NamingConventions.ValidHookName 550 "redux/field/{$core->args['opt_name']}/render/before", 551 array( 552 &$field, 553 &$value, 554 ) 555 ); 556 557 if ( ! isset( $field['name_suffix'] ) ) { 558 $field['name_suffix'] = ''; 559 } 560 561 $data = array( 562 'field' => $field, 563 'value' => $value, 564 'core' => $core, 565 'mode' => 'render', 566 ); 567 568 $pro_field_loaded = Redux_Functions::load_pro_field( $data ); 569 570 $render = new $field_class( $field, $value, $core ); 571 572 ob_start(); 573 try { 574 $render->render(); 575 } catch ( Error $e ) { 576 echo 'Field failed to render: ', esc_html( $e->getMessage() ), "\n"; 577 } 578 579 /** 580 * Filter 'redux/field/{opt_name}' 581 * 582 * @param string rendered field markup 583 * @param array $field field data 584 */ 585 586 // phpcs:ignore WordPress.NamingConventions.ValidHookName 587 $_render = apply_filters( "redux/field/{$core->args['opt_name']}", ob_get_contents(), $field ); 588 589 /** 590 * Filter 'redux/field/{opt_name}/{field.type}/render/after' 591 * 592 * @param string rendered field markup 593 * @param array $field field data 594 */ 595 596 // phpcs:ignore WordPress.NamingConventions.ValidHookName 597 $_render = apply_filters( "redux/field/{$core->args['opt_name']}/{$field['type']}/render/after", $_render, $field ); 598 599 /** 600 * Filter 'redux/field/{opt_name}/render/after' 601 * 602 * @param string rendered field markup 603 * @param array $field field data 604 */ 605 606 // phpcs:ignore WordPress.NamingConventions.ValidHookName 607 $_render = apply_filters( "redux/field/{$core->args['opt_name']}/render/after", $_render, $field ); 608 609 ob_end_clean(); 610 611 // create default data und class string and checks the dependencies of an object. 612 $class_string = ''; 613 614 $core->required_class->check_dependencies( $field ); 615 616 /** 617 * Action 'redux/field/{opt_name}/{field.type}/fieldset/before/{opt_name}' 618 * 619 * @param array $field field data 620 * @param string $value field id 621 */ 622 // phpcs:ignore WordPress.NamingConventions.ValidHookName 623 do_action_ref_array( 624 // phpcs:ignore WordPress.NamingConventions.ValidHookName 625 "redux/field/{$core->args['opt_name']}/{$field['type']}/fieldset/before/{$core->args['opt_name']}", 626 array( 627 &$field, 628 &$value, 629 ) 630 ); 631 632 /** 633 * Action 'redux/field/{opt_name}/fieldset/before/{opt_name}' 634 * 635 * @param array $field field data 636 * @param string $value field id 637 */ 638 // phpcs:ignore WordPress.NamingConventions.ValidHookName 639 do_action_ref_array( 640 // phpcs:ignore WordPress.NamingConventions.ValidHookName 641 "redux/field/{$core->args['opt_name']}/fieldset/before/{$core->args['opt_name']}", 642 array( 643 &$field, 644 &$value, 645 ) 646 ); 647 648 $hidden = ''; 649 if ( isset( $field['hidden'] ) && $field['hidden'] ) { 650 $hidden = 'hidden '; 651 } 652 653 $disabled = ''; 654 if ( isset( $field['disabled'] ) && $field['disabled'] ) { 655 $disabled = 'disabled '; 656 } 657 658 if ( isset( $field['full_width'] ) && true === $field['full_width'] ) { 659 $class_string .= 'redux_remove_th'; 660 } 661 662 if ( isset( $field['fieldset_class'] ) && ! empty( $field['fieldset_class'] ) ) { 663 $class_string .= ' ' . $field['fieldset_class']; 664 } 665 666 if ( Redux_Core::$pro_loaded ) { 667 if ( $pro_field_loaded ) { 668 $class_string .= ' redux-pro-field-init'; 669 } 670 } 671 672 echo '<fieldset id="' . esc_attr( $core->args['opt_name'] . '-' . $field['id'] ) . '" class="' . esc_attr( $hidden . esc_attr( $disabled ) . 'redux-field-container redux-field redux-field-init redux-container-' . $field['type'] . ' ' . $class_string ) . '" data-id="' . esc_attr( $field['id'] ) . '" data-type="' . esc_attr( $field['type'] ) . '">'; 673 echo $_render; // phpcs:ignore WordPress.Security.EscapeOutput 674 675 if ( ! empty( $field['desc'] ) ) { 676 $field['description'] = $field['desc']; 677 } 678 679 echo ( isset( $field['description'] ) && 'info' !== $field['type'] && 'section' !== $field['type'] && ! empty( $field['description'] ) ) ? '<div class="description field-desc">' . wp_kses_post( $field['description'] ) . '</div>' : ''; 680 echo '</fieldset>'; 681 682 /** 683 * Action 'redux/field/{opt_name}/{field.type}/fieldset/after/{opt_name}' 684 * 685 * @param array $field field data 686 * @param string $value field id 687 */ 688 // phpcs:ignore WordPress.NamingConventions.ValidHookName 689 do_action_ref_array( 690 // phpcs:ignore WordPress.NamingConventions.ValidHookName 691 "redux/field/{$core->args['opt_name']}/{$field['type']}/fieldset/after/{$core->args['opt_name']}", 692 array( 693 &$field, 694 &$value, 695 ) 696 ); 697 698 /** 699 * Action 'redux/field/{opt_name}/fieldset/after/{opt_name}' 700 * 701 * @param array $field field data 702 * @param string $value field id 703 */ 704 // phpcs:ignore WordPress.NamingConventions.ValidHookName 705 do_action_ref_array( 706 // phpcs:ignore WordPress.NamingConventions.ValidHookName 707 "redux/field/{$core->args['opt_name']}/fieldset/after/{$core->args['opt_name']}", 708 array( 709 &$field, 710 &$value, 711 ) 712 ); 713 } 714 } 715 } 716 717 /** 718 * Add admin bar menu. 719 * 720 * @since 3.1.5.16 721 * @access public 722 * @global $menu , $submenu, $wp_admin_bar 723 * @return void 724 */ 725 public function add_menu() { 726 global $menu, $submenu, $wp_admin_bar; 727 728 $core = $this->core(); 729 730 if ( ! is_super_admin() || ! is_admin_bar_showing() || ! $core->args['admin_bar'] || 'hidden' === $core->args['menu_type'] ) { 731 return; 732 } 733 734 if ( $menu ) { 735 foreach ( $menu as $menu_item ) { 736 if ( isset( $menu_item[2] ) && $menu_item[2] === $core->args['page_slug'] ) { 737 738 // Fetch the title. 739 $title = empty( $core->args['admin_bar_icon'] ) ? $menu_item[0] : '<span class="ab-icon ' . esc_attr( $core->args['admin_bar_icon'] ) . '"></span>' . esc_html( $menu_item[0] ); 740 741 $nodeargs = array( 742 'id' => $menu_item[2], 743 'title' => $title, 744 'href' => admin_url( 'admin.php?page=' . $menu_item[2] ), 745 'meta' => array(), 746 ); 747 748 $wp_admin_bar->add_node( $nodeargs ); 749 750 break; 751 } 752 } 753 754 if ( isset( $submenu[ $core->args['page_slug'] ] ) && is_array( $submenu[ $core->args['page_slug'] ] ) ) { 755 foreach ( $submenu[ $core->args['page_slug'] ] as $index => $redux_options_submenu ) { 756 $subnodeargs = array( 757 'id' => esc_html( $core->args['page_slug'] . '_' . $index ), 758 'title' => esc_html( $redux_options_submenu[0] ), 759 'parent' => esc_html( $core->args['page_slug'] ), 760 'href' => esc_url( admin_url( 'admin.php?page=' . $redux_options_submenu[2] ) ), 761 ); 762 763 $wp_admin_bar->add_node( $subnodeargs ); 764 } 765 } 766 767 // Let's deal with external links. 768 if ( isset( $core->args['admin_bar_links'] ) ) { 769 if ( ! $core->args['dev_mode'] && $core->args_class->omit_items ) { 770 return; 771 } 772 773 // Group for Main Root Menu (External Group). 774 $wp_admin_bar->add_node( 775 array( 776 'id' => esc_html( $core->args['page_slug'] . '-external' ), 777 'parent' => esc_html( $core->args['page_slug'] ), 778 'group' => true, 779 'meta' => array( 'class' => 'ab-sub-secondary' ), 780 ) 781 ); 782 783 // Add Child Menus to External Group Menu. 784 foreach ( $core->args['admin_bar_links'] as $link ) { 785 if ( ! isset( $link['id'] ) ) { 786 $link['id'] = $core->args['page_slug'] . '-sub-' . sanitize_html_class( $link['title'] ); 787 } 788 789 $externalnodeargs = array( 790 'id' => esc_html( $link['id'] ), 791 'title' => esc_html( $link['title'] ), 792 'parent' => esc_html( $core->args['page_slug'] . '-external' ), 793 'href' => esc_url( $link['href'] ), 794 'meta' => array( 'target' => '_blank' ), 795 ); 796 797 $wp_admin_bar->add_node( $externalnodeargs ); 798 } 799 } 800 } else { 801 // Fetch the title. 802 $title = empty( $core->args['admin_bar_icon'] ) ? $core->args['menu_title'] : '<span class="ab-icon ' . esc_attr( $core->args['admin_bar_icon'] ) . '"></span>' . esc_html( $core->args['menu_title'] ); 803 804 $nodeargs = array( 805 'id' => esc_html( $core->args['page_slug'] ), 806 'title' => $title, 807 'href' => esc_url( admin_url( 'admin.php?page=' . $core->args['page_slug'] ) ), 808 'meta' => array(), 809 ); 810 811 $wp_admin_bar->add_node( $nodeargs ); 812 } 813 } 814 815 /** 816 * Do action redux-admin-head for options page 817 * 818 * @since 1.0.0 819 * @access public 820 * @return void 821 */ 822 public function admin_head() { 823 $core = $this->core(); 824 825 /** 826 * Action 'redux/page/{opt_name}/header' 827 * 828 * @param object $this ReduxFramework 829 */ 830 831 // phpcs:ignore WordPress.NamingConventions.ValidHookName 832 do_action( "redux/page/{$core->args['opt_name']}/header", $core ); 833 } 834 835 /** 836 * Return footer text 837 * 838 * @since 2.0.0 839 * @access public 840 * @return string $this->args['footer_credit'] 841 */ 842 public function admin_footer_text(): string { 843 $core = $this->core(); 844 845 return $core->args['footer_credit']; 846 } 847 848 /** 849 * Generate field header HTML 850 * 851 * @param array $field Field array. 852 * 853 * @return string 854 */ 855 public function get_header_html( array $field ): string { 856 global $current_user; 857 858 $core = $this->core(); 859 860 // Set to empty string to avoid wanrings. 861 $hint = ''; 862 $th = ''; 863 864 if ( isset( $field['title'] ) && isset( $field['type'] ) && 'info' !== $field['type'] && 'section' !== $field['type'] ) { 865 $default_mark = ( ! empty( $field['default'] ) && isset( $core->options[ $field['id'] ] ) && $field['default'] === $core->options[ $field['id'] ] && ! empty( $core->args['default_mark'] ) && isset( $field['default'] ) ) ? $core->args['default_mark'] : ''; 866 867 // If a hint is specified in the field, process it. 868 if ( isset( $field['hint'] ) && ! empty( $field['hint'] ) ) { 869 870 // Set show_hints flag to true, so helptab will be displayed. 871 $this->show_hints = true; 872 873 // phpcs:ignore WordPress.NamingConventions.ValidHookName 874 $hint = apply_filters( 'redux/hints/html', $hint, $field, $core->args ); 875 876 // Get user pref for displaying hints. 877 $meta_val = get_user_meta( $current_user->ID, 'ignore_hints', true ); 878 if ( 'true' === $meta_val || empty( $meta_val ) && empty( $hint ) ) { 879 880 // Set hand cursor for clickable hints. 881 $pointer = ''; 882 if ( isset( $core->args['hints']['tip_effect']['show']['event'] ) && 'click' === $core->args['hints']['tip_effect']['show']['event'] ) { 883 $pointer = 'pointer'; 884 } 885 886 $size = '16px'; 887 if ( 'large' === $core->args['hints']['icon_size'] ) { 888 $size = '18px'; 889 } 890 891 // In case docs are ignored. 892 $title_param = $field['hint']['title'] ?? ''; 893 $content_param = $field['hint']['content'] ?? ''; 894 895 $hint_color = $core->args['hints']['icon_color'] ?? '#d3d3d3'; 896 897 // Set hint html with appropriate position css. 898 $hint = '<div class="redux-hint-qtip" style="float:' . esc_attr( $core->args['hints']['icon_position'] ) . '; font-size: ' . esc_attr( $size ) . '; color:' . esc_attr( $hint_color ) . '; cursor: ' . $pointer . ';" qtip-title="' . esc_attr( $title_param ) . '" qtip-content="' . wp_kses_post( $content_param ) . '"> <i class="' . ( isset( $core->args['hints']['icon'] ) ? esc_attr( $core->args['hints']['icon'] ) : '' ) . '"></i></div>'; 899 } 900 } 901 902 if ( ! empty( $field['title'] ) ) { 903 if ( 'left' === $core->args['hints']['icon_position'] ) { 904 $th = $hint . wp_kses_post( $field['title'] ) . $default_mark . ''; 905 } else { 906 $th = wp_kses_post( $field['title'] ) . $default_mark . '' . $hint; 907 } 908 } 909 910 if ( isset( $field['subtitle'] ) ) { 911 $th .= '<span class="description">' . wp_kses_post( $field['subtitle'] ) . '</span>'; 912 } 913 } 914 915 if ( ! empty( $th ) ) { 916 $th = '<div class="redux_field_th">' . $th . '</div>'; 917 } 918 919 $filter_arr = array( 920 'editor', 921 'ace_editor', 922 'info', 923 'section', 924 'repeater', 925 'color_scheme', 926 'social_profiles', 927 'css_layout', 928 ); 929 930 if ( true === $core->args['default_show'] && isset( $field['default'] ) && isset( $core->options[ $field['id'] ] ) && $field['default'] !== $core->options[ $field['id'] ] && ! in_array( $field['type'], $filter_arr, true ) ) { 931 $th .= $this->get_default_output_string( $field ); 932 } 933 934 return $th; 935 } 936 937 /** 938 * Return default output string for use in panel 939 * 940 * @param array $field Field array. 941 * 942 * @return string default_output 943 * @since 3.1.5 944 * @access public 945 */ 946 private function get_default_output_string( array $field ): string { 947 $default_output = ''; 948 949 if ( ! isset( $field['default'] ) ) { 950 $field['default'] = ''; 951 } 952 953 if ( ! is_array( $field['default'] ) ) { 954 if ( ! empty( $field['options'][ $field['default'] ] ) ) { 955 if ( ! empty( $field['options'][ $field['default'] ]['alt'] ) ) { 956 $default_output .= $field['options'][ $field['default'] ]['alt'] . ', '; 957 } else { 958 if ( ! is_array( $field['options'][ $field['default'] ] ) ) { 959 $default_output .= $field['options'][ $field['default'] ] . ', '; 960 } else { 961 $default_output .= maybe_serialize( $field['options'][ $field['default'] ] ) . ', '; 962 } 963 } 964 } elseif ( ! empty( $field['options'][ $field['default'] ] ) ) { 965 $default_output .= $field['options'][ $field['default'] ] . ', '; 966 } elseif ( ! empty( $field['default'] ) ) { 967 if ( 'switch' === $field['type'] && isset( $field['on'] ) && isset( $field['off'] ) ) { 968 $default_output .= ( 1 === $field['default'] ? $field['on'] : $field['off'] ) . ', '; 969 } else { 970 $default_output .= $field['default'] . ', '; 971 } 972 } 973 } else { 974 foreach ( $field['default'] as $defaultk => $defaultv ) { 975 if ( ! empty( $field['options'][ $defaultv ]['alt'] ) ) { 976 $default_output .= $field['options'][ $defaultv ]['alt'] . ', '; 977 } elseif ( ! empty( $field['options'][ $defaultv ] ) ) { 978 $default_output .= $field['options'][ $defaultv ] . ', '; 979 } elseif ( ! empty( $field['options'][ $defaultk ] ) ) { 980 $default_output .= $field['options'][ $defaultk ] . ', '; 981 } elseif ( ! empty( $defaultv ) ) { 982 $default_output .= $defaultv . ', '; 983 } 984 } 985 } 986 987 if ( ! empty( $default_output ) ) { 988 $default_output = esc_html__( 'Default', 'redux-framework' ) . ': ' . substr( $default_output, 0, - 2 ); 989 } 990 991 if ( ! empty( $default_output ) ) { 992 $default_output = '<span class="showDefaults">' . esc_html( $default_output ) . '</span><br class="default_br" />'; 993 } 994 995 return $default_output; 996 } 997 998 /** 999 * Return Section Menu HTML. 1000 * 1001 * @param int|string $k Section index. 1002 * @param array $section Sectio array. 1003 * @param string $suffix Optional suffix. 1004 * @param array $sections Sections array. 1005 * 1006 * @return string 1007 * @since 3.1.5 1008 * @access public 1009 */ 1010 public function section_menu( $k, array $section, string $suffix = '', array $sections = array() ): string { 1011 $function_count = 0; 1012 1013 $core = $this->core(); 1014 1015 $display = true; 1016 1017 $section['class'] = isset( $section['class'] ) ? ' ' . $section['class'] : ''; 1018 1019 if ( isset( $_GET['page'] ) && $core->args['page_slug'] === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification 1020 if ( isset( $section['panel'] ) && false === $section['panel'] ) { 1021 $display = false; 1022 } 1023 } 1024 1025 if ( ! $display ) { 1026 return ''; 1027 } 1028 1029 if ( empty( $sections ) ) { 1030 $sections = $core->sections; 1031 $function_count = $k; 1032 } 1033 1034 $string = ''; 1035 if ( ( ( isset( $core->args['icon_type'] ) && 'image' === $core->args['icon_type'] ) || ( isset( $section['icon_type'] ) && 'image' === $section['icon_type'] ) ) || ( isset( $section['icon'] ) && false !== strpos( $section['icon'], '/' ) ) ) { 1036 $icon = ( ! isset( $section['icon'] ) ) ? '' : '<img class="image_icon_type" src="' . esc_url( $section['icon'] ) . '" /> '; 1037 } else { 1038 if ( ! empty( $section['icon_class'] ) ) { 1039 $icon_class = ' ' . $section['icon_class']; 1040 } elseif ( ! empty( $core->args['default_icon_class'] ) ) { 1041 $icon_class = ' ' . $core->args['default_icon_class']; 1042 } else { 1043 $icon_class = ''; 1044 } 1045 $icon = ( ! isset( $section['icon'] ) ) ? '<i class="el el-cog' . esc_attr( $icon_class ) . '"></i> ' : '<i class="' . esc_attr( $section['icon'] ) . esc_attr( $icon_class ) . '"></i> '; 1046 } 1047 if ( strpos( $icon, 'el-icon-' ) !== false ) { 1048 $icon = str_replace( 'el-icon-', 'el el-', $icon ); 1049 } 1050 1051 $hide_section = ''; 1052 if ( isset( $section['hidden'] ) ) { 1053 $hide_section = ( true === $section['hidden'] ) ? ' hidden ' : ''; 1054 } 1055 1056 $can_be_subsection = $k > 0 && ( ! isset( $sections[ ( $k ) ]['type'] ) || 'divide' !== $sections[ ( $k ) ]['type'] ); 1057 1058 if ( ! $can_be_subsection && isset( $section['subsection'] ) && true === $section['subsection'] ) { 1059 unset( $section['subsection'] ); 1060 } 1061 1062 if ( isset( $section['type'] ) && 'divide' === $section['type'] ) { 1063 $string .= '<li class="divide' . esc_attr( $section['class'] ) . '"> </li>'; 1064 } elseif ( ! isset( $section['subsection'] ) || true !== $section['subsection'] ) { 1065 if ( ! isset( $core->args['pro']['flyout_submenus'] ) ) { 1066 $core->args['pro']['flyout_submenus'] = false; 1067 } 1068 1069 $subsections = isset( $sections[ ( $k + 1 ) ] ) && isset( $sections[ ( $k + 1 ) ]['subsection'] ) && true === $sections[ ( $k + 1 ) ]['subsection']; 1070 $subsections_class = $subsections ? ' hasSubSections' : ''; 1071 $subsections_class .= ( ! isset( $section['fields'] ) || empty( $section['fields'] ) ) ? ' empty_section' : ''; 1072 $rotate = true === $core->args['pro']['flyout_submenus'] ? ' el-rotate' : ''; 1073 $extra_icon = $subsections ? '<span class="extraIconSubsections"><i class="el el-chevron-down' . $rotate . '"> </i></span>' : ''; 1074 $string .= '<li id="' . esc_attr( $k . $suffix ) . '_section_group_li" class="redux-group-tab-link-li' . esc_attr( $hide_section ) . esc_attr( $section['class'] ) . esc_attr( $subsections_class ) . '">'; 1075 $string .= '<a href="javascript:void(0);" id="' . esc_attr( $k . $suffix ) . '_section_group_li_a" class="redux-group-tab-link-a" data-key="' . esc_attr( $k ) . '" data-rel="' . esc_attr( $k . $suffix ) . '">' . $extra_icon . $icon . '<span class="group_title">' . wp_kses_post( $section['title'] ) . '</span></a>'; 1076 1077 $next_k = $k; 1078 1079 // Make sure you can make this a subsection. 1080 if ( $subsections ) { 1081 $string .= '<ul id="' . esc_attr( $next_k . $suffix ) . '_section_group_li_subsections" class="subsection">'; 1082 1083 $do_loop = true; 1084 1085 while ( $do_loop ) { 1086 $next_k ++; 1087 $function_count++; 1088 1089 $display = true; 1090 1091 if ( isset( $_GET['page'] ) && $core->args['page_slug'] === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification 1092 if ( isset( $sections[ $next_k ]['panel'] ) && false === $sections[ $next_k ]['panel'] ) { 1093 $display = false; 1094 } 1095 } 1096 1097 if ( count( $sections ) < $function_count || ! isset( $sections[ $next_k ] ) || ! isset( $sections[ $next_k ]['subsection'] ) || true !== $sections[ $next_k ]['subsection'] ) { 1098 $do_loop = false; 1099 } else { 1100 if ( ! $display ) { 1101 continue; 1102 } 1103 1104 $hide_sub = ''; 1105 if ( isset( $sections[ $next_k ]['hidden'] ) ) { 1106 $hide_sub = ( true === $sections[ $next_k ]['hidden'] ) ? ' hidden ' : ''; 1107 } 1108 1109 if ( ( isset( $core->args['icon_type'] ) && 'image' === $core->args['icon_type'] ) || ( isset( $sections[ $next_k ]['icon_type'] ) && 'image' === $sections[ $next_k ]['icon_type'] ) ) { 1110 $icon = ( ! isset( $sections[ $next_k ]['icon'] ) ) ? '' : '<img class="image_icon_type" src="' . esc_url( $sections[ $next_k ]['icon'] ) . '" /> '; 1111 } else { 1112 if ( ! empty( $sections[ $next_k ]['icon_class'] ) ) { 1113 $icon_class = ' ' . $sections[ $next_k ]['icon_class']; 1114 } elseif ( ! empty( $core->args['default_icon_class'] ) ) { 1115 $icon_class = ' ' . $core->args['default_icon_class']; 1116 } else { 1117 $icon_class = ''; 1118 } 1119 $icon = ( ! isset( $sections[ $next_k ]['icon'] ) ) ? '' : '<i class="' . esc_attr( $sections[ $next_k ]['icon'] ) . esc_attr( $icon_class ) . '"></i> '; 1120 } 1121 if ( strpos( $icon, 'el-icon-' ) !== false ) { 1122 $icon = str_replace( 'el-icon-', 'el el-', $icon ); 1123 } 1124 1125 $sections[ $next_k ]['class'] = $sections[ $next_k ]['class'] ?? ''; 1126 $section[ $next_k ]['class'] = $section[ $next_k ]['class'] ?? $sections[ $next_k ]['class']; 1127 $string .= '<li id="' . esc_attr( $next_k . $suffix ) . '_section_group_li" class="redux-group-tab-link-li ' . esc_attr( $hide_sub ) . esc_attr( $section[ $next_k ]['class'] ) . ( $icon ? ' hasIcon' : '' ) . '">'; 1128 $string .= '<a href="javascript:void(0);" id="' . esc_attr( $next_k . $suffix ) . '_section_group_li_a" class="redux-group-tab-link-a" data-key="' . esc_attr( $next_k ) . '" data-rel="' . esc_attr( $next_k . $suffix ) . '">' . $icon . '<span class="group_title">' . wp_kses_post( $sections[ $next_k ]['title'] ) . '</span></a>'; 1129 $string .= '</li>'; 1130 } 1131 } 1132 1133 $string .= '</ul>'; 1134 } 1135 1136 $string .= '</li>'; 1137 } 1138 1139 return $string; 1140 } 1141 } 1142 }