balmet.com

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

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 . '&amp;dismiss=' . $dismiss . '&amp;id=hints&amp;page=' . esc_attr( $cur_page ) . '&amp;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 ) . '">&nbsp;<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'] ) . '">&nbsp;</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 . '">&nbsp;</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 }