<?php
/**
 * Redux User Meta Extension Class
 *
 * @package Redux
 * @author  Dovy Paukstys
 * @class   Redux_Extension_Users
 * @version 4.4.1
 */

defined( 'ABSPATH' ) || exit;

// Don't duplicate me!
if ( ! class_exists( 'Redux_Extension_Users' ) ) {

	/**
	 * Main Redux_Extension_Users class
	 *
	 * @since       1.0.0
	 */
	class Redux_Extension_Users extends Redux_Extension_Abstract {

		/**
		 * Extension version.
		 *
		 * @var string
		 */
		public static $version = '4.4.1';

		/**
		 * Extension friendly name.
		 *
		 * @var string
		 */
		public $extension_name = 'Users';


		/**
		 * Profiles array.
		 *
		 * @var array
		 */
		public $profiles = array();

		/**
		 * User roles array.
		 *
		 * @var array
		 */
		public $users_roles = array();

		/**
		 * User role array.
		 *
		 * @var array
		 */
		public $users_role = array();

		/**
		 * Sections array.
		 *
		 * @var array
		 */
		public $sections = array();

		/**
		 * Original args array.
		 *
		 * @var array
		 */
		public $orig_args = array();

		/**
		 * Output array.
		 *
		 * @var array
		 */
		public $output = array();

		/**
		 * Options array.
		 *
		 * @var array
		 */
		public $options = array();

		/**
		 * Parent options array.
		 *
		 * @var array
		 */
		public $parent_options = array();

		/**
		 * Parent defaults.
		 *
		 * @var array
		 */
		public $parent_defaults = array();

		/**
		 * Profile fields array.
		 *
		 * @var array
		 */
		public $profile_fields = array();

		/**
		 * WP Links array.
		 *
		 * @var array
		 */
		public $wp_links = array();

		/**
		 * Options defaults.
		 *
		 * @var array
		 */
		public $options_defaults = array();

		/**
		 * Localize data array.
		 *
		 * @var array
		 */
		public $localize_data = array();

		/**
		 * To replace array.
		 *
		 * @var array
		 */
		public $to_replace = array();

		/**
		 * Meta array.
		 *
		 * @var array
		 */
		public $meta = array();

		/**
		 * Base URL.
		 *
		 * @var string
		 */
		public $base_url;

		/**
		 * Array of page names.
		 *
		 * @var array
		 */
		public $pagenows;

		/**
		 * Notices array.
		 *
		 * @var array
		 */
		public $notices;

		/**
		 * Redux_Extension_Users constructor.
		 *
		 * @param object $redux ReduxFramework pointer.
		 */
		public function __construct( $redux ) {
			global $pagenow;

			parent::__construct( $redux, __FILE__ );

			$this->parent = $redux;

			$this->add_field( 'users' );
			$this->parent->extensions['users'] = $this;

			$this->pagenows = array( 'user-new.php', 'profile.php', 'user-edit.php' );

			require_once __DIR__ . '/redux-users-helpers.php';

			add_action( 'admin_notices', array( $this, 'meta_profiles_show_errors' ), 0 );
			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ), 20 );

			if ( is_admin() && in_array( $pagenow, $this->pagenows, true ) ) {
				$this->init();

				add_action( 'personal_options_update', array( $this, 'user_meta_save' ) );
				add_action( 'edit_user_profile_update', array( $this, 'user_meta_save' ) );

			}
		}

		/**
		 * Add term classes.
		 *
		 * @param array $classes Classes.
		 *
		 * @return array
		 */
		public function add_term_classes( array $classes ): array {
			$classes[] = 'redux-users';
			$classes[] = 'redux-' . $this->parent->args['opt_name'];

			if ( '' !== $this->parent->args['class'] ) {
				$classes[] = $this->parent->args['class'];
			}

			return $classes;
		}

		/**
		 * Add hide term class.
		 *
		 * @param array $classes Classes.
		 *
		 * @return array
		 */
		public function add_term_hide_class( array $classes ): array {
			$classes[] = 'hide';

			return $classes;
		}

		/**
		 * Init.
		 */
		public function init() {
			global $pagenow;

			// phpcs:ignore WordPress.Security.NonceVerification
			$user       = isset( $_GET['user_id'] ) ? sanitize_text_field( wp_unslash( $_GET['user_id'] ) ) : get_current_user_id();
			$this->meta = Redux_Users::get_user_meta( array( 'user' => $user ) );

			$this->parent->options = $this->meta;

			// phpcs:ignore WordPress.NamingConventions.ValidHookName
			$this->profiles = apply_filters( 'redux/users/' . $this->parent->args['opt_name'] . '/profiles', $this->profiles, $this->parent->args['opt_name'] );

			if ( empty( $this->profiles ) && class_exists( 'Redux_Users' ) ) {
				$this->profiles = Redux_Users::construct_profiles( $this->parent->args['opt_name'] );
			}

			if ( empty( $this->profiles ) || ! is_array( $this->profiles ) ) {
				return;
			}

			$this->base_url = ( is_ssl() ? 'https://' : 'http://' ) . Redux_Core::$server['HTTP_HOST'] . Redux_Core::$server['REQUEST_URI'];

			foreach ( $this->profiles as $bk => $profile ) {
				$profile['roles'] = isset( $profile['roles'] ) ? (array) $profile['roles'] : array( 'read' );

				if ( ! empty( $profile['sections'] ) ) {
					$this->sections = $profile['sections'];

					$this->users_roles = wp_parse_args( $this->users_roles, $profile['roles'] );

					// Checking to override the parent variables.
					$add_field = false;

					foreach ( $profile['roles'] as $role ) {
						if ( $this->users_role === $role ) {
							$add_field = true;
						}
					}

					// Replacing all the fields.
					if ( $add_field || ( ( is_admin() && in_array( $pagenow, $this->pagenows, true ) ) || ( ! is_admin() ) ) ) {
						$profile_id = 'redux-' . $this->parent->args['opt_name'] . '-metaterm-' . $profile['id'];

						if ( isset( $profile['page_template'] ) && 'page' === $this->users_role ) {
							if ( ! is_array( $profile['page_template'] ) ) {
								$profile['page_template'] = array( $profile['page_template'] );
							}

							$this->wp_links[ $profile_id ]['page_template'] = isset( $this->wp_links[ $profile_id ]['page_template'] ) ? wp_parse_args( $this->wp_links[ $profile_id ]['page_template'], $profile['page_template'] ) : $profile['page_template'];
						}

						if ( isset( $profile['post_format'] ) && ( in_array( $this->users_role, $this->users_roles, true ) || '' === $this->users_role ) ) {
							if ( ! is_array( $profile['post_format'] ) ) {
								$profile['post_format'] = array( $profile['post_format'] );
							}

							$this->wp_links[ $profile_id ]['post_format'] = isset( $this->wp_links[ $profile_id ]['post_format'] ) ? wp_parse_args( $this->wp_links[ $profile_id ]['post_format'], $profile['post_format'] ) : $profile['post_format'];
						}

						foreach ( $profile['sections'] as $sk => $section ) {
							if ( ! empty( $section['fields'] ) ) {
								foreach ( $section['fields'] as $fk => $field ) {
									if ( ! isset( $field['class'] ) ) {
										$field['class'] = '';

										$this->profiles[ $bk ]['sections'][ $sk ]['fields'][ $fk ] = $field;
									}

									$this->parent->required_class->check_dependencies( $field );

									/** phpcs:ignore
									 * if ( stripos( $field['class'], 'redux-field-init' ) === 0 ) {
									 * $field['class'] = trim( $field['class'] . ' redux-field-init' );
									 * }
									 */

									if ( $add_field || ( ( is_admin() && in_array( $pagenow, $this->pagenows, true ) ) || ( ! is_admin() ) ) ) {
										if ( empty( $field['id'] ) ) {
											continue;
										}

										if ( isset( $field['default'] ) ) {
											$this->options_defaults[ $field['id'] ] = $field['default'];
										} else {
											$this->options_defaults[ $field['id'] ] = $this->field_default( $field );
										}

										foreach ( $profile['roles'] as $type ) {
											$this->profile_fields[ $type ][ $field['id'] ] = 1;
										}

										if ( ! empty( $field['output'] ) ) {
											$this->output[ $field['id'] ] = isset( $this->output[ $field['id'] ] ) ? array_merge( $field['output'], $this->output[ $field['id'] ] ) : $field['output'];
										}

										// Detect what field types are being used.
										if ( ! isset( $this->parent->fields[ $field['type'] ][ $field['id'] ] ) ) {
											$this->parent->fields[ $field['type'] ][ $field['id'] ] = 1;
										} else {
											$this->parent->fields[ $field['type'] ] = array( $field['id'] => 1 );
										}

										if ( isset( $this->options_defaults[ $field['id'] ] ) ) {
											$this->to_replace[ $field['id'] ] = $field;
										}
									}

									if ( ! isset( $this->parent->options[ $field['id'] ] ) ) {
										$this->parent->sections[ ( count( $this->parent->sections ) - 1 ) ]['fields'][] = $field;
									}

									if ( ! isset( $this->meta[ $field['id'] ] ) ) {
										$this->meta[ $field['id'] ] = $this->options_defaults[ $field['id'] ];
									}

									// Only override if it exists, and it's not the default.
									/** If ( isset( $this->meta[ $field['id'] ] ) && isset( $field['default'] ) && $this->meta[ $field['id'] ] === $field['default'] ) {
									 *    // unset($this->meta[$this->tag_id][$field['id']]);
									 *  }
									 */
								}
							}
						}
					}
				}
			}

			$this->parent_options = '';

			if ( ! empty( $this->to_replace ) ) {
				foreach ( $this->to_replace as $id => $field ) {
					add_filter( "redux/options/{$this->parent->args['opt_name']}/field/$id/register", array( $this, 'replace_field' ) );
				}
			}

			add_filter( "redux/options/{$this->parent->args['opt_name']}/options", array( $this, 'override_options' ) );

			if ( is_admin() && in_array( $pagenow, $this->pagenows, true ) ) {
				$priority = $this->parent->args['user_priority'] ?? 3;

				add_action( 'show_user_profile', array( $this, 'add_profiles' ), $priority );
				add_action( 'edit_user_profile', array( $this, 'add_profiles' ), $priority );
				add_action( 'user_new_form', array( $this, 'add_profiles' ), $priority );
			}
		}

		/**
		 * Replace field.
		 *
		 * @param array $field Field array.
		 *
		 * @return mixed
		 */
		public function replace_field( array $field ) {
			if ( isset( $this->to_replace[ $field['id'] ] ) ) {
				$field = $this->to_replace[ $field['id'] ];
			}

			return $field;
		}

		/**
		 * Output CSS.
		 *
		 * @param array $field Field array.
		 *
		 * @return array
		 */
		public function output_css( array $field ): array {
			if ( isset( $this->output[ $field['id'] ] ) ) {
				$field['output'] = $this->output[ $field['id'] ];
			}

			return $field;
		}

		/**
		 * Make sure the defaults are the defaults
		 *
		 * @param array $options Option array.
		 *
		 * @return array
		 */
		public function override_options( array $options ): array {
			$this->parent->options_class->default_values();
			$this->parent_defaults = $this->parent->options_defaults;

			if ( empty( $this->meta ) ) {
				// phpcs:ignore WordPress.Security.NonceVerification
				$user       = isset( $_GET['user_id'] ) ? sanitize_text_field( wp_unslash( $_GET['user_id'] ) ) : get_current_user_id();
				$this->meta = Redux_Users::get_user_meta( array( 'user' => $user ) );
			}

			$data = wp_parse_args( $this->meta, $this->options_defaults );

			foreach ( $data as $key => $value ) {
				if ( isset( $meta[ $key ] ) && '' !== $meta[ $key ] ) {
					$data[ $key ] = $meta[ $key ];
					continue;
				}

				if ( isset( $options[ $key ] ) ) {
					$data[ $key ] = $options[ $key ];
				}
			}

			$this->parent->options_defaults = wp_parse_args( $this->options_defaults, $this->parent->options_defaults );

			return wp_parse_args( $data, $options );
		}

		/**
		 * Sanitize Query.
		 *
		 * @param array $queries Queries.
		 *
		 * @return array
		 */
		public function sanitize_query( array $queries ): array {
			$clean_queries = array();

			foreach ( $queries as $key => $query ) {
				if ( 'relation' === $key ) {
					$relation = $query;

				} elseif ( ! is_array( $query ) ) {
					$clean_queries[ $key ]['values'] = array( $query );

					// First-order clause.
				} elseif ( $this->is_first_order_clause( $query ) ) {
					print( 'h3' );
					if ( isset( $query['value'] ) && array() === $query['value'] ) {
						unset( $query['value'] );
					}

					$clean_queries[ $key ] = $query;

					// Otherwise, it's a nested query, so we recurse.
				} else {
					print( 'h4' );
					$cleaned_query = $this->sanitize_query( $query );

					if ( ! empty( $cleaned_query ) ) {
						$clean_queries[ $key ] = $cleaned_query;
					}
				}
			}

			if ( empty( $clean_queries ) ) {
				return $clean_queries;
			}

			// Sanitize the 'relation' key provided in the query.
			if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
				$clean_queries['relation'] = 'OR';

				/*
				 * If there is only a single clause, call the relation 'OR'.
				 * This value will not actually be used to join clauses, but it
				 * simplifies the logic around combining key-only queries.
				 */
			} elseif ( 1 === count( $clean_queries ) ) {
				$clean_queries['relation'] = 'OR';

				// Default to AND.
			} else {
				$clean_queries['relation'] = 'AND';
			}

			return $clean_queries;
		}

		/**
		 * Is first order clause.
		 *
		 * @param array $query Query array.
		 *
		 * @return bool
		 */
		protected function is_first_order_clause( array $query ): bool {
			return isset( $query['key'] ) || isset( $query['value'] );
		}

		/**
		 * Support file and field enqueue.
		 */
		public function enqueue() {
			global $pagenow;

			if ( in_array( $pagenow, $this->pagenows, true ) ) {

				if ( 'user-new.php' === $pagenow ) {
					$this->parent->args['disable_save_warn'] = true;
				}

				$this->parent->transients       = get_transient( $this->parent->args['opt_name'] . '-transients-users' );
				$this->parent->transients_check = $this->parent->transients;

				if ( isset( $this->parent->transients['notices'] ) ) {
					$this->notices                              = $this->parent->transients['notices'];
					$this->parent->transients['last_save_mode'] = 'users';
				}

				delete_transient( $this->parent->args['opt_name'] . '-transients-users' );
				$this->parent->enqueue_class->init();

				// phpcs:ignore WordPress.NamingConventions.ValidHookName
				do_action( "redux/users/{$this->parent->args['opt_name']}/enqueue" );

				/**
				 * Redux users CSS
				 * filter 'redux/page/{opt_name}/enqueue/redux-extension-users-css'
				 */
				if ( true === $this->parent->args['dev_mode'] ) {
					wp_enqueue_style(
						'redux-extension-users',
						// phpcs:ignore WordPress.NamingConventions.ValidHookName
						apply_filters( "redux/users/{$this->parent->args['opt_name']}/enqueue/redux-extension-users-css", $this->extension_url . 'redux-extension-users.css' ),
						array(),
						self::$version
					);
				}

				/**
				 * Redux users JS
				 * filter 'redux/page/{opt_name}/enqueue/redux-extension-users-js
				 */
				wp_enqueue_script(
					'redux-extension-users',
					// phpcs:ignore WordPress.NamingConventions.ValidHookName
					apply_filters( "redux/users/{$this->parent->args['opt_name']}/enqueue/redux-extension-users-js", $this->extension_url . 'redux-extension-users' . Redux_Functions::is_min() . '.js' ),
					array( 'jquery', 'redux-js' ),
					self::$version,
					true
				);

				// Values used by the javascript.
				wp_localize_script( 'redux-extension-users', 'reduxUsers', $this->users_roles );

			}
		}


		/**
		 * DEPRECATED
		 */
		public function default_values() {
			if ( ! empty( $this->profiles ) && empty( $this->options_defaults ) ) {
				foreach ( $this->profiles as $key => $profile ) {
					if ( empty( $profile['sections'] ) ) {
						continue;
					}

					// fill the cache.
					foreach ( $profile['sections'] as $sk => $section ) {
						if ( ! isset( $section['id'] ) ) {
							if ( ! is_numeric( $sk ) || ! isset( $section['title'] ) ) {
								$section['id'] = $sk;
							} else {
								$section['id'] = sanitize_title( $section['title'], $sk );
							}
							$this->profiles[ $key ]['sections'][ $sk ] = $section;
						}
						if ( isset( $section['fields'] ) ) {
							foreach ( $section['fields'] as $k => $field ) {
								if ( empty( $field['id'] ) && empty( $field['type'] ) ) {
									continue;
								}

								if ( 'ace_editor' === $field['type'] && isset( $field['options'] ) ) {
									$this->profiles[ $key ]['sections'][ $sk ]['fields'][ $k ]['args'] = $field['options'];
									unset( $this->profiles[ $key ]['sections'][ $sk ]['fields'][ $k ]['options'] );
								}

								if ( 'section' === $field['type'] && isset( $field['indent'] ) && true === $field['indent'] ) {
									$field['class']  = $field['class'] ?? '';
									$field['class'] .= 'redux-section-indent-start';

									$this->profiles[ $key ]['sections'][ $sk ]['fields'][ $k ] = $field;
								}

								$this->parent->options_defaults_class->field_default_values( $this->parent->args['opt_name'], $field );
							}
						}
					}
				}
			}

			if ( empty( $this->meta ) ) {
				// phpcs:ignore WordPress.Security.NonceVerification
				$user       = isset( $_GET['user_id'] ) ? sanitize_text_field( wp_unslash( $_GET['user_id'] ) ) : get_current_user_id();
				$this->meta = Redux_Users::get_user_meta( array( 'user' => $user ) );
			}
		}

		/**
		 * Add Profiles.
		 */
		public function add_profiles() {
			if ( empty( $this->profiles ) || ! is_array( $this->profiles ) ) {
				return;
			}

			foreach ( $this->profiles as $key => $profile ) {
				if ( empty( $profile['sections'] ) ) {
					continue;
				}

				$defaults = array(
					'id'         => "$key",
					'section_id' => $key,
					'profiles'   => array(),
				);

				$profile = wp_parse_args( $profile, $defaults );

				// Override the parent args on a meta-term level.
				if ( empty( $this->orig_args ) ) {
					$this->orig_args = $this->parent->args;
				}

				if ( isset( $profile['args'] ) ) {
					$this->parent->args = wp_parse_args( $profile['args'], $this->orig_args );
				} elseif ( $this->parent->args !== $this->orig_args ) {
					$this->parent->args = $this->orig_args;
				}

				if ( ! isset( $profile['class'] ) ) {
					$profile['class'] = array();
				}

				if ( ! empty( $profile['class'] ) ) {
					if ( ! is_array( $profile['class'] ) ) {
						$profile['class'] = array( $profile['class'] );
					}
				}

				$profile['class'] = $this->add_term_classes( $profile['class'] );

				if ( isset( $profile['post_format'] ) ) {
					$profile['class'] = $this->add_term_hide_class( $profile['class'] );
				}

				global $pagenow;
				if ( strpos( $pagenow, 'edit-' ) !== false ) {

					$profile['style']   = 'wp';
					$profile['class'][] = ' edit-page';
					$profile['class'][] = ' redux-wp-style';
				}

				$this->generate_profiles( array( 'args' => $profile ) );

				if ( ! empty( $profile['roles'] ) ) {
					foreach ( $profile['roles'] as $profiletype ) {
						if ( sanitize_text_field( wp_unslash( $_GET['users'] ) ) !== $profiletype ) { // phpcs:ignore WordPress.Security.NonceVerification
							continue;
						}
					}
				}
			}
		}

		/**
		 * Field Default.
		 *
		 * @param array $field_id ID.
		 *
		 * @return mixed|string
		 */
		public function field_default( array $field_id ) {
			if ( ! isset( $this->parent->options_defaults ) ) {
				$this->parent->options_defaults = $this->parent->default_values();
			}

			if ( ! isset( $this->parent->options ) || empty( $this->parent->options ) ) {
				$this->parent->get_options();
			}

			$this->options = $this->parent->options;

			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'] ] ) {
				return $this->parent->options[ $field_id['id'] ];
			} else {
				if ( empty( $this->options_defaults ) ) {
					$this->default_values(); // fill cache.
				}

				$data = '';
				if ( ! empty( $this->options_defaults ) ) {
					$data = $this->options_defaults[ $field_id['id'] ] ?? '';
				}

				if ( empty( $data ) && isset( $this->parent->options_defaults[ $field_id['id'] ] ) ) {
					$data = $this->parent->options_defaults[ $field_id['id'] ] ?? '';
				}

				return $data;
			}
		}

		/**
		 * Function to get and cache the post meta.
		 *
		 * @param string $id ID.
		 *
		 * @return array
		 */
		private function get_meta( string $id ): array {
			if ( ! isset( $this->meta[ $id ] ) ) {
				$this->meta[ $id ] = array();
				$o_data            = get_post_meta( $id );

				// phpcs:ignore WordPress.NamingConventions.ValidHookName
				$o_data = apply_filters( "redux/users/{$this->parent->args['opt_name']}/get_meta", $o_data );

				if ( ! empty( $o_data ) ) {
					foreach ( $o_data as $key => $value ) {
						if ( count( $value ) === 1 ) {
							$this->meta[ $id ][ $key ] = maybe_unserialize( $value[0] );
						} else {
							$new_value = array_map( 'maybe_unserialize', $value );

							$this->meta[ $id ][ $key ] = $new_value[0];
						}
					}
				}

				if ( isset( $this->meta[ $id ][ $this->parent->args['opt_name'] ] ) ) {
					$data = maybe_unserialize( $this->meta[ $id ][ $this->parent->args['opt_name'] ] );

					foreach ( $data as $key => $value ) {
						$this->meta[ $id ][ $key ] = $value;
						update_post_meta( $id, $key, $value );
					}

					unset( $this->meta[ $id ][ $this->parent->args['opt_name'] ] );

					delete_post_meta( $id, $this->parent->args['opt_name'] );
				}
			}

			return $this->meta[ $id ];
		}

		/**
		 * Get values.
		 *
		 * @param mixed  $the_post Post array.
		 * @param string $meta_key Meta key.
		 * @param string $def_val  Default value.
		 *
		 * @return mixed|string
		 */
		public function get_values( $the_post, string $meta_key = '', string $def_val = '' ) {

			// Override these values if they differ from the admin panel defaults.  ;).
			if ( isset( $the_post->users_role ) && in_array( $the_post->users_role, $this->users_roles, true ) ) {
				if ( isset( $this->users_role_values[ $the_post->users_role ] ) ) {
					$meta = $this->profile_fields[ $the_post->users_role ];
				} else {
					$defaults = array();
					if ( ! empty( $this->profile_fields[ $the_post->users_role ] ) ) {
						foreach ( $this->profile_fields[ $the_post->users_role ] as $key => $null ) {
							if ( isset( $this->options_defaults[ $key ] ) ) {
								$defaults[ $key ] = $this->options_defaults[ $key ];
							}
						}
					}

					$meta = wp_parse_args( $this->get_meta( $the_post->ID ), $defaults );

					$this->profile_fields[ $the_post->users_role ] = $meta;
				}

				if ( ! empty( $meta_key ) ) {
					if ( ! isset( $meta[ $meta_key ] ) ) {
						$meta[ $meta_key ] = $def_val;
					}

					return $meta[ $meta_key ];
				} else {
					return $meta;
				}
			}

			return $def_val;
		}

		/**
		 * Check Edit Visibility.
		 *
		 * @param array $params Array.
		 *
		 * @return bool
		 */
		private function check_edit_visibility( array $params = array() ): bool {
			global $pagenow;

			// Edit page visibility.
			if ( strpos( $pagenow, 'edit-' ) !== false ) {
				if ( isset( $params['fields'] ) ) {
					foreach ( $params['fields'] as $field ) {
						if ( in_array( $field['id'], $this->parent->fields_hidden, true ) ) {
							// Not visible.
						} elseif ( isset( $field['add_visibility'] ) && $field['add_visibility'] ) {
							return true;
						}
					}

					return false;
				}
				if ( isset( $params['add_visibility'] ) && $params['add_visibility'] ) {
					return true;
				}

				return false;
			}

			return true;
		}

		/**
		 * Generate Profiles.
		 *
		 * @param array $metaterm Meta term.
		 */
		private function generate_profiles( array $metaterm ) {
			if ( ! empty( $metaterm['args']['permissions'] ) && ! Redux_Helpers::current_user_can( $metaterm['args']['permissions'] ) ) {
				return;
			}

			if ( isset( $metaterm['args']['style'] ) && in_array( $metaterm['args']['style'], array( 'wp', 'wordpress' ), true ) ) {
				$container_class             = 'redux-wp-style';
				$metaterm['args']['sidebar'] = false;
			} elseif ( isset( $metaterm['args']['sidebar'] ) && ! $metaterm['args']['sidebar'] ) {
				$container_class = 'redux-no-sections';
			} else {
				$container_class = 'redux-has-sections';
			}

			$class = implode( ' ', $metaterm['args']['class'] );
			echo '<div class="' . esc_attr( $class ) . '">';

			$sections = $metaterm['args']['sections'];

			wp_nonce_field( 'redux_users_meta_nonce', 'redux_users_meta_nonce' );

			wp_dequeue_script( 'json-view-js' );

			$sidebar = true;
			if ( count( $sections ) === 1 || ( isset( $metaterm['args']['sidebar'] ) && false === $metaterm['args']['sidebar'] ) ) {
				$sidebar = false; // Show the section dividers or not.
			}
			?>
			<div data-opt-name="<?php echo esc_attr( $this->parent->args['opt_name'] ); ?>"
				class="redux-container <?php echo( esc_attr( $container_class ) ); ?> redux-term redux-box-normal redux-term-normal">
				<div class="redux-notices">
					<?php if ( $sidebar ) { ?>
						<div class="saved_notice admin-notice notice-blue" style="display:none;">
							<?php // phpcs:ignore WordPress.NamingConventions.ValidHookName ?>
							<strong><?php echo esc_html( apply_filters( "redux-imported-text-{$this->parent->args['opt_name']}", esc_html__( 'Settings Imported!', 'redux-framework' ) ) ); ?></strong>
						</div>
						<div class="redux-save-warn notice-yellow">
							<?php // phpcs:ignore WordPress.NamingConventions.ValidHookName ?>
							<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>
						</div>
					<?php } ?>
					<div class="redux-field-errors notice-red">
						<strong> <span></span> <?php echo esc_html__( 'error(s) were found!', 'redux-framework' ); ?>
						</strong>
					</div>
					<div class="redux-field-warnings notice-yellow">
						<strong> <span></span> <?php echo esc_html__( 'warning(s) were found!', 'redux-framework' ); ?>
						</strong>
					</div>
				</div>
				<?php
				echo '<a href="javascript:void(0);" class="expand_options hide" style="display:none;">' . esc_html__( 'Expand', 'redux-framework' ) . '</a>';
				if ( $sidebar ) {
					?>
					<div class="redux-sidebar">
						<ul class="redux-group-menu">
							<?php
							foreach ( $sections as $s_key => $section ) {
								if ( ! empty( $section['permissions'] ) && ! Redux_Helpers::current_user_can( $section['permissions'] ) ) {
									continue;
								}

								// phpcs:ignore WordPress.Security.EscapeOutput
								echo $this->parent->render_class->section_menu( $s_key, $section, '_' . $metaterm['args']['id'], $sections );
							}
							?>
						</ul>
					</div>
				<?php } ?>
				<div class="redux-main">
					<?php

					foreach ( $sections as $s_key => $section ) {
						if ( ! $this->check_edit_visibility( $section ) ) {
							continue;
						}

						if ( ! empty( $section['permissions'] ) && ! Redux_Helpers::current_user_can( $section['permissions'] ) ) {
							continue;
						}

						if ( ! empty( $section['fields'] ) ) {
							if ( isset( $section['args'] ) ) {
								$this->parent->args = wp_parse_args( $section['args'], $this->orig_args );
							} elseif ( $this->parent->args !== $this->orig_args ) {
								$this->parent->args = $this->orig_args;
							}

							$hide             = $sidebar ? '' : ' display-group';
							$section['class'] = isset( $section['class'] ) ? " {$section['class']}" : '';

							// phpcs:ignore WordPress.Security.EscapeOutput
							echo "<div id='{$s_key}_{$metaterm['args']['id']}_section_group' class='redux-group-tab{$section['class']} redux_metaterm_panel$hide'>";

							if ( ! empty( $section['title'] ) ) {
								echo '<h3 class="redux-section-title">' . wp_kses_post( $section['title'] ) . '</h3>';
							}

							if ( ! empty( $section['desc'] ) ) {
								echo '<div class="redux-section-desc">' . wp_kses_post( $section['desc'] ) . '</div>';
							}

							echo '<table class="form-table"><tbody>';

							foreach ( $section['fields'] as $field ) {
								if ( ! $this->check_edit_visibility( $field ) ) {
									continue;
								}

								if ( ! empty( $field['permissions'] ) && ! Redux_Helpers::current_user_can( $field['permissions'] ) ) {
									continue;
								}

								$field['name'] = $this->parent->args['opt_name'] . '[' . $field['id'] . ']';

								$is_hidden = false;
								$ex_style  = '';
								if ( isset( $field['hidden'] ) && $field['hidden'] ) {
									$is_hidden = true;
									$ex_style  = ' style="border-bottom: none;"';
								}

								echo '<tr valign="top"' . $ex_style . '>'; // phpcs:ignore WordPress.Security.EscapeOutput

								$th = $this->parent->render_class->get_header_html( $field );

								if ( $is_hidden ) {
									$str_pos = strpos( $th, 'redux_field_th' );

									if ( $str_pos > - 1 ) {
										$th = str_replace( 'redux_field_th', 'redux_field_th hide', $th );
									}
								}

								if ( $sidebar ) {
									if ( ! ( isset( $metaterm['args']['sections'] ) && count( $metaterm['args']['sections'] ) === 1 && isset( $metaterm['args']['sections'][0]['fields'] ) && count( $metaterm['args']['sections'][0]['fields'] ) === 1 ) && isset( $field['title'] ) ) {
										echo '<th scope="row">';
										if ( ! empty( $th ) ) {
											echo $th; //phpcs:ignore WordPress.Security.EscapeOutput
										}
										echo '</th>';
										echo '<td>';
									}
								} else {
									echo '<td>' . $th; //phpcs:ignore WordPress.Security.EscapeOutput
								}

								if ( 'section' === $field['type'] && true === $field['indent'] ) {
									$field['class']  = $field['class'] ?? '';
									$field['class'] .= 'redux-section-indent-start';
								}

								if ( ! isset( $this->meta[ $field['id'] ] ) ) {
									$this->meta[ $field['id'] ] = '';
								}

								$this->parent->render_class->field_input( $field, $this->meta[ $field['id'] ] );
								echo '</td></tr>';
							}
							echo '</tbody></table>';
							echo '</div>';
						}
					}
					?>
				</div>
				<div class="clear"></div>
			</div>
			<?php

			echo '</div>';
		}

		/**
		 * Save meta profiles
		 * Runs when a post is saved and does an action which to write panel save scripts can hook into.
		 *
		 * @access public
		 *
		 * @param mixed $user_id User ID.
		 *
		 * @return boolean
		 */
		public function user_meta_save( $user_id = 0 ): bool {
			if ( ! current_user_can( 'edit_user', $user_id ) ) {
				return false;
			}

			// Check if our nonce is set.
			if ( ! isset( $_POST['redux_users_meta_nonce'] ) || ! isset( $_POST[ $this->parent->args['opt_name'] ] ) ) {
				return false;
			}

			// Verify that the nonce is valid.
			if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['redux_users_meta_nonce'] ) ), 'redux_users_meta_nonce' ) ) {
				return false;
			}

			$check_user_id = sanitize_text_field( wp_unslash( $_POST['checkuser_id'] ?? 1 ) );

			$user       = sanitize_text_field( wp_unslash( $_GET['user_id'] ?? get_current_user_id() ) );
			$this->meta = Redux_Users::get_user_meta( array( 'user' => $user ) );

			$to_save    = array();
			$to_compare = array();
			$to_delete  = array();

			$field_args = Redux_Users::$fields[ $this->parent->args['opt_name'] ];

			foreach ( $_POST[ $this->parent->args['opt_name'] ] as $key => $value ) { // phpcs:ignore WordPress.Security
				$key = sanitize_text_field( wp_unslash( $key ) );

				if ( ! empty( $field_args[ $key ]['permissions'] ) ) {
					foreach ( (array) $field_args[ $key ]['permissions'] as $pv ) {

						// Do not save anything the user doesn't have permissions for.
						if ( isset( $field_args[ $key ] ) ) {
							if ( user_can( $user_id, $pv ) && user_can( $check_user_id, $pv ) ) {
								break;
							}
						}
					}
				}

				// Have to remove the escaping for array comparison.
				if ( is_array( $value ) ) {
					foreach ( $value as $k => $v ) {
						if ( ! is_array( $v ) ) {
							$value[ $k ] = sanitize_text_field( wp_unslash( $v ) );
						}
					}
				}

				// parent_options.
				if ( isset( $this->options_defaults[ $key ] ) && $value === $this->options_defaults[ $key ] ) {
					$to_delete[ $key ] = $value;
				} elseif ( isset( $this->options_defaults[ $key ] ) ) {
					$to_save[ $key ]    = $value;
					$to_compare[ $key ] = $meta[ $key ] ?? '';
				} else {
					break;
				}
			}

			// phpcs:ignore WordPress.NamingConventions.ValidHookName
			$to_save = apply_filters( "redux/{$this->parent->args['opt_name']}/users/save/before_validate", $to_save, $to_compare, $this->sections );

			$validate = $this->parent->validate_class->validate( $to_save, $to_compare, $this->sections );

			// Validate fields (if needed).
			foreach ( $to_save as $key => $value ) {
				if ( isset( $validate[ $key ] ) && $validate[ $key ] !== $value ) {
					if ( isset( $this->meta[ $key ] ) && $validate[ $key ] === $this->meta[ $key ] ) {
						unset( $to_save[ $key ] );
					} else {
						$to_save[ $key ] = $validate[ $key ];
					}
				}
			}

			if ( ! empty( $this->parent->errors ) || ! empty( $this->parent->warnings ) ) {
				$this->parent->transients['notices'] = ( isset( $this->parent->transients['notices'] ) && is_array( $this->parent->transients['notices'] ) ) ? $this->parent->transients['notices'] : array();

				if ( ! isset( $this->parent->transients['notices']['errors'] ) || $this->parent->transients['notices']['errors'] !== $this->parent->errors ) {
					$this->parent->transients['notices']['errors'] = $this->parent->errors;
					$update_transients                             = true;
				}

				if ( ! isset( $this->parent->transients['notices']['warnings'] ) || $this->parent->transients['notices']['warnings'] !== $this->parent->warnings ) {
					$this->parent->transients['notices']['warnings'] = $this->parent->warnings;
					$update_transients                               = true;
				}

				if ( isset( $update_transients ) ) {
					$this->parent->transients['notices']['override'] = 1;
					set_transient( $this->parent->args['opt_name'] . '-transients-users', $this->parent->transients );
				}
			}

			$check = $this->profile_fields;

			// phpcs:ignore WordPress.NamingConventions.ValidHookName
			$to_save = apply_filters( 'redux/users/save', $to_save, $to_compare, $this->sections );

			foreach ( $to_save as $key => $value ) {
				if ( is_array( $value ) ) {
					$still_update = false;

					foreach ( $value as $vv ) {
						if ( ! empty( $vv ) ) {
							$still_update = true;
						}
					}

					if ( ! $still_update ) {
						continue;
					}
				}

				$prev_value = $this->meta[ $key ] ?? '';

				if ( isset( $check[ $key ] ) ) {
					unset( $check[ $key ] );
				}

				update_user_meta( $user_id, $key, $value, $prev_value );
			}

			foreach ( $to_delete as $key => $value ) {
				if ( isset( $check[ $key ] ) ) {
					unset( $check[ $key ] );
				}

				$prev_value = $this->meta[ $key ] ?? '';
				delete_user_meta( $user_id, $key, $prev_value );
			}
			if ( ! empty( $check ) ) {
				foreach ( $check as $key => $value ) {
					delete_user_meta( $user_id, $key );
				}
			}

			return true;
		}

		/**
		 * Show any stored error messages.
		 *
		 * @access public
		 * @return void
		 */
		public function meta_profiles_show_errors() {
			if ( isset( $this->notices['errors'] ) && ! empty( $this->notices['errors'] ) ) {
				echo '<div id="redux_users_errors" class="error fade">';
				echo '<p><strong><span></span> ' . count( $this->notices['errors'] ) . ' ' . esc_html__( 'error(s) were found!', 'redux-framework' ) . '</strong></p>';
				echo '</div>';
			}

			if ( isset( $this->notices['warnings'] ) && ! empty( $this->notices['warnings'] ) ) {
				echo '<div id="redux_users_warnings" class="error fade" style="border-left-color: #E8E20C;">';
				echo '<p><strong><span></span> ' . count( $this->notices['warnings'] ) . ' ' . esc_html__( 'warnings(s) were found!', 'redux-framework' ) . '</strong></p>';
				echo '</div>';
			}
		}
	}
}
