<?php

use Pods\Data\Map_Field_Values;
use Pods\Whatsit\Object_Field;
use Pods\Whatsit\Field;
use Pods\Whatsit\Pod;
use Pod as Deprecated_Pod;

/**
 * Pods class.
 *
 * @package Pods
 *
 * @property null|string $pod         The Pod name.
 * @property null|string $pod_id      The Pod ID.
 * @property null|string $id          The current item ID (singular mode).
 * @property null|string $rows        The list of rows.
 * @property null|string $row         The current row.
 * @property null|string $pagination  Whether pagination is enabled.
 * @property null|string $page        The current page number.
 * @property null|string $page_var    The query variable used for pagination.
 * @property null|string $search      Whether search is enabled.
 * @property null|string $search_var  The query variable used for search.
 * @property null|string $search_mode The search mode to use.
 * @property null|string $filter_var  The query variable used for filters.
 * @property null|array  $params      The last find() params.
 * @property null|string $sql         The last find() SQL query.
 */
class Pods implements Iterator {

	/**
	 * Whether the Pods object is in a PHP Iterator call.
	 *
	 * @var bool
	 */
	private $iterator = false;

	/**
	 * PodsData object.
	 *
	 * @var PodsData
	 */
	public $data;

	/**
	 * PodsData object for additional calls.
	 *
	 * @var PodsData
	 */
	public $alt_data;

	/**
	 * Override pod item array.
	 *
	 * @var array
	 */
	public $row_override = array();

	/**
	 * Whether to display errors on the screen.
	 *
	 * @var bool
	 */
	public $display_errors = true;

	/**
	 * Current pod information.
	 *
	 * @var \Pods\Whatsit\Pod|array|bool|mixed|null|void
	 */
	public $pod_data;

	/**
	 * Last used ui options for ui() call.
	 *
	 * @var array
	 */
	public $ui = array();

	/**
	 * Page template to use for SEO feature in Pods Pages.
	 *
	 * @var string
	 */
	public $page_template;

	/**
	 * Body classes to use for SEO feature in Pods Pages.
	 *
	 * @var array
	 */
	public $body_classes;

	/**
	 * Meta tags to use for SEO feature in Pods Pages.
	 *
	 * @var array
	 */
	public $meta = array();

	/**
	 * Meta properties to use for SEO feature in Pods Pages.
	 *
	 * @var array
	 */
	public $meta_properties = array();

	/**
	 * Meta custom HTML to use for SEO feature in Pods Pages.
	 *
	 * @var string
	 */
	public $meta_extra = '';

	/**
	 * Pods_Deprecated object.
	 *
	 * @var Pods_Deprecated
	 */
	public $deprecated;

	/**
	 * Constructor - Pods Framework core.
	 *
	 * @since   1.0.0
	 *
	 * @param string $pod    The pod name, leave null to auto-detect from The Loop.
	 * @param mixed  $id     (optional) The ID or slug, to load a single record; Provide array of $params to run 'find';
	 *                       Or leave null to auto-detect from The Loop.
	 * @param bool   $strict (optional) If set to true, we will not attempt to auto-setup the pod based on the object.
	 *
	 * @link    https://docs.pods.io/code/pods/
	 * @license http://www.gnu.org/licenses/gpl-2.0.html
	 */
	public function __construct( $pod = null, $id = null, $strict = false ) {
		if ( null === $pod ) {
			$pod = get_queried_object();
		}

		if ( $pod && ! is_string( $pod ) && ! $pod instanceof Pod ) {
			if ( $pod instanceof WP_Post ) {
				// Post Type Singular.
				if ( null === $id ) {
					$id = $pod->ID;
				}

				$pod = $pod->post_type;
			} elseif ( $pod instanceof WP_Term ) {
				// Term Archive.
				if ( null === $id ) {
					$id = $pod->term_id;
				}

				$pod = $pod->taxonomy;
			} elseif ( $pod instanceof WP_User ) {
				// Author Archive.
				if ( null === $id ) {
					$id = $pod->ID;
				}

				$pod = 'user';
			} elseif ( $pod instanceof WP_Post_Type ) {
				// Post Type Archive.
				$pod = $pod->name;
			} elseif ( $pod instanceof WP_Taxonomy ) {
				// Taxonomy object.
				$pod = $pod->name;
			} else {
				// Unsupported pod object.
				$pod = null;
			}
		}//end if

		$maybe_id = null;

		if ( ! is_array( $id ) && ! is_object( $id ) ) {
			$maybe_id = $id;
		}

		$this->data = pods_data( $pod, $maybe_id, $strict );

		PodsData::$display_errors =& $this->display_errors;

		if ( ! $pod ) {
			return;
		}

		$this->pod_data =& $this->data->pod_data;

		if ( is_array( $id ) || is_object( $id ) ) {
			$this->find( $id );
		}
	}

	/**
	 * Determine whether this Pod object was defined or was built adhoc.
	 *
	 * @since 2.8.18
	 *
	 * @return bool Whether this Pod object was defined or was built adhoc.
	 */
	public function is_defined() {
		return $this->pod_data && empty( $this->pod_data['adhoc'] );
	}

	/**
	 * Determine whether this Pod object is valid or not.
	 *
	 * @since 2.8.18
	 *
	 * @return bool Whether this Pod object is valid or not.
	 */
	public function is_valid() {
		if ( empty( $this->pod_data ) ) {
			return false;
		}

		if ( $this->iterator ) {
			return isset( $this->data->rows[ $this->data->row_number ] );
		}

		return true;
	}

	/**
	 * Whether this Pod object is valid or not
	 *
	 * @return bool
	 *
	 * @since 2.0.0
	 *
	 * @see Pods::is_valid()
	 */
	public function valid(): bool {
		return $this->is_valid();
	}

	/**
	 * Check if in Iterator mode
	 *
	 * @return bool
	 *
	 * @since 2.3.4
	 *
	 * @link  http://www.php.net/manual/en/class.iterator.php
	 */
	public function is_iterator() {
		return $this->iterator;
	}

	/**
	 * Turn off Iterator mode to off
	 *
	 * @return void
	 *
	 * @since 2.3.4
	 *
	 * @link  http://www.php.net/manual/en/class.iterator.php
	 */
	public function stop_iterator() {
		$this->iterator = false;
	}

	/**
	 * Rewind Iterator
	 *
	 * @since 2.3.4
	 *
	 * @link  http://www.php.net/manual/en/class.iterator.php
	 */
	public function rewind() : void {
		if ( ! $this->iterator ) {
			$this->iterator = true;

			$this->data->row_number = 0;
		}
	}

	/**
	 * Get current Iterator row
	 *
	 * @return mixed|boolean
	 *
	 * @since 2.3.4
	 *
	 * @link  http://www.php.net/manual/en/class.iterator.php
	 */
	#[\ReturnTypeWillChange]
	public function current() {
		if ( $this->iterator && $this->fetch() ) {
			return $this;
		}

		return false;
	}

	/**
	 * Get current Iterator key
	 *
	 * @return int
	 *
	 * @since 2.3.4
	 *
	 * @link  http://www.php.net/manual/en/class.iterator.php
	 */
	#[\ReturnTypeWillChange]
	public function key() {
		return $this->data->row_number;
	}

	/**
	 * Move onto the next Iterator row
	 *
	 * @return void
	 *
	 * @since 2.3.4
	 *
	 * @link  http://www.php.net/manual/en/class.iterator.php
	 */
	public function next(): void {
		$this->data->row_number ++;
	}

	/**
	 * Whether a Pod item exists or not when using fetch() or construct with an ID or slug.
	 *
	 * @return bool
	 *
	 * @since 2.0.0
	 */
	public function exists() {
		return ! empty( $this->data->row );
	}

	/**
	 * Whether the find() resulted in any rows returned.
	 *
	 * @return bool
	 *
	 * @since 2.9.12
	 */
	public function has_rows() {
		return ! empty( $this->data->rows );
	}

	/**
	 * Return an array of all rows returned from a find() call.
	 *
	 * Most of the time, you will want to loop through data using fetch()
	 * instead of using this function.
	 *
	 * @return array|bool An array of all rows returned from a find() call, or false if no items returned
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/data/
	 */
	public function data() {

		do_action( 'pods_pods_data', $this );

		if ( empty( $this->data->rows ) ) {
			return false;
		}

		return (array) $this->data->rows;
	}

	/**
	 * Return a field input for a specific field
	 *
	 * @param string|array      $field      Field name or Field data array.
	 * @param string|array|null $input_name Input field name to use (overrides default name).
	 * @param mixed             $value      Current value to use.
	 *
	 * @return string Field Input HTML
	 *
	 * @since 2.3.10
	 */
	public function input( $field, $input_name = null, $value = '__null' ) {
		$is_field_object = $field instanceof Field;

		if ( is_array( $field ) || $is_field_object ) {
			// Field data override.
			$field_data = $field;

			$field = pods_v( 'name', $field );
		} else {
			// Get field data from field name.
			$field_data = $this->fields( $field );
		}

		if ( ! empty( $field_data ) ) {
			$field_type = pods_v( 'type', $field_data );

			if ( empty( $input_name ) ) {
				$input_name = $field;
			}

			if ( '__null' === $value ) {
				$value = $this->field( array(
					'name'    => $field,
					'in_form' => true,
				) );
			}

			return PodsForm::field( $input_name, $value, $field_type, $field_data, $this, $this->id() );
		}

		return '';

	}

	/**
	 * Return field array from a Pod, a field's data, or a field option
	 *
	 * @param null $field  Field name.
	 * @param null $option Option name.
	 *
	 * @return array|\Pods\Whatsit\Field|mixed|null
	 *
	 * @since 2.0.0
	 */
	public function fields( $field_name = null, $option = null ) {

		$field_data = null;

		if ( empty( $this->pod_data ) ) {
			return null;
		}

		if ( empty( $field_name ) ) {
			// Return all fields.
			$field_data = pods_config_get_all_fields( $this->pod_data );
		} else {
			$field = pods_config_get_field_from_all_fields( $field_name, $this->pod_data );

			if ( empty( $field ) || empty( $option ) ) {
				$field_data = $field;
			} elseif ( 'data' === $option && $field->is_relationship() ) {
				// Get a list of available items from a relationship field.
				$field_data = PodsForm::field_method( 'pick', 'get_field_data', $field );
			} elseif ( isset( $field[ $option ] ) ) {
				// Return option.
				$field_data = $field[ $option ];
			}//end if
		}//end if

		/**
		 * Modify the field data before returning
		 *
		 * @since unknown
		 *
		 * @param array|\Pods\Whatsit\Field|mixed $field_data The data to be returned for the field / option.
		 * @param string|null                     $field_name The specific field that data is being return for, if set when method is called or null.
		 * @param string|null                     $option     Value of option param when method was called. Can be used to get a list of available items from a relationship field.
		 * @param Pods|object                     $obj        The current Pods class instance.
		 */
		return apply_filters( 'pods_pods_fields', $field_data, $field_name, $option, $this );

	}

	/**
	 * Return row array for an item
	 *
	 * @return array|false
	 *
	 * @since 2.0.0
	 */
	public function row() {
		do_action( 'pods_pods_row', $this );

		if ( ! is_array( $this->data->row ) ) {
			return false;
		}

		return (array) $this->data->row;
	}

	/**
	 * Return the output for a field. If you want the raw value for use in PHP for custom manipulation,
	 * you will want to use field() instead. This function will automatically convert arrays into a
	 * list of text such as "Rick, John, and Gary"
	 *
	 * @param string|array|object  $name   The field name, or an associative array of parameters.
	 * @param boolean|array|object $single (optional) For tableless fields, to return an array or the first.
	 *
	 * @return string|null|false The output from the field, null if the field doesn't exist, false if no value returned
	 *                           for tableless fields
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/display/
	 */
	public function display( $name, $single = null ) {
		$defaults = array(
			'name'          => $name,
			'single'        => $single,
			'display'       => true,
			'serial_params' => null,
		);

		if ( is_array( $name ) || is_object( $name ) ) {
			$defaults['name'] = null;

			$params = (object) array_merge( $defaults, (array) $name );
		} elseif ( is_array( $single ) || is_object( $single ) ) {
			$defaults['single'] = null;

			$params = (object) array_merge( $defaults, (array) $single );
		} else {
			$params = $defaults;
		}

		$params = (object) $params;

		$value = $this->field( $params );

		if ( is_array( $value ) ) {
			$fields = pods_config_get_all_fields( $this->pod_data );

			$serial_params = array(
				'field'  => $params->name,
				'fields' => $fields,
			);

			if ( ! empty( $params->serial_params ) && is_array( $params->serial_params ) ) {
				$serial_params = array_merge( $serial_params, $params->serial_params );
			}

			$value = pods_serial_comma( $value, $serial_params );
		}

		return $value;
	}

	/**
	 * Return the raw output for a field If you want the raw value for use in PHP for custom manipulation,
	 * you will want to use field() instead. This function will automatically convert arrays into a
	 * list of text such as "Rick, John, and Gary"
	 *
	 * @param string|array|object  $name   The field name, or an associative array of parameters.
	 * @param boolean|array|object $single (optional) For tableless fields, to return an array or the first.
	 *
	 * @return string|null|false The output from the field, null if the field doesn't exist, false if no value returned
	 *                           for tableless fields
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/display/
	 */
	public function raw( $name, $single = null ) {
		$defaults = array(
			'name'   => $name,
			'single' => $single,
			'raw'    => true,
		);

		if ( is_array( $name ) || is_object( $name ) ) {
			$defaults['name'] = null;

			$params = (object) array_merge( $defaults, (array) $name );
		} elseif ( is_array( $single ) || is_object( $single ) ) {
			$defaults['single'] = null;

			$params = (object) array_merge( $defaults, (array) $single );
		} else {
			$params = (object) $defaults;
		}

		return $this->field( $params );
	}

	/**
	 * Return the value for a field.
	 *
	 * If you are getting a field for output in a theme, most of the time you will want to use display() instead.
	 *
	 * This function will return arrays for relationship and file fields.
	 *
	 * @param string|array|object  $name   The field name, or an associative array of parameters.
	 * @param boolean|array|object $single For tableless fields, to return the whole array or the just the first item,
	 *                                     or an associative array of parameters.
	 * @param boolean|array|object $raw    Whether to return the raw value, or to run through the field type's display
	 *                                     method, or an associative array of parameters.
	 *
	 * @return mixed|null Value returned depends on the field type, null if the field doesn't exist, false if no value
	 *                    returned for tableless fields.
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/field/
	 */
	public function field( $name, $single = null, $raw = false ) {
		$defaults = [
			'name'                         => $name,
			'orderby'                      => null,
			'single'                       => $single,
			'params'                       => null,
			'in_form'                      => false,
			'raw'                          => $raw,
			'raw_display'                  => false,
			'display'                      => false,
			'display_process_individually' => false,
			'get_meta'                     => false,
			'output'                       => null,
			'pods_callback'                => 'pods',
			'deprecated'                   => false,
			'keyed'                        => false,
			'bypass_map_field_values'      => false,
			// extra data to send to field handlers.
			'args'                         => [],
		];

		if ( is_object( $name ) ) {
			$name = get_object_vars( $name );
		}

		if ( is_object( $single ) ) {
			$single = get_object_vars( $single );
		}

		if ( is_object( $raw ) ) {
			$raw = get_object_vars( $raw );
		}

		if ( is_array( $name ) ) {
			$defaults['name'] = null;

			$params = (object) array_merge( $defaults, (array) $name );
		} elseif ( is_array( $single ) ) {
			$defaults['single'] = null;

			$params = (object) array_merge( $defaults, (array) $single );
		} elseif ( is_array( $raw ) ) {
			$defaults['raw'] = false;

			$params = (object) array_merge( $defaults, (array) $raw );
		} else {
			$params = (object) $defaults;
		}//end if

		$params->original_output = $params->output;

		if ( $params->in_form ) {
			$params->output = 'ids';
		} elseif ( null === $params->output ) {
			/**
			 * Override the way related fields are output
			 *
			 * @param string       $output How to output related fields. Default is 'arrays'. Options: ids|names|objects|arrays|pods|find
			 * @param array|object $row    Current row being outputted.
			 * @param object       $params Params array passed to field().
			 * @param Pods         $obj    Current Pods object.
			 */
			$params->output = apply_filters( 'pods_pods_field_related_output_type', 'arrays', $this->data->row, $params, $this );
		}

		if ( in_array( $params->output, array( 'id', 'name', 'object', 'array', 'pod' ), true ) ) {
			$params->output .= 's';
		}

		if ( empty( $params->pods_callback ) || ! is_callable( $params->pods_callback ) ) {
			$params->pods_callback = 'pods';
		}

		// Support old $orderby variable.
		if ( null !== $params->single && is_string( $params->single ) && empty( $params->orderby ) ) {
			if ( pods_is_debug_display() ) {
				pods_deprecated( 'Pods::field', '2.0', 'Use $params[ \'orderby\' ] instead' );
			}

			$params->orderby = $params->single;
			$params->single  = false;
		}

		if ( null !== $params->single ) {
			$params->single = (boolean) $params->single;
		}

		$params->name = trim( (string) $params->name );

		if ( is_array( $params->name ) || '' === $params->name ) {
			return null;
		}

		$params->full_name = $params->name;

		$value = null;

		if ( isset( $this->row_override[ $params->name ] ) ) {
			$value = $this->row_override[ $params->name ];
		}

		if ( false === $this->row() ) {
			if ( false !== $this->data() ) {
				$this->fetch();
			} else {
				return $value;
			}
		}

		if ( $this->data->field_id === $params->name ) {
			if ( isset( $this->data->row[ $params->name ] ) ) {
				return $this->data->row[ $params->name ];
				// @codingStandardsIgnoreLine.
			} elseif ( null !== $value ) {
				return $value;
			}

			return 0;
		}

		$tableless_field_types = PodsForm::tableless_field_types();

		$params->traverse = array();

		$permalink_fields = array(
			'_link',
			'detail_url',
			'permalink',
			'the_permalink',
		);

		$wp_object_types = array(
			'post_type',
			'taxonomy',
			'media',
			'user',
			'comment',
		);

		/** @var string $pod_type The pod object type. */
		$pod_type = pods_v( 'type', $this->pod_data, '' );

		$use_meta_fallback = in_array( $pod_type, $wp_object_types, true );

		/**
		 * Allow hooking in to support getting meta using the meta fallback.
		 *
		 * @since 2.8.0
		 *
		 * @param bool   $use_meta_fallback Whether to support getting meta using the meta fallback.
		 * @param string $object_type       The object type.
		 */
		$use_meta_fallback = apply_filters( 'pods_field_wp_object_use_meta_fallback', $use_meta_fallback, $pod_type );

		if ( in_array( $params->name, $permalink_fields, true ) ) {
			if (
				is_string( $this->detail_page )
				&& 0 < strlen( $this->detail_page )
				&& false === strpos( $params->name, 'permalink' )
			) {
				// ACT Pods. Prevent tag loop by not parsing `permalink`.
				$value = get_home_url() . '/' . $this->do_magic_tags( $this->detail_page );
			} else {
				switch ( $pod_type ) {
					case 'post_type':
					case 'media':
						$value = get_permalink( $this->id() );
						break;
					case 'taxonomy':
						$value = get_term_link( $this->id(), $this->pod_data['name'] );
						break;
					case 'user':
						$value = get_author_posts_url( $this->id() );
						break;
					case 'comment':
						$value = get_comment_link( $this->id() );
						break;
				}
			}
		}

		/**
		 * @var bool   $is_field_set       Is the field found.
		 * @var bool   $is_relationship_field Is it a tableless field.
		 * @var string $field_source       Regular field or object field.
		 * @var array  $field_data         The field data.
		 * @var string $field_type         The field type.
		 * @var array  $field_options      The field options.
		 * @var array  $traverse_fields    All the traversal field names.
		 * @var bool   $is_traversal       Is it a traversal field request.
		 * @var string $first_field        The name of the fieds without the traversal names from $params->name.
		 * @var array  $last_field_data    The field data used in traversal loop.
		 */

		$is_field_set          = false;
		$field_source          = '';
		$traverse_fields       = explode( '.', $params->name );
		$is_traversal          = 1 < count( $traverse_fields );
		$first_field           = $traverse_fields[0];
		$last_field_data       = null;

		// Get the first field name data.
		$field_data = $this->fields( $first_field );

		// Ensure the field name is using the correct name and not the alias.
		if ( $field_data ) {
			$first_field = $field_data['name'];

			if ( ! $is_traversal ) {
				$params->name = $first_field;
			}
		}

		if ( ! $field_data && ! $is_traversal ) {
			// Get the full field name data.
			$field_data = $this->fields( $params->name );
		}

		if ( is_array( $field_data ) ) {
			$field_data = pods_config_for_field( $field_data, $this->pod_data );
		}

		$override_object_field = false;

		if ( $field_data instanceof \Pods\Whatsit\Object_Field ) {
			$field_source = 'object_field';
			$is_field_set = true;
		} elseif ( $field_data instanceof \Pods\Whatsit\Field ) {
			$field_source = 'field';
			$is_field_set = true;

			$override_object_field = (bool) $field_data->get_arg( 'override_object_field', false );
		}

		// Store field info.
		$field_type                   = pods_v( 'type', $field_data, 'text' );
		$field_options                = $field_data;
		$is_valid_field               = $field_data instanceof Field;
		$is_relationship_field        = $is_valid_field && $field_data->is_relationship();
		$is_simple_relationship_field = $is_valid_field && $field_data->is_simple_relationship();
		$is_repeatable_field          = $is_valid_field && $field_data->is_repeatable();

		// Handle output specific handling.
		if ( $is_relationship_field ) {
			// Simple fields have no other output options.
			if ( $is_simple_relationship_field ) {
				$params->output = 'arrays';
			}

			// Handle relationships with ID output (only if not traversing).
			$pick_output = pods_v( 'pick_output', $field_data, null, true );

			if ( null !== $pick_output && ! $is_traversal && null === $params->original_output ) {
				$params->output = $pick_output;
			}
		}

		$is_relationship_field_and_not_simple = (
			! $is_traversal
			&& $field_data
			&& (
				! $field_data instanceof Object_Field
				|| 'comments' === $field_data->get_name()
			)
			&& $is_relationship_field
			&& ! $is_simple_relationship_field
		);

		$is_object_expanded_relationship_field = (
			$is_relationship_field
			&&  $field_data instanceof Object_Field
			&& 'comments' === $field_data->get_name()
		);

		// If a relationship is returned from the table as an ID but the parameter is not traversal, we need to run traversal logic.
		if ( $is_relationship_field_and_not_simple && isset( $this->data->row[ $params->name ] ) && ! is_array( $this->data->row[ $params->name ] ) ) {
			unset( $this->data->row[ $params->name ] );
		}

		// Enforce output type for tableless fields in forms.
		if ( empty( $value ) && $is_relationship_field ) {
			$params->raw = true;

			$value = false;

			$row_key = '_' . $params->output . '_' . $params->name;

			if ( 'arrays' !== $params->output && isset( $this->data->row[ $row_key ] ) ) {
				$value = $this->data->row[ $row_key ];
			} elseif ( 'arrays' === $params->output && isset( $this->data->row[ $params->name ] ) ) {
				$value = $this->data->row[ $params->name ];
			}

			if (
				false !== $value
				&& ! is_array( $value )
				&& $is_simple_relationship_field
			) {
				$value = PodsForm::field_method( 'pick', 'simple_value', $params->name, $value, $field_data, $this->pod_data, $this->id(), true );
			}
		}

		if (
			! $override_object_field
			&& empty( $value )
			&& isset( $this->data->row[ $params->name ] )
			&& ( ! $is_relationship_field || 'arrays' === $params->output )
		) {
			if ( empty( $field_data ) || in_array( $field_type, [
					'boolean',
					'number',
					'currency',
				], true ) ) {
				$params->raw = true;
			}

			if ( null === $params->single ) {
				if ( ! $is_relationship_field ) {
					$params->single = true;
				} else {
					$params->single = false;
				}
			}

			$value = $this->data->row[ $params->name ];
		} elseif ( empty( $value ) ) {
			$object_field_found = false;

			if (
				'object_field' === $field_source
				&& ! $is_traversal
				&& (
					! $is_object_expanded_relationship_field
					|| 'arrays' === $params->output
				)
			) {
				$object_field_found = true;

				if ( isset( $this->data->row[ $first_field ] ) ) {
					$value = $this->data->row[ $first_field ];
				} elseif ( $is_relationship_field ) {
					$object_field_found = false;
				} else {
					return null;
				}
			} elseif (
				! $params->bypass_map_field_values
				&& ! $params->in_form
			) {
				// Handle custom/supported value mappings.
				$map_field_values = pods_container( Map_Field_Values::class );

				$value = $map_field_values->map_value( $first_field, $traverse_fields, $is_field_set ? $field_data : null, $this, $params );

				$object_field_found = null !== $value;
			}

			// Continue regular field parsing.
			if ( false === $object_field_found ) {
				$params->traverse = array( $params->name );

				if ( false !== strpos( $params->name, '.' ) ) {
					$params->traverse = explode( '.', $params->name );

					$params->name = $params->traverse[0];
				}

				$simple = false;

				if ( $field_data ) {
					/**
					 * Modify value returned by field() after its retrieved, but before its validated or formatted
					 *
					 * Filter name is set dynamically with name of field: "pods_pods_field_{field_type}"
					 *
					 * @since unknown
					 *
					 * @param array|string|null $value      Value retrieved.
					 * @param array             $field_data Current field object.
					 * @param array|object      $row        Current row being outputted.
					 * @param object            $params     Params array passed to field().
					 * @param Pods              $obj        Current Pods object.
					 */
					$v = apply_filters( "pods_pods_field_{$field_type}", null, $field_data, $this->row(), $params, $this );

					if ( null !== $v ) {
						return $v;
					}

					if ( 'meta' === $this->pod_data['storage'] && ! $is_relationship_field ) {
						$simple = true;
					}

					if ( $is_relationship_field ) {
						$params->raw = true;

						if ( $is_simple_relationship_field ) {
							$simple         = true;
							$params->single = true;
						}
					} elseif ( in_array( $field_type, [
						'boolean',
						'number',
						'currency',
					], true ) ) {
						$params->raw = true;
					}
				}

				if ( ! $is_traversal && ( $simple || $is_repeatable_field || ! $is_field_set || ! $is_relationship_field ) ) {
					if ( null === $params->single ) {
						if ( $is_field_set && ! $is_relationship_field && ! $is_repeatable_field ) {
							$params->single = true;
						} else {
							$params->single = false;
						}
					}

					$no_conflict = pods_no_conflict_check( $pod_type );

					if ( ! $no_conflict ) {
						// Temporarily enable no conflict.
						pods_no_conflict_on( $pod_type );
					}

					if ( $use_meta_fallback ) {
						$metadata_type = $pod_type;

						if ( in_array( $metadata_type, array( 'post_type', 'media' ), true ) ) {
							$metadata_type = 'post';
						} elseif ( 'taxonomy' === $metadata_type ) {
							$metadata_type = 'term';
						}

						/**
						 * Modify the object ID for getting metadata.
						 * Added for i18n integration classes.
						 *
						 * @since 2.8.0
						 *
						 * @param int    $id            The object ID.
						 * @param string $metadata_type The object metadata type.
						 * @param object $params        Field params.
						 * @param Pods   $obj           Pods object.
						 */
						$id = apply_filters( 'pods_pods_field_get_metadata_object_id', $this->id(), $metadata_type, $params, $this );

						$value = get_metadata( $metadata_type, $id, $params->name, $params->single );

						$single_multi = 'single';

						if ( $is_field_set ) {
							if ( $is_repeatable_field ) {
								$single_multi = 'multi';
							} else {
								$single_multi = pods_v( $field_type . '_format_type', $field_data, $single_multi );
							}
						}

						if ( ( $simple || $is_repeatable_field ) && ! is_array( $value ) && 'single' !== $single_multi ) {
							$value = get_metadata( $metadata_type, $id, $params->name );
						}
					} elseif ( 'settings' === $pod_type ) {
						$value = get_option( $this->pod_data['name'] . '_' . $params->name, null );
					}//end if

					// Handle Simple Relationships.
					if ( $simple ) {
						if ( null === $params->single ) {
							$params->single = false;
						}

						$value = PodsForm::field_method( 'pick', 'simple_value', $params->name, $value, $field_data, $this->pod_data, $this->id(), true );
					}

					if ( ! $no_conflict ) {
						// Revert temporarily no conflict mode.
						pods_no_conflict_off( $pod_type );
					}
				} else {
					// Dot-traversal.
					$pod = $this->pod_data['name'];
					$ids = [];

					if ( 'settings' === $pod_type && $this->pod_data['id'] ) {
						$ids[] = $this->pod_data['id'];
					} else {
						$ids[] = $this->id();
					}

					$all_fields = [];

					$lookup = $params->traverse;

					// Get fields matching traversal names.
					if ( ! empty( $lookup ) ) {
						if ( 1 === count( $params->traverse ) && $params->name === $params->traverse[0] && $field_data && $field_data['name'] === $params->traverse[0] ) {
							$fields = array(
								$field_data,
							);
						} else {
							$fields = $this->data->api->traverse_fields( array(
								'pod'    => $pod,
								'expand' => $lookup,
								'type'   => $tableless_field_types,
							) );
						}

						if ( ! empty( $fields ) ) {
							foreach ( $fields as $field ) {
								if ( ! empty( $field ) ) {
									if ( ! isset( $all_fields[ $field['pod'] ] ) ) {
										$all_fields[ $field['pod'] ] = array();
									}

									$all_fields[ $field['pod'] ][ $field['name'] ] = $field;
								}
							}
						}
					}//end if

					$last_type                         = '';
					$last_object                       = '';
					$last_pick_val                     = '';
					$last_options                      = [];
					$last_object_options               = [];
					$last_is_relationship_field        = false;
					$last_is_simple_relationship_field = false;
					$last_is_repeatable_field          = false;

					$limit = $field_data ? $field_data->get_limit() : 0;

					// Loop through each traversal level.
					foreach ( $params->traverse as $key => $field ) {
						$last_loop = false;

						if ( count( $params->traverse ) <= ( $key + 1 ) ) {
							$last_loop = true;
						}

						$field_exists = isset( $all_fields[ $pod ][ $field ] );

						$current_field       = null;
						$simple              = false;

						if ( $field_exists ) {
							/** @var \Pods\Whatsit\Field $current_field */
							$current_field = $all_fields[ $pod ][ $field ];

							if ( $current_field->is_relationship() ) {
								$last_options = $current_field;

								if ( $current_field->is_simple_relationship() ) {
									$simple = true;
								}
							}
						} elseif ( $last_options ) {
							$current_related_pod = $last_options->get_related_object();

							if ( $current_related_pod ) {
								$current_related_pod_field = $current_related_pod->get_field( $field );

								if ( $current_related_pod_field ) {
									$current_field = $current_related_pod_field;
								}
							}
						}

						// Tableless handler.
						if ( $field_exists && ( ! $simple || ! in_array( $current_field['type'], array(
							'pick',
							'taxonomy',
							'comment',
						), true ) ) ) {
							$type        = $current_field['type'];
							$pick_object = $current_field->get_related_object_type();
							$pick_val    = $current_field->get_related_object_name();

							$last_limit = 0;

							if ( $current_field->is_relationship() ) {
								$last_limit = $current_field->get_limit();
							}

							$last_type           = $type;
							$last_object         = $pick_object;
							$last_pick_val       = $pick_val;
							$last_options        = $current_field;
							$last_object_options = $current_field;

							// Temporary hack until there's some better handling here.
							$last_limit *= count( $ids );

							// Override the $limit in case $limit was a single select, there are multiple values to return now.
							$limit = $last_limit;

							// Get related IDs.
							$lookup_related_items_field = ! empty( $current_field['id'] ) ? $current_field['id'] : $current_field['name'];

							$ids = $this->data->api->lookup_related_items( $lookup_related_items_field, $current_field->get_parent_id(), $ids, $current_field );

							// No items found.
							if ( empty( $ids ) ) {
								return false;
							}

							if ( 0 < $last_limit ) {
								// @todo This should return array() if not $params->single.
								$ids = array_slice( $ids, 0, $last_limit );
							}

							// Get $pod if related to a Pod.
							if ( ! empty( $pick_object ) && ( ! empty( $pick_val ) || in_array( $pick_object, array(
								'user',
								'media',
								'comment',
							), true ) ) ) {
								if ( 'pod' === $pick_object ) {
									$pod = $pick_val;
								} else {
									$check = $current_field->get_table_info();

									if ( $check && ! empty( $check['pod'] ) ) {
										$pod = $check['pod']['name'];
									}
								}
							}
						} else {
							// Assume last iteration.
							$last_loop = true;

							if ( 0 === $key ) {
								// This is also the first loop. Assume metadata or options to traverse into.
								$last_object   = $this->pod_data['object_type'];
								$last_pick_val = $this->pod_data['name'];
							}
						}//end if

						if ( $last_loop ) {
							$object_type = $last_object;
							$object      = $last_pick_val;

							if ( in_array( $last_type, PodsForm::file_field_types(), true ) ) {
								$object_type = 'media';
								$object      = 'media';
							}

							$data  = array();
							$table = array();

							if ( $last_options ) {
								if ( $simple ) {
									$table = $last_object_options->get_table_info();
								} else {
									$table = $last_options->get_table_info();
								}

								if ( 'object' === $last_options->get_arg( 'pick_data_storage' ) ) {
									// Handle relationships with ID output when traversing.
									$last_pick_output = pods_v( 'pick_output', $last_options, null, true );

									if (
										null !== $last_pick_output
										&& null === $params->original_output
										&& $current_field === $last_options
									) {
										$params->output = $last_pick_output;
									}
								}

								$last_is_valid_field               = $current_field instanceof Field;
								$last_is_relationship_field        = $last_is_valid_field && $current_field->is_relationship();
								$last_is_simple_relationship_field = $last_is_valid_field && $current_field->is_simple_relationship();
								$last_is_repeatable_field          = $last_is_valid_field && $current_field->is_repeatable();
							}

							$join  = array();
							$where = array();

							if ( ! empty( $table['join'] ) ) {
								$join = (array) $table['join'];
							}

							if ( $table && ( ! empty( $ids ) || ! empty( $table['where'] ) ) ) {
								foreach ( $ids as $id ) {
									$where[ $id ] = '`t`.`' . $table['field_id'] . '` = ' . (int) $id;
								}

								if ( ! empty( $where ) ) {
									$where = array( implode( ' OR ', $where ) );
								}

								if ( ! empty( $table['where'] ) ) {
									// @codingStandardsIgnoreLine.
									$where = array_merge( $where, array_values( (array) $table['where'] ) );
								}
							}

							/**
							 * Related object.
							 *
							 * @var $related_obj Pods|false
							 */
							$related_obj = false;

							// Check if we can return the full object/array or if we need to traverse into it.
							$is_field_output_full = false;

							if (
								false !== $field_exists
								&& (
									$last_is_relationship_field
									&& ! $simple
								)
							) {
								$is_field_output_full = true;
							}

							/** @var Pods $related_obj */
							if ( 'pod' === $object_type ) {
								$related_obj = call_user_func( $params->pods_callback, $object, null, false );
							} elseif ( ! empty( $table['pod'] ) ) {
								$related_obj = call_user_func( $params->pods_callback, $table['pod']['name'], null, false );
							}

							if ( $table && ( $related_obj || ! empty( $table['table'] ) ) ) {
								$sql = array(
									'select'     => '*, `t`.`' . $table['field_id'] . '` AS `pod_item_id`',
									'table'      => $table['table'],
									'join'       => $join,
									'where'      => $where,
									'orderby'    => $params->orderby,
									'pagination' => false,
									'search'     => false,
									'limit'      => - 1,
									'expires'    => 180,
									// @todo This could potentially cause issues if someone changes the data within this time and persistent storage is used.
								);

								if ( ! empty( $table['where_default'] ) ) {
									$sql['where_default'] = $table['where_default'];
								}

								// Output types.
								if ( in_array( $params->output, array( 'ids', 'objects', 'pods' ), true ) ) {
									$sql['select']  = '`t`.`' . $table['field_id'] . '` AS `pod_item_id`';
									$sql['orderby'] = [];
								} elseif ( 'names' === $params->output && ! empty( $table['field_index'] ) ) {
									$sql['select']  = '`t`.`' . $table['field_index'] . '` AS `pod_item_index`, `t`.`' . $table['field_id'] . '` AS `pod_item_id`';
									$sql['orderby'] = [];
								}

								if ( ! empty( $params->params ) && is_array( $params->params ) ) {
									$where = $sql['where'];

									// @codingStandardsIgnoreLine.
									$sql = array_merge( $sql, $params->params );

									if ( isset( $params->params['where'] ) ) {
										// @codingStandardsIgnoreLine.
										$sql['where'] = array_merge( (array) $where, (array) $params->params['where'] );
									}
								}

								$item_data = array();

								// Debug purposes
								if ( 1 == pods_v( 'pods_debug_params_all', 'get', 0 ) && pods_is_admin( array( 'pods' ) ) ) {
									pods_debug( __METHOD__ . ':' . __LINE__ );
									pods_debug( $sql );
								}

								if ( ! $related_obj || ! $related_obj->valid() ) {
									if ( ! is_object( $this->alt_data ) ) {
										$this->alt_data = pods_data();
									}

									pods_debug_log_data( [ 'field_name' => $params->name, 'sql' => $sql ], 'related-field-params', __METHOD__, __LINE__ );

									$item_data = $this->alt_data->select( $sql );
								} else {
									// Support 'find' output ordering.
									if ( $ids && 'find' === $params->output && $is_field_output_full && empty( $sql['orderby'] ) ) {
										// Handle default orderby for ordering by the IDs.
										$order_ids = implode( ', ', array_map( 'absint', $ids ) );

										$sql['orderby'] = 'FIELD( `t`.`' . $table['field_id'] . '`, ' . $order_ids . ' )';
									}

									pods_debug_log_data( [ 'field_name' => $params->name, 'sql' => $sql ], 'related-field-params', __METHOD__, __LINE__ );

									$related_obj->find( $sql );

									// Support 'find' output.
									if ( 'find' === $params->output && $is_field_output_full ) {
										$data = $related_obj;

										$is_field_output_full = true;
									} else {
										$item_data = $related_obj->data();
									}
								}//end if

								$items = array();

								if ( ! empty( $item_data ) ) {
									foreach ( $item_data as $item ) {
										if ( is_array( $item ) ) {
											$item = (object) $item;
										}

										if ( empty( $item->pod_item_id ) ) {
											continue;
										}

										// Bypass sensitive fields.
										$item = pods_access_bleep_data( $item );

										// Get Item ID.
										$item_id = $item->pod_item_id;

										// Output types.
										if ( 'ids' === $params->output ) {
											$item = (int) $item_id;
										} elseif ( 'names' === $params->output && ! empty( $table['field_index'] ) ) {
											$item = $item->pod_item_index;
										} elseif ( 'objects' === $params->output ) {
											if ( in_array( $object_type, array( 'post_type', 'media' ), true ) ) {
												$item = get_post( $item_id );

												if ( ! empty( $item ) ) {
													$item = pods_access_bleep_data( $item );
												}
											} elseif ( 'taxonomy' === $object_type ) {
												$item = get_term( $item_id, $object );
											} elseif ( 'user' === $object_type ) {
												$item = get_userdata( $item_id );

												if ( ! empty( $item ) ) {
													// Get other vars.
													$roles   = $item->roles;
													$caps    = $item->caps;
													$allcaps = $item->allcaps;

													$item = $item->data;

													// Set other vars.
													$item->roles   = $roles;
													$item->caps    = $caps;
													$item->allcaps = $allcaps;

													$item = pods_access_bleep_data( $item );
												}
											} elseif ( 'comment' === $object_type ) {
												$item = get_comment( $item_id );
											} else {
												$item = (object) $item;
											}//end if
										} elseif ( 'pods' === $params->output ) {
											/** @var Pods $item */
											if ( in_array( $object_type, array( 'user', 'media' ), true ) ) {
												$item = call_user_func( $params->pods_callback, $object_type, (int) $item_id );
											} else {
												$item = call_user_func( $params->pods_callback, $object, (int) $item_id );
											}

											if ( ! $item || ! $item->valid() ) {
												// Related pod does not exist.
												$item = false;
											}
										} else {
											// arrays.
											$item = get_object_vars( (object) $item );
										}//end if

										// Pass item data into $data.
										$items[ $item_id ] = $item;
									}//end foreach

									// Cleanup.
									unset( $item_data );

									// Return all of the data in the order expected.
									if ( empty( $params->orderby ) ) {
										foreach ( $ids as $id ) {
											if ( isset( $items[ $id ] ) ) {
												$data[ $id ] = $items[ $id ];
											}
										}
									} else {
										// Use order set by orderby.
										foreach ( $items as $id => $v ) {
											// @codingStandardsIgnoreLine.
											if ( in_array( $id, $ids ) ) {
												$data[ $id ] = $v;
											}
										}
									}
								}//end if
							}//end if

							if ( $last_is_relationship_field || in_array( $last_type, array(
								'boolean',
								'number',
								'currency',
							), true ) ) {
								$params->raw = true;
							}

							if ( empty( $data ) ) {
								$value = false;
							} else {
								$object_type = $table['type'];

								if ( in_array( $table['type'], array( 'post_type', 'attachment', 'media' ), true ) ) {
									$object_type = 'post';
								}

								$object_no_conflict = in_array( $object_type, array( 'post', 'taxonomy', 'user', 'comment', 'settings' ), true );

								$no_conflict = pods_no_conflict_check( $object_type );

								if ( $object_no_conflict && ! $no_conflict ) {
									// Temporarily enable no conflict.
									pods_no_conflict_on( $object_type );
								}

								if ( $is_field_output_full ) {
									// Return entire array.
									$value = $data;
								} else {
									// Return an array of single column values.
									$value = array();

									// $field is 123x123, needs to be _src.123x123
									$traverse_fields = array_splice( $params->traverse, $key );
									$full_field      = implode( '.', $traverse_fields );
									array_shift( $traverse_fields );

									foreach ( $data as $item_id => $item ) {
										if ( is_array( $item ) && isset( $item[ $field ] ) ) {
											if ( $table['field_id'] === $field ) {
												$value[] = (int) $item[ $field ];
											} else {
												$value[] = $item[ $field ];
											}
										} elseif ( is_object( $item ) && isset( $item->{$field} ) ) {
											if ( $table['field_id'] === $field ) {
												$value[] = (int) $item->{$field};
											} else {
												$value[] = $item->{$field};
											}
										} elseif ( ! empty( $related_obj ) && 0 === strpos( $full_field, 'post_thumbnail' ) ) {
											// We want to catch post_thumbnail and post_thumbnail_url here
											$value[] = $related_obj->field( $full_field );
										} elseif (
											(
												( false !== strpos( $full_field, '_src' ) || 'guid' === $field )
												&& (
													in_array( $table['type'], [ 'attachment', 'media' ], true )
													|| in_array( $last_type, PodsForm::file_field_types(), true )
												)
											)
											|| ( in_array( $field, $permalink_fields, true ) && in_array( $last_type, PodsForm::file_field_types(), true ) )
										) {
											// @todo Refactor the above condition statement.
											$size = 'full';

											if ( ! wp_attachment_is_image( $item_id ) ) {
												// No default sizes for non-images.
												// When a size is defined this will be overwritten.
												$size = null;
											}

											if ( false !== strpos( $full_field, '_src.' ) && 5 < strlen( $full_field ) ) {
												$size = substr( $full_field, 5 );
											} elseif ( false !== strpos( $full_field, '_src_relative.' ) && 14 < strlen( $full_field ) ) {
												$size = substr( $full_field, 14 );
											} elseif ( false !== strpos( $full_field, '_src_schemeless.' ) && 16 < strlen( $full_field ) ) {
												$size = substr( $full_field, 16 );
											}

											if ( $size ) {
												$value_url = pods_image_url( $item_id, $size, 0, true );
											} else {
												$value_url = wp_get_attachment_url( $item_id );
											}

											if ( ! empty( $value_url ) ) {
												if ( false !== strpos( $full_field, '_src_relative' ) ) {
													$value_url_parsed = wp_parse_url( $value_url );
													$value_url        = $value_url_parsed['path'];
												} elseif ( false !== strpos( $full_field, '_src_schemeless' ) ) {
													$value_url = str_replace( array(
														'http://',
														'https://',
													), '//', $value_url );
												}
											}

											if ( ! empty( $value_url ) ) {
												$value[] = $value_url;
											}

											$params->raw_display = true;
										} elseif (
											false !== strpos( $full_field, '_img' ) &&
											(
												in_array( $table['type'], array( 'attachment', 'media', ), true ) ||
												in_array( $last_type, PodsForm::file_field_types(), true )
											)
										) {
											$size = 'full';

											if ( false !== strpos( $full_field, '_img.' ) && 5 < strlen( $full_field ) ) {
												$size = substr( $full_field, 5 );
											}

											$value[] = pods_image( $item_id, $size, 0, array(), true );

											$params->raw_display = true;
										} elseif ( in_array( $field, $permalink_fields, true ) ) {
											if ( 'pod' === $object_type ) {
												if ( is_object( $related_obj ) ) {
													$related_obj->fetch( $item_id );

													$value[] = $related_obj->field( 'detail_url' );
												} else {
													$value[] = '';
												}
											} elseif ( 'post' === $object_type ) {
												$value[] = get_permalink( $item_id );
											} elseif ( 'taxonomy' === $object_type ) {
												$value[] = get_term_link( $item_id, $object );
											} elseif ( 'user' === $object_type ) {
												$value[] = get_author_posts_url( $item_id );
											} elseif ( 'comment' === $object_type ) {
												$value[] = get_comment_link( $item_id );
											} else {
												$value[] = '';
											}

											$params->raw_display = true;
										} elseif ( in_array( $object_type, array(
											'post',
											'taxonomy',
											'user',
											'comment',
										), true ) ) {
											$metadata_object_id = $item_id;

											$metadata_type = $object_type;

											if ( 'taxonomy' === $object_type ) {
												$metadata_type = 'term';
											}

											/** This filter is documented earlier in this method */
											$metadata_object_id = apply_filters( 'pods_pods_field_get_metadata_object_id', $metadata_object_id, $metadata_type, $params, $this );

											$meta_value = get_metadata( $metadata_type, $metadata_object_id, $field, ! $last_is_repeatable_field );

											$value[] = pods_traverse( $traverse_fields, $meta_value );
										} elseif ( 'settings' === $object_type ) {
											$option_value = get_option( $object . '_' . $field );

											$value[] = pods_traverse( $traverse_fields, $option_value );
										}//end if
									}//end foreach
								}//end if

								if ( $object_no_conflict && ! $no_conflict ) {
									// Revert temporarily no conflict mode.
									pods_no_conflict_off( $object_type );
								}

								if ( isset( $related_obj->fields[ $field ] ) ) {
									// Set the last field options for formatting.
									$last_options = $related_obj->fields[ $field ];
								}

								// Handle Simple Relationships.
								if ( $simple ) {
									if ( null === $params->single ) {
										$params->single = false;
									}

									$value = PodsForm::field_method( 'pick', 'simple_value', $field, $value, $last_options, $all_fields[ $pod ], 0, true );
								} elseif ( false === $params->in_form && ! empty( $value ) && is_array( $value ) && false === $params->keyed ) {
									$value = array_values( $value );
								}

								// Return a single column value.
								if ( false === $params->in_form && 1 === $limit && ! empty( $value ) && is_array( $value ) && 1 === count( $value ) ) {
									$value = current( $value );
								}
							}//end if

							if ( $last_options ) {
								$last_field_data = $last_options;
							} elseif ( isset( $related_obj->fields[ $field ] ) ) {
								// Save related field data for later to be used for display formatting
								$last_field_data = $related_obj->fields[ $field ];
							}

							break;
						}//end if
					}//end foreach
				}//end if
			}//end if
		}//end if

		if ( ! empty( $params->traverse ) && 1 < count( $params->traverse ) ) {
			$field_names = implode( '.', $params->traverse );

			$this->data->row[ $field_names ] = $value;
		} elseif ( 'arrays' !== $params->output && $field_data && $is_relationship_field ) {
			$this->data->row[ '_' . $params->output . '_' . $params->full_name ] = $value;
		} elseif ( 'arrays' === $params->output || ! $field_data || ! $is_relationship_field ) {
			$this->data->row[ $params->full_name ] = $value;
		}

		if ( true === $params->single && is_array( $value ) && 1 === count( $value ) ) {
			$value = current( $value );
		}

		if ( ! empty( $last_field_data ) ) {
			$field_data = $last_field_data;

			if ( isset( $last_is_repeatable_field ) ) {
				$is_repeatable_field = $last_is_repeatable_field;
			}
		}

		if (
			$is_repeatable_field
			&& is_array( $value )
			&& ! empty( $value )
			&& is_array( current( $value ) )
		) {
			$value = array_merge( ...$value );
		}

		if ( ! empty( $field_data ) && ( $params->display || ! $params->raw ) && ! $params->in_form && ! $params->raw_display ) {
			if ( $params->display || ( ( $params->get_meta || $params->deprecated ) && ! $is_relationship_field ) ) {
				$post_temp   = false;
				$old_post    = null;
				$old_post_id = null;
				$post_ID     = null;

				if ( empty( $GLOBALS['post'] ) && 'post_type' === pods_v( 'type', $this->pod_data ) && 0 < $this->id() ) {
					global $post_ID, $post;

					$post_temp = true;

					$old_post    = $post;
					$old_post_id = $post_ID;

					// @codingStandardsIgnoreLine.
					$post    = get_post( $this->id() );
					// @codingStandardsIgnoreLine.
					$post_ID = $this->id();
				}

				$filter = pods_v( 'display_filter', $field_data, '', true );

				if ( is_string( $filter ) && 0 < strlen( $filter ) ) {
					$value_reset = false;

					if ( $params->single || ! is_array( $value ) ) {
						$value = array( $value );

						$value_reset = true;
					}

					foreach ( $value as $key => $val ) {
						$args = array(
							$filter,
							$val,
						);

						$filter_args = pods_v( 'display_filter_args', $field_data );

						if ( ! empty( $filter_args ) ) {
							$args = array_merge( $args, compact( $filter_args ) );
						}

						$val = call_user_func_array( 'apply_filters', array_values( $args ) );

						$value[ $key ] = $val;

					}

					if ( $value_reset ) {
						$value = reset( $value );
					}
				} elseif ( $is_repeatable_field || 1 === (int) pods_v( 'display_process', $field_data, 1 ) ) {
					if ( $is_repeatable_field && $value && ! is_array( $value ) ) {
						$value = [
							$value,
						];
					}

					if (
						is_array( $value )
						&& (
							$is_repeatable_field
							|| $params->display_process_individually
						)
					) {
						// Attempt to process each value independently.
						foreach ( $value as $k => $val ) {
							$value[ $k ] = PodsForm::display( $field_data['type'], $val, $params->name, $field_data, $this->pod_data, $this->id() );
						}
					} else {
						// Do the normal display handling.
						$value = PodsForm::display( $field_data['type'], $value, $params->name, $field_data, $this->pod_data, $this->id() );
					}
				}

				if ( $post_temp ) {
					// @codingStandardsIgnoreLine.
					$post    = $old_post;
					// @codingStandardsIgnoreLine.
					$post_ID = $old_post_id;
				}
			} else {
				$value = PodsForm::value( $field_data['type'], $value, $params->name, $field_data, $this->pod_data, $this->id() );
			}//end if
		}//end if

		/**
		 * Modify value returned by field() directly before output.
		 *
		 * Will not run if value was null
		 *
		 * @since unknown
		 *
		 * @param array|string|null $value  Value to be returned.
		 * @param array|object      $row    Current row being outputted.
		 * @param object            $params Params array passed to field().
		 * @param Pods              $obj    Current Pods object.
		 */
		$value = apply_filters( 'pods_pods_field', $value, $this->row(), $params, $this );

		return $value;
	}

	/**
	 * Check if an item field has a specific value in it
	 *
	 * @param string $field Field name.
	 * @param mixed  $value Value to check.
	 * @param int    $id    (optional) ID of the pod item to check.
	 *
	 * @return bool Whether the value was found
	 *
	 * @since 2.3.3
	 */
	public function has( $field, $value, $id = null ) {
		$pod =& $this;

		$field_object = $this->fields( $field );

		if ( ! $field_object ) {
			return false;
		}

		if ( null === $id ) {
			$id = $this->id();
			// @codingStandardsIgnoreLine.
		} elseif ( $id != $this->id() ) {
			$pod = pods( $this->pod, $id );
		}

		$this->do_hook( 'has', $field, $value, $id );

		$field_type = $field_object->get_type();

		// Tableless fields.
		if ( $field_object->is_relationship() ) {
			if ( ! is_array( $value ) ) {
				$value = explode( ',', $value );
			}

			if ( $field_object->is_simple_relationship() ) {
				$current_value = $pod->raw( $field );

				if ( ! empty( $current_value ) ) {
					$current_value = (array) $current_value;
				}

				foreach ( $current_value as $v ) {
					// @codingStandardsIgnoreLine.
					if ( in_array( $v, $value ) ) {
						return true;
					}
				}
			} else {
				$related_ids = $this->data->api->lookup_related_items( $field_object->get_name(), $this->pod_data['name'], $id, $field_object, $this->pod_data );

				foreach ( $value as $k => $v ) {
					if ( ! preg_match( '/[^\D]/', $v ) ) {
						$value[ $k ] = (int) $v;
					}

					// @todo Convert slugs into IDs.
				}

				foreach ( $related_ids as $v ) {
					// @codingStandardsIgnoreLine.
					if ( in_array( $v, $value ) ) {
						return true;
					}
				}
			}//end if
		} elseif ( in_array( $field_type, PodsForm::text_field_types(), true ) ) {
			// Text fields.
			$current_value = $pod->raw( $field );

			if ( is_string( $current_value ) && 0 < strlen( $current_value ) ) {
				return stripos( $current_value, $value );
			}
		} else {
			// All other fields.
			return $this->is( $field, $value, $id );
		}//end if

		return false;
	}

	/**
	 * Check if an item field is a specific value
	 *
	 * @param string $field Field name.
	 * @param mixed  $value Value to check.
	 * @param int    $id    (optional) ID of the pod item to check.
	 *
	 * @return bool Whether the value was found
	 *
	 * @since 2.3.3
	 */
	public function is( $field, $value, $id = null ) {
		$pod =& $this;

		$field_object = $this->fields( $field );

		if ( ! $field_object ) {
			return false;
		}

		if ( null === $id ) {
			$id = $this->id();
			// @codingStandardsIgnoreLine.
		} elseif ( $id != $this->id() ) {
			$pod = pods( $this->pod, $id );
		}

		$this->do_hook( 'is', $field, $value, $id );

		$field_type = $field_object->get_type();

		// Tableless fields.
		if ( $field_object->is_relationship() ) {
			if ( ! is_array( $value ) ) {
				$value = explode( ',', $value );
			}

			$current_value = array();

			if ( $field_object->is_simple_relationship() ) {
				$current_value = $pod->raw( $field );

				if ( ! empty( $current_value ) ) {
					$current_value = (array) $current_value;
				}

				foreach ( $current_value as $v ) {
					// @codingStandardsIgnoreLine.
					if ( in_array( $v, $value ) ) {
						return true;
					}
				}
			} else {
				$related_ids = $this->data->api->lookup_related_items( $field_object->get_name(), $this->pod_data['name'], $id, $field_object, $this->pod_data );

				foreach ( $value as $k => $v ) {
					if ( ! preg_match( '/[^\D]/', $v ) ) {
						$value[ $k ] = (int) $v;
					}

					// @todo Convert slugs into IDs.
				}

				foreach ( $related_ids as $v ) {
					// @codingStandardsIgnoreLine.
					if ( in_array( $v, $value ) ) {
						return true;
					}
				}
			}//end if

			if ( ! empty( $current_value ) ) {
				$current_value = array_filter( array_unique( $current_value ) );
			} else {
				$current_value = array();
			}

			if ( ! empty( $value ) ) {
				$value = array_filter( array_unique( $value ) );
			} else {
				$value = array();
			}

			sort( $current_value );
			sort( $value );

			if ( $value === $current_value ) {
				return true;
			}
		} elseif ( in_array( $field_type, PodsForm::number_field_types(), true ) ) {
			// Number fields.
			$current_value = $pod->raw( $field );

			if ( (float) $current_value === (float) $value ) {
				return true;
			}
		} elseif ( in_array( $field_type, PodsForm::date_field_types(), true ) ) {
			// Date fields.
			$current_value = $pod->raw( $field );

			if ( is_string( $current_value ) && 0 < strlen( $current_value ) ) {
				if ( strtotime( $current_value ) === strtotime( $value ) ) {
					return true;
				}
			} elseif ( empty( $value ) ) {
				return true;
			}
		} elseif ( in_array( $field_type, PodsForm::text_field_types(), true ) ) {
			// Text fields.
			$current_value = $pod->raw( $field );

			if ( (string) $current_value === (string) $value ) {
				return true;
			}
		} else {
			// All other fields.
			$current_value = $pod->raw( $field );

			if ( $current_value === $value ) {
				return true;
			}
		}//end if

		return false;
	}

	/**
	 * Return the item ID
	 *
	 * @return int
	 * @since 2.0.0
	 */
	public function id() {

		if ( isset( $this->data->row['id'] ) ) {
			// If we already have data loaded return that ID.
			return $this->data->row['id'];
		}

		return $this->field( $this->data->field_id );
	}

	/**
	 * Return the previous item ID, loops at the last id to return the first
	 *
	 * @param int|null          $id              ID to start from.
	 * @param array|object|null $params_override Override the find() parameters.
	 *
	 * @return int
	 * @since 2.0.0
	 */
	public function prev_id( $id = null, $params_override = null ) {

		if ( null === $id ) {
			$id = $this->id();
		}

		$id = (int) $id;

		$params = array(
			'select'  => "`t`.`{$this->data->field_id}`",
			'where'   => "`t`.`{$this->data->field_id}` < {$id}",
			'orderby' => "`t`.`{$this->data->field_id}` DESC",
			'limit'   => 1,
		);

		if ( ! empty( $params_override ) || ! empty( $this->params ) ) {
			if ( ! empty( $params_override ) ) {
				$params = $params_override;
			} elseif ( ! empty( $this->params ) ) {
				$params = $this->params;
			}

			if ( is_object( $params ) ) {
				$params = get_object_vars( $params );
			}

			if ( 0 < $id ) {
				if ( isset( $params['where'] ) && ! empty( $params['where'] ) ) {
					$params['where']   = (array) $params['where'];
					$params['where'][] = "`t`.`{$this->data->field_id}` < {$id}";
				} else {
					$params['where'] = "`t`.`{$this->data->field_id}` < {$id}";
				}
			} elseif ( isset( $params['offset'] ) && 0 < $params['offset'] ) {
				$params['offset'] --;
			} elseif ( 0 < $this->data->row_number && ! isset( $params['offset'] ) && ! empty( $this->params ) ) {
				$params['offset'] = $this->data->row_number - 1;
			} else {
				return 0;
			}

			if ( isset( $params['orderby'] ) && ! empty( $params['orderby'] ) ) {
				if ( is_array( $params['orderby'] ) ) {
					foreach ( $params['orderby'] as $orderby => $dir ) {
						$dir = strtoupper( $dir );

						if ( ! in_array( $dir, array( 'ASC', 'DESC' ), true ) ) {
							continue;
						}

						if ( 'ASC' === $dir ) {
							$params['orderby'][ $orderby ] = 'DESC';
						} else {
							$params['orderby'][ $orderby ] = 'ASC';
						}
					}

					$params['orderby'][ $this->data->field_id ] = 'DESC';
				} elseif ( "`t`.`{$this->data->field_id}` DESC" !== $params['orderby'] ) {
					$params['orderby'] .= ", `t`.`{$this->data->field_id}` DESC";
				}
			}//end if

			$params['select'] = "`t`.`{$this->data->field_id}`";
			$params['limit']  = 1;
		}//end if

		$pod = pods( $this->pod, $params );

		$new_id = 0;

		if ( $pod->fetch() ) {
			$new_id = $pod->id();
		}

		$new_id = $this->do_hook( 'prev_id', $new_id, $id, $pod, $params_override );

		return $new_id;
	}

	/**
	 * Return the next item ID, loops at the first id to return the last
	 *
	 * @param int|null          $id              ID to start from.
	 * @param array|object|null $params_override Override the find() parameters.
	 *
	 * @return int
	 * @since 2.0.0
	 */
	public function next_id( $id = null, $params_override = null ) {

		if ( null === $id ) {
			$id = $this->id();
		}

		$id = (int) $id;

		$params = array(
			'select'  => "`t`.`{$this->data->field_id}`",
			'where'   => "{$id} < `t`.`{$this->data->field_id}`",
			'orderby' => "`t`.`{$this->data->field_id}` ASC",
			'limit'   => 1,
		);

		if ( ! empty( $params_override ) || ! empty( $this->params ) ) {
			if ( ! empty( $params_override ) ) {
				$params = $params_override;
			} elseif ( ! empty( $this->params ) ) {
				$params = $this->params;
			}

			if ( is_object( $params ) ) {
				$params = get_object_vars( $params );
			}

			if ( 0 < $id ) {
				if ( isset( $params['where'] ) && ! empty( $params['where'] ) ) {
					$params['where']   = (array) $params['where'];
					$params['where'][] = "{$id} < `t`.`{$this->data->field_id}`";
				} else {
					$params['where'] = "{$id} < `t`.`{$this->data->field_id}`";
				}
			} elseif ( ! isset( $params['offset'] ) ) {
				if ( ! empty( $this->params ) && - 1 < $this->data->row_number ) {
					$params['offset'] = $this->data->row_number + 1;
				} else {
					$params['offset'] = 1;
				}
			} else {
				$params['offset'] ++;
			}

			$params['select'] = "`t`.`{$this->data->field_id}`";
			$params['limit']  = 1;
		}//end if

		$pod = pods( $this->pod, $params );

		$new_id = 0;

		if ( $pod->fetch() ) {
			$new_id = $pod->id();
		}

		$new_id = $this->do_hook( 'next_id', $new_id, $id, $pod, $params_override );

		return $new_id;
	}

	/**
	 * Return the first item ID
	 *
	 * @param array|object|null $params_override Override the find() parameters.
	 *
	 * @return int
	 * @since 2.3.0
	 */
	public function first_id( $params_override = null ) {

		$params = array(
			'select'  => "`t`.`{$this->data->field_id}`",
			'orderby' => "`t`.`{$this->data->field_id}` ASC",
			'limit'   => 1,
		);

		if ( ! empty( $params_override ) || ! empty( $this->params ) ) {
			if ( ! empty( $params_override ) ) {
				$params = $params_override;
			} elseif ( ! empty( $this->params ) ) {
				$params = $this->params;
			}

			if ( is_object( $params ) ) {
				$params = get_object_vars( $params );
			}

			$params['select'] = "`t`.`{$this->data->field_id}`";
			$params['offset'] = 0;
			$params['limit']  = 1;
		}

		$pod = pods( $this->pod, $params );

		$new_id = 0;

		if ( $pod->fetch() ) {
			$new_id = $pod->id();
		}

		$new_id = $this->do_hook( 'first_id', $new_id, $pod, $params_override );

		return $new_id;
	}

	/**
	 * Return the last item ID
	 *
	 * @param array|object|null $params_override Override the find() parameters.
	 *
	 * @return int
	 * @since 2.3.0
	 */
	public function last_id( $params_override = null ) {

		$params = array(
			'select'  => "`t`.`{$this->data->field_id}`",
			'orderby' => "`t`.`{$this->data->field_id}` DESC",
			'limit'   => 1,
		);

		if ( ! empty( $params_override ) || ! empty( $this->params ) ) {
			if ( ! empty( $params_override ) ) {
				$params = $params_override;
			} elseif ( ! empty( $this->params ) ) {
				$params = $this->params;
			}

			if ( is_object( $params ) ) {
				$params = get_object_vars( $params );
			}

			if ( isset( $params['total_found'] ) ) {
				$params['offset'] = $params['total_found'] - 1;
			} else {
				$params['offset'] = $this->total_found() - 1;
			}

			if ( isset( $params['orderby'] ) && ! empty( $params['orderby'] ) ) {
				if ( is_array( $params['orderby'] ) ) {
					foreach ( $params['orderby'] as $orderby => $dir ) {
						$dir = strtoupper( $dir );

						if ( ! in_array( $dir, array( 'ASC', 'DESC' ), true ) ) {
							continue;
						}

						if ( 'ASC' === $dir ) {
							$params['orderby'][ $orderby ] = 'DESC';
						} else {
							$params['orderby'][ $orderby ] = 'ASC';
						}
					}

					$params['orderby'][ $this->data->field_id ] = 'DESC';
				} elseif ( "`t`.`{$this->data->field_id}` DESC" !== $params['orderby'] ) {
					$params['orderby'] .= ", `t`.`{$this->data->field_id}` DESC";
				}
			}//end if

			$params['select'] = "`t`.`{$this->data->field_id}`";
			$params['limit']  = 1;
		}//end if

		$pod = pods( $this->pod, $params );

		$new_id = 0;

		if ( $pod->fetch() ) {
			$new_id = $pod->id();
		}

		$new_id = $this->do_hook( 'last_id', $new_id, $pod, $params_override );

		return $new_id;
	}

	/**
	 * Return the item name
	 *
	 * @return string
	 * @since 2.0.0
	 */
	public function index() {

		return $this->field( $this->data->field_index );
	}

	/**
	 * Find items of a pod, much like WP_Query, but with advanced table handling.
	 *
	 * @param array|object $params An associative array of parameters.
	 * @param int          $limit  (deprecated) Limit the number of items to find, -1 to return all items with no limit.
	 * @param string       $where  (deprecated) SQL WHERE declaration to use.
	 * @param string       $sql    (deprecated) For advanced use, a custom SQL query to run.
	 *
	 * @return \Pods The pod object
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/find/
	 */
	public function find( $params = null, $limit = 15, $where = null, $sql = null ) {
		$this->params = $params;

		$select = '`t`.*';

		if ( ! $this->pod_data instanceof Pods\Whatsit\Pod ) {
			return $this;
		}

		if ( 'table' === $this->pod_data['storage'] && ! in_array( $this->pod_data['type'], array(
			'pod',
			'table',
		), true ) ) {
			$select .= ', `d`.*';
		}

		$table = $this->pod_data['table'];

		if ( empty( $table ) ) {
			return $this;
		}

		$defaults = array(
			// Optimization parameters.
			'table'               => $table,
			'select'              => $select,
			'join'                => null,
			// Main query parameters.
			'where'               => $where,
			'groupby'             => null,
			'having'              => null,
			'orderby'             => null,
			// Pagination parameters.
			'limit'               => (int) $limit,
			'offset'              => null,
			'page'                => (int) $this->page,
			'page_var'            => $this->page_var,
			'pagination'          => (boolean) $this->pagination,
			// Search parameters.
			'search'              => (boolean) $this->search,
			'search_var'          => $this->search_var,
			'search_query'        => null,
			'search_mode'         => $this->search_mode,
			'search_across'       => false,
			'search_across_picks' => false,
			'search_across_files' => false,
			// Advanced parameters.
			'filter_var'          => $this->filter_var,
			'filters'             => $this->filters,
			'sql'                 => $sql,
			// Caching parameters.
			'expires'             => null,
			'cache_mode'          => 'cache',
		);

		if ( is_array( $params ) ) {
			$params = (object) array_merge( $defaults, $params );
		} elseif ( is_object( $params ) ) {
			$params = (object) array_merge( $defaults, get_object_vars( $params ) );
		} else {
			$defaults['orderby'] = $params;

			$params = (object) $defaults;
		}

		/**
		 * Filter the Pods::find() parameters.
		 *
		 * @param object $params Parameters to make lookup with.
		 */
		$params = apply_filters( 'pods_pods_find', $params );

		$params->limit = (int) $params->limit;

		if ( 0 === $params->limit ) {
			$params->limit = - 1;
		}

		$this->limit      = (int) $params->limit;
		$this->offset     = (int) $params->offset;
		$this->page       = (int) $params->page;
		$this->page_var   = $params->page_var;
		$this->pagination = (boolean) $params->pagination;
		$this->search     = (boolean) $params->search;
		$this->search_var = $params->search_var;
		$this->filter_var = $params->filter_var;
		$params->join     = (array) $params->join;

		if ( empty( $params->search_query ) ) {
			$params->search_query = pods_v_sanitized( $this->search_var, 'get', '' );
		}

		$pod_type     = $this->pod_data->get_type();
		$storage_type = $this->pod_data->get_storage();

		// Add prefix to $params->orderby if needed and handle field => ASC|DESC formats.
		if ( ! empty( $params->orderby ) ) {
			if ( ! is_array( $params->orderby ) ) {
				$params->orderby = array( $params->orderby );
			}

			foreach ( $params->orderby as $key => $prefix_orderby ) {
				if (
					false !== strpos( $prefix_orderby, ',' )
					|| false !== strpos( $prefix_orderby, '(' )
					|| false !== stripos( $prefix_orderby, ' AS ' )
					|| false !== strpos( $prefix_orderby, '`' )
					|| false !== strpos( $prefix_orderby, '.' )
				) {
					continue;
				}

				if ( in_array( strtoupper( $prefix_orderby ), [ 'ASC', 'DESC' ], true ) ) {
					// Handle field => ASC|DESC.
					$field_name = $key;
					$dir        = $prefix_orderby;
				} elseif ( false !== stripos( $prefix_orderby, ' ASC' ) ) {
					// Handle field ASC.
					$field_name = trim( str_ireplace( [ '`', ' ASC' ], '', $prefix_orderby ) );
					$dir        = 'ASC';
				} elseif ( false !== stripos( $prefix_orderby, ' DESC' ) ) {
					// Handle field DESC.
					$field_name = trim( str_ireplace( [ '`', ' DESC' ], '', $prefix_orderby ) );
					$dir        = 'DESC';
				} else {
					// Default assumes no order was set but the field was given.
					$field_name = trim( str_ireplace( '`', '', $prefix_orderby ) );
					$dir        = 'ASC';
				}

				// Invalid orderby provided.
				if ( empty( $field_name ) ) {
					unset( $params->orderby[ $key ] );

					continue;
				}

				$order_field = $this->fields( $field_name );

				if ( $order_field instanceof Field ) {
					$field_name = $order_field->get_name();

					$is_object_field      = $order_field instanceof Object_Field;
					$is_pod_or_table_type = in_array( $pod_type, [ 'pod', 'table' ], true );

					if ( $order_field->is_relationship() ) {
						$order_object_type = $order_field->get_related_object_type();

						if ( $order_field->is_simple_relationship() ) {
							if ( $is_object_field || 'table' === $storage_type ) {
								if ( ! $is_object_field && ! $is_pod_or_table_type ) {
									$field_name = "`d`.`{$field_name}`";
								} else {
									$field_name = "`t`.`{$field_name}`";
								}
							} else {
								$field_name = "`{$field_name}`.`meta_value`";
							}
						} else {
							$table = $order_field->get_table_info();

							if ( ! empty( $table ) ) {
								$field_name = "`{$field_name}`.`" . $table['field_index'] . '`';
							}
						}
					} elseif ( $is_object_field || 'table' === $storage_type ) {
						if ( ! $is_object_field && ! $is_pod_or_table_type ) {
							$field_name = "`d`.`{$field_name}`";
						} else {
							$field_name = "`t`.`{$field_name}`";
						}
					} elseif ( 'meta' === $storage_type ) {
						$field_name = "`{$field_name}`.`meta_value`";
					}
				}

				$params->orderby[ $key ] = "{$field_name} {$dir}";
			}//end foreach
		}//end if

		$this->data->select( $params );

		return $this;
	}

	/**
	 * Fetch an item from a Pod. If $id is null, it will return the next item in the list after running find().
	 * You can rewind the list back to the start by using reset().
	 *
	 * Providing an $id will fetch a specific item from a Pod, much like a call to pods(), and can handle either an id
	 * or slug.
	 *
	 * @see   PodsData::fetch
	 *
	 * @param int  $id           ID or slug of the item to fetch.
	 * @param bool $explicit_set Whether to set explicitly (use false when in loop).
	 *
	 * @return array An array of fields from the row
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/fetch/
	 */
	public function fetch( $id = null, $explicit_set = true ) {

		/**
		 * Runs directly before an item is fetched by fetch().
		 *
		 * @since unknown
		 *
		 * @param int|string|null $id  Item ID being fetched or null.
		 * @param Pods            $obj Current Pods object.
		 */
		do_action( 'pods_pods_fetch', $id, $this );

		if ( ! empty( $id ) ) {
			$this->params = array();
		}

		$this->data->fetch( $id, $explicit_set );

		return $this->row();
	}

	/**
	 * (Re)set the MySQL result pointer
	 *
	 * @see   PodsData::reset
	 *
	 * @param int $row ID of the row to reset to.
	 *
	 * @return \Pods The pod object
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/reset/
	 */
	public function reset( $row = null ) {

		/**
		 * Runs directly before the Pods object is reset by reset()
		 *
		 * @since unknown
		 *
		 * @param int|string|null $row The ID of the row being reset to or null if being reset to the beginning.
		 * @param Pods            $obj Current Pods object.
		 */
		do_action( 'pods_pods_reset', $row, $this );

		$this->data->reset( $row );

		return $this;
	}

	/**
	 * Fetch the total row count returned by the last call to find(), based on the 'limit' parameter set.
	 *
	 * This is different than the total number of rows found in the database, which you can get with total_found().
	 *
	 * @see   PodsData::total
	 *
	 * @return int Number of rows returned by find(), based on the 'limit' parameter set
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/total/
	 */
	public function total() {

		do_action( 'pods_pods_total', $this );

		$this->data->total();

		return $this->data->total;
	}

	/**
	 * Fetch the total amount of rows found by the last call to find(), regardless of the 'limit' parameter set.
	 *
	 * This is different than the total number of rows limited by the current call, which you can get with total().
	 *
	 * @see   PodsData::total_found
	 *
	 * @params null|array $params The list of Pods::find() parameters to use, otherwise use current dataset to calculate total found.
	 *
	 * @return int Number of rows returned by find(), regardless of the 'limit' parameter
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/total-found/
	 */
	public function total_found( $params = null ) {
		// Support find() shorthand to get total_found() for a specific dataset.
		if ( is_array( $params ) ) {
			$this->find( $params );

			return $this->total_found();
		}

		/**
		 * Runs directly before the value of total_found() is determined and returned.
		 *
		 * @since unknown
		 *
		 * @param Pods $obj Current Pods object.
		 */
		do_action( 'pods_pods_total_found', $this );

		$this->data->total_found();

		return (int) $this->data->total_found;
	}

	/**
	 * Get the total count of all rows in a simple find() across the whole Pod. This will perform a new find() request
	 * and then return the total number of rows found.
	 *
	 * This method is non-destructive and it will not alter the current Pod object.
	 *
	 * @since 2.8.9
	 *
	 * @return int The total count of all rows in a simple find() across the whole Pod.
	 */
	public function total_all_rows() {
		$field_id = pods_v( 'field_id', $this->pod_data );

		if ( empty( $field_id ) ) {
			return 0;
		}

		$pod = clone $this;

		// Make a simple request so we can perform a total_found() SQL request.
		$params = [
			'distinct' => false,
			'select'   => 't.' . $this->pod_data['field_id'],
			'limit'    => 1,
		];

		$pod->find( $params );

		return $pod->total_found();
	}

	/**
	 * Fetch the total number of pages, based on total rows found and the last find() limit
	 *
	 * @param null|int $limit  Rows per page.
	 * @param null|int $offset Offset of rows.
	 * @param null|int $total  Total rows.
	 *
	 * @return int Number of pages.
	 * @since 2.3.10
	 */
	public function total_pages( $limit = null, $offset = null, $total = null ) {

		$this->do_hook( 'total_pages' );

		if ( null === $limit ) {
			$limit = $this->limit;
		}

		if ( null === $offset ) {
			$offset = $this->offset;
		}

		if ( null === $total ) {
			$total = $this->total_found();
		}

		// No limit means one page.
		if ( $limit < 1 ) {
			return 1;
		}

		return (int) ceil( ( $total - $offset ) / $limit );

	}

	/**
	 * Fetch the zebra switch
	 *
	 * @see   PodsData::zebra
	 *
	 * @return bool Zebra state
	 * @since 1.12
	 */
	public function zebra() {

		$this->do_hook( 'zebra' );

		return $this->data->zebra();
	}

	/**
	 * Fetch the nth state
	 *
	 * @see   PodsData::nth
	 *
	 * @param int|string $nth The $nth to match on the PodsData::row_number.
	 *
	 * @return bool Whether $nth matches
	 * @since 2.3.0
	 */
	public function nth( $nth = null ) {

		$this->do_hook( 'nth', $nth );

		return $this->data->nth( $nth );
	}

	/**
	 * Fetch the current position in the loop (starting at 1)
	 *
	 * @see   PodsData::position
	 *
	 * @return int Current row number (+1)
	 * @since 2.3.0
	 */
	public function position() {

		$this->do_hook( 'position' );

		return $this->data->position();
	}

	/**
	 * Add an item to a Pod by giving an array of field data or set a specific field to
	 * a specific value if you're just wanting to add a new item but only set one field.
	 *
	 * You may be looking for save() in most cases where you're setting a specific field.
	 *
	 * @see   PodsAPI::save_pod_item
	 *
	 * @param array|string $data  Either an associative array of field information or a field name.
	 * @param mixed        $value (optional) Value of the field, if $data is a field name.
	 *
	 * @return int The item ID
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/add/
	 */
	public function add( $data = null, $value = null ) {

		if ( null !== $value ) {
			$data = array( $data => $value );
		}

		$data = (array) $this->do_hook( 'add', $data );

		if ( empty( $data ) ) {
			return 0;
		}

		$params = array(
			'pod'                 => $this->pod,
			'data'                => $data,
			'allow_custom_fields' => true,
		);

		return $this->data->api->save_pod_item( $params );
	}

	/**
	 * Add an item to the values of a relationship field, add a value to a number field (field+1), add time to a date
	 * field, or add text to a text field
	 *
	 * @see   PodsAPI::save_pod_item
	 *
	 * @param string $field Field name.
	 * @param mixed  $value IDs to add, int|float to add to number field, string for dates (+1 day), or string for text.
	 * @param int    $id    (optional) ID of the pod item to update.
	 *
	 * @return int The item ID
	 *
	 * @since 2.3.0
	 */
	public function add_to( $field, $value, $id = null ) {

		$pod =& $this;

		$field_object = $this->fields( $field );

		if ( ! $field_object ) {
			return false;
		}

		$fetch = false;

		if ( null === $id ) {
			$fetch = true;

			$id = $pod->id();
			// @codingStandardsIgnoreLine
		} elseif ( $id != $this->id() ) {
			$pod = pods( $this->pod, $id );
		}

		$this->do_hook( 'add_to', $field, $value, $id );

		$field_type = $field_object->get_type();

		// Tableless fields.
		if ( $field_object->is_relationship() ) {
			if ( ! is_array( $value ) ) {
				$value = explode( ',', $value );
			}

			if ( $field_object->is_simple_relationship() ) {
				$current_value = $pod->raw( $field );

				if ( ! empty( $current_value ) || ( is_string( $current_value ) && 0 < strlen( $current_value ) ) ) {
					$current_value = (array) $current_value;
				} else {
					$current_value = array();
				}

				$value = array_merge( $current_value, $value );
			} else {
				$related_ids = $this->data->api->lookup_related_items( $field_object->get_name(), $this->pod_data['name'], $id, $field_object, $this->pod_data );

				foreach ( $value as $k => $v ) {
					if ( ! preg_match( '/[^\D]/', $v ) ) {
						$value[ $k ] = (int) $v;
					}
				}

				$value = array_merge( $related_ids, $value );
			}//end if

			if ( ! empty( $value ) ) {
				$value = array_filter( array_unique( $value ) );
			} else {
				$value = array();
			}

			if ( empty( $value ) ) {
				return $id;
			}
		} elseif ( in_array( $field_type, PodsForm::number_field_types(), true ) ) {
			// Number fields.
			$current_value = (float) $pod->raw( $field );

			$value = ( $current_value + (float) $value );
		} elseif ( in_array( $field_type, PodsForm::date_field_types(), true ) ) {
			// Date fields.
			$current_value = $pod->raw( $field );

			if ( is_string( $current_value ) && 0 < strlen( $current_value ) ) {
				$value = strtotime( $value, strtotime( $current_value ) );
			} else {
				$value = strtotime( $value );
			}
		} elseif ( in_array( $field_type, PodsForm::text_field_types(), true ) ) {
			// Text fields.
			$current_value = $pod->raw( $field );

			if ( is_string( $current_value ) && 0 < strlen( $current_value ) ) {
				$value = $current_value . $value;
			}
		}//end if

		// @todo handle object fields and taxonomies.
		$params = array(
			'pod'  => $this->pod,
			'id'   => $id,
			'data' => array(
				$field => $value,
			),
		);

		$id = $this->data->api->save_pod_item( $params );

		if ( 0 < $id && $fetch ) {
			// Clear local var cache of field values.
			$pod->data->row = array();

			$pod->fetch( $id, false );
		}

		return $id;
	}

	/**
	 * Remove an item from the values of a relationship field, remove a value from a number field (field-1), remove
	 * time to a date field
	 *
	 * @see   PodsAPI::save_pod_item
	 *
	 * @param string $field Field name.
	 * @param mixed  $value IDs to add, int|float to add to number field, string for dates (-1 day), or string for text.
	 * @param int    $id    (optional) ID of the pod item to update.
	 *
	 * @return int The item ID
	 *
	 * @since 2.3.3
	 */
	public function remove_from( $field, $value = null, $id = null ) {

		$pod =& $this;

		$field_object = $this->fields( $field );

		if ( ! $field_object ) {
			return false;
		}

		$fetch = false;

		if ( null === $id ) {
			$fetch = true;

			$id = $this->id();
			// @codingStandardsIgnoreLine
		} elseif ( $id != $this->id() ) {
			$pod = pods( $this->pod, $id );
		}

		$this->do_hook( 'remove_from', $field, $value, $id );

		$field_type = $field_object->get_type();

		// Tableless fields.
		if ( $field_object->is_relationship() ) {
			if ( empty( $value ) ) {
				$value = array();
			}

			if ( ! empty( $value ) ) {
				if ( ! is_array( $value ) ) {
					$value = explode( ',', $value );
				}

				if ( $field_object->is_simple_relationship() ) {
					$current_value = $pod->raw( $field );

					if ( ! empty( $current_value ) ) {
						$current_value = (array) $current_value;
					}

					foreach ( $current_value as $k => $v ) {
						// @codingStandardsIgnoreLine.
						if ( in_array( $v, $value ) ) {
							unset( $current_value[ $k ] );
						}
					}

					$value = $current_value;
				} else {
					$related_ids = $this->data->api->lookup_related_items( $field_object->get_name(), $this->pod_data['name'], $id, $field_object, $this->pod_data );

					foreach ( $value as $k => $v ) {
						if ( ! preg_match( '/[^\D]/', $v ) ) {
							$value[ $k ] = (int) $v;
						}

						// @todo Convert slugs into IDs.
					}

					foreach ( $related_ids as $k => $v ) {
						// @codingStandardsIgnoreLine.
						if ( in_array( $v, $value ) ) {
							unset( $related_ids[ $k ] );
						}
					}

					$value = $related_ids;
				}//end if

				if ( ! empty( $value ) ) {
					$value = array_filter( array_unique( $value ) );
				} else {
					$value = array();
				}
			}//end if
		} elseif ( in_array( $field_type, PodsForm::number_field_types(), true ) ) {
			// Number fields.
			// Date fields don't support empty for removing.
			if ( empty( $value ) ) {
				return $id;
			}

			$current_value = (float) $pod->raw( $field );

			$value = ( $current_value - (float) $value );
		} elseif ( in_array( $field_type, PodsForm::date_field_types(), true ) ) {
			// Date fields.
			// Date fields don't support empty for removing.
			if ( empty( $value ) ) {
				return $id;
			}

			$current_value = $pod->raw( $field );

			if ( is_string( $current_value ) && 0 < strlen( $current_value ) ) {
				$value = strtotime( $value, strtotime( $current_value ) );
			} else {
				$value = strtotime( $value );
			}

			$value = date_i18n( 'Y-m-d h:i:s', $value );
		}//end if

		// @todo handle object fields and taxonomies.
		$params = array(
			'pod'  => $this->pod,
			'id'   => $id,
			'data' => array(
				$field => $value,
			),
		);

		$id = $this->data->api->save_pod_item( $params );

		if ( 0 < $id && $fetch ) {
			// Clear local var cache of field values.
			$pod->data->row = array();

			$pod->fetch( $id, false );
		}

		return $id;

	}

	/**
	 * Save an item by giving an array of field data or set a specific field to a specific value.
	 *
	 * Though this function has the capacity to add new items, best practice should direct you
	 * to use add() for that instead.
	 *
	 * @see   PodsAPI::save_pod_item
	 *
	 * @param array|string $data   Either an associative array of field information or a field name.
	 * @param mixed        $value  (optional) Value of the field, if $data is a field name.
	 * @param int          $id     (optional) ID of the pod item to update.
	 * @param array        $params (optional) Additional params to send to save_pod_item.
	 *
	 * @return int The item ID
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/save/
	 */
	public function save( $data = null, $value = null, $id = null, $params = null ) {

		if ( null !== $data && ! is_array( $data ) ) {
			$data = array( $data => $value );
		}

		$fetch = false;

		// @codingStandardsIgnoreLine
		if ( null === $id || ( $this->row() && $id == $this->id() ) ) {
			$fetch = true;

			if ( null === $id ) {
				$id = $this->id();
			}
		}

		$data = (array) $this->do_hook( 'save', $data, $id );

		if ( empty( $data ) && empty( $params['is_new_item'] ) && empty( $params['podsmeta'] ) ) {
			return $id;
		}

		$default = array();

		if ( ! empty( $params ) && is_array( $params ) ) {
			$default = $params;
		}

		// If ID is sent as part of data, use it and then unset it from the data.
		if ( isset( $data[ $this->pod_data['field_id'] ] ) ) {
			$id = $data[ $this->pod_data['field_id'] ];

			unset( $data[ $this->pod_data['field_id'] ] );
		}

		$params = array(
			'pod'                 => $this->pod,
			'id'                  => $id,
			'data'                => $data,
			'allow_custom_fields' => true,
			'clear_slug_cache'    => false,
		);

		if ( ! empty( $default ) ) {
			$params = array_merge( $params, $default );
		}

		$id = $this->data->api->save_pod_item( $params );

		if ( 0 < $id && $fetch ) {
			// Clear local var cache of field values.
			$this->data->row = array();

			$this->fetch( $id, false );
		}

		if ( ! empty( $this->pod_data['field_slug'] ) ) {
			if ( 0 < $id && $fetch ) {
				$slug = $this->field( $this->pod_data['field_slug'] );
			} else {
				$slug = pods( $this->pod, $id )->field( $this->pod_data['field_slug'] );
			}

			if ( is_string( $slug ) && 0 < strlen( $slug ) ) {
				pods_cache_clear( $slug, 'pods_items_' . $this->pod );
			}
		}

		return $id;
	}

	/**
	 * Delete an item
	 *
	 * @see   PodsAPI::delete_pod_item
	 *
	 * @param int $id ID of the Pod item to delete.
	 *
	 * @return bool Whether the item was successfully deleted
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/delete/
	 */
	public function delete( $id = null ) {

		if ( null === $id ) {
			$id = $this->id();
		}

		$id = (int) $this->do_hook( 'delete', $id );

		if ( empty( $id ) ) {
			return false;
		}

		$params = array(
			'pod' => $this->pod,
			'id'  => $id,
		);

		return $this->data->api->delete_pod_item( $params );
	}

	/**
	 * Reset Pod
	 *
	 * @see   PodsAPI::reset_pod
	 *
	 * @return bool Whether the Pod was successfully reset
	 *
	 * @since 2.1.1
	 */
	public function reset_pod() {
		$params = [
			'id'   => $this->pod_id,
			'name' => $this->pod,
		];

		$this->data->id   = null;
		$this->data->row  = [];
		$this->data->data = [];

		$this->data->total       = 0;
		$this->data->total_found = 0;

		return $this->data->api->reset_pod( $params );
	}

	/**
	 * Duplicate an item
	 *
	 * @see   PodsAPI::duplicate_pod_item
	 *
	 * @param int $id ID of the pod item to duplicate.
	 *
	 * @return int|bool ID of the new pod item
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/duplicate/
	 */
	public function duplicate( $id = null ) {

		if ( null === $id ) {
			$id = $this->id();
		}

		$id = (int) $this->do_hook( 'duplicate', $id );

		if ( empty( $id ) ) {
			return false;
		}

		$params = array(
			'pod' => $this->pod,
			'id'  => $id,
		);

		return $this->data->api->duplicate_pod_item( $params );
	}

	/**
	 * Import data / Save multiple rows of data at once
	 *
	 * @see   PodsAPI::import
	 *
	 * @param mixed  $import_data  PHP associative array or CSV input.
	 * @param bool   $numeric_mode Use IDs instead of the name field when matching.
	 * @param string $format       Format of import data, options are php or csv.
	 *
	 * @return array IDs of imported items
	 *
	 * @since 2.3.0
	 */
	public function import( $import_data, $numeric_mode = false, $format = null ) {
		return $this->data->api->import( $import_data, $numeric_mode, $format );
	}

	/**
	 * Export an item's data
	 *
	 * @see   PodsAPI::export_pod_item
	 *
	 * @param array       $fields (optional) Fields to export.
	 * @param int         $id     (optional) ID of the pod item to export.
	 * @param null|string $format (optional) The format of the export (php | json).
	 *
	 * @return array|bool Data array of the exported pod item
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/export/
	 */
	public function export( $fields = null, $id = null, $format = null ) {
		$params = array(
			'pod'     => $this->pod,
			'id'      => $id,
			'fields'  => null,
			'depth'   => 2,
			'flatten' => false,
			'context' => null,
			'format'  => $format,
		);

		if ( is_array( $fields ) && ( isset( $fields['fields'] ) || isset( $fields['depth'] ) ) ) {
			$params = array_merge( $params, $fields );
		} else {
			$params['fields'] = $fields;
		}

		if ( isset( $params['fields'] ) && is_array( $params['fields'] ) && ! in_array( $this->pod_data['field_id'], $params['fields'], true ) ) {
			$params['fields'] = array_merge( array( $this->pod_data['field_id'] ), $params['fields'] );
		}

		if ( null === $params['id'] ) {
			$params['id'] = $this->id();
		}

		$params = (array) $this->do_hook( 'export', $params );

		if ( empty( $params['id'] ) ) {
			return false;
		}

		$data = $this->data->api->export_pod_item( $params );

		if ( ! empty( $params['format'] ) && 'json' === $params['format'] ) {
			$data = wp_json_encode( (array) $data );
		}

		return $data;
	}

	/**
	 * Export data from all items
	 *
	 * @see   PodsAPI::export
	 *
	 * @param array $params An associative array of parameters.
	 *
	 * @return array Data arrays of all exported pod items
	 *
	 * @since 2.3.0
	 */
	public function export_data( $params = null ) {
		$defaults = array(
			'fields' => null,
			'depth'  => 2,
			'params' => null,
		);

		if ( empty( $params ) ) {
			$params = $defaults;
		} else {
			$params = array_merge( $defaults, (array) $params );
		}

		return $this->data->api->export( $this, $params );
	}

	/**
	 * Display the pagination controls, types supported by default
	 * are simple, paginate and advanced. The base and format parameters
	 * are used only for the paginate view.
	 *
	 * @param array|object $params Associative array of parameters.
	 *
	 * @return string Pagination HTML
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/pagination/
	 */
	public function pagination( $params = null ) {

		if ( empty( $params ) ) {
			$params = array();
		} elseif ( ! is_array( $params ) ) {
			$params = array( 'label' => $params );
		}

		$this->page_var = pods_v( 'page_var', $params, $this->page_var );

		$url = pods_query_arg( null, null, $this->page_var );

		$append = '?';

		if ( false !== strpos( $url, '?' ) ) {
			$append = '&';
		}

		$defaults = [
			'type'            => 'advanced',
			'label'           => __( 'Go to page:', 'pods' ),
			'show_label'      => true,
			'first_text'      => __( '&laquo; First', 'pods' ),
			'prev_text'       => __( '&lsaquo; Previous', 'pods' ),
			'next_text'       => __( 'Next &rsaquo;', 'pods' ),
			'last_text'       => __( 'Last &raquo;', 'pods' ),
			'prev_next'       => true,
			'first_last'      => true,
			'limit'           => (int) $this->limit,
			'offset'          => (int) $this->offset,
			'page'            => max( 1, (int) $this->page ),
			'mid_size'        => 2,
			'end_size'        => 1,
			'total_found'     => $this->total_found(),
			'page_var'        => $this->page_var,
			'base'            => "{$url}{$append}%_%",
			'format'          => "{$this->page_var}=%#%",
			'class'           => '',
			'link_class'      => '',
			'wrap_pagination' => false,
		];

		if ( is_object( $params ) ) {
			$params = get_object_vars( $params );
		}

		$params = (object) array_merge( $defaults, (array) $params );

		$params->total = $this->total_pages( $params->limit, $params->offset, $params->total_found );

		if ( $params->limit < 1 || $params->total_found < 1 || 1 === $params->total || $params->total_found <= $params->offset ) {
			return $this->do_hook( 'pagination', $this->do_hook( 'pagination_' . $params->type, '', $params ), $params );
		}

		$pagination = $params->type;

		if ( ! in_array( $params->type, array( 'simple', 'advanced', 'paginate', 'list' ), true ) ) {
			$pagination = 'advanced';
		}

		$wrap_pagination = (bool) $params->wrap_pagination;

		ob_start();

		pods_view( PODS_DIR . 'ui/front/pagination/' . $pagination . '.php', compact( array_keys( get_defined_vars() ) ) );

		$output = ob_get_clean();

		return $this->do_hook( 'pagination', $this->do_hook( 'pagination_' . $params->type, $output, $params ), $params );

	}

	/**
	 * Return a filter form for searching a Pod
	 *
	 * @param array|string $params Comma-separated list of fields or array of parameters.
	 *
	 * @return string Filters HTML
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/filters/
	 */
	public function filters( $params = null ) {
		// Only show placeholder text if in REST API block preview.
		if ( wp_is_json_request() && did_action( 'rest_api_init' ) ) {
			return '<em>[' . esc_html__( 'This is a placeholder. Filters and form fields are not included in block previews.', 'pods' ) . ']</em>';
		}

		$defaults = array(
			'fields' => $params,
			'label'  => '',
			'action' => '',
			'search' => '',
		);

		if ( is_array( $params ) ) {
			$params = array_merge( $defaults, $params );
		} else {
			$params = $defaults;
		}

		$pod =& $this;

		/**
		 * Filter the Pods::filters() parameters.
		 *
		 * @param array $params Parameters to filter with.
		 * @param Pods  $pod    Pods object.
		 */
		$params = apply_filters( 'pods_filters_params', $params, $pod );

		$fields = $params['fields'];

		if ( null !== $fields && is_string( $fields ) && 0 < strlen( $fields ) ) {
			$fields = explode( ',', $fields );
		}

		$object_fields = (array) pods_v( 'object_fields', $this->pod_data, array(), true );

		// Force array.
		if ( empty( $fields ) ) {
			$fields = array();
		} else {
			// Temporary.
			$filter_fields = $fields;

			$fields = array();

			foreach ( $filter_fields as $k => $field ) {
				$name = $k;

				$defaults = array(
					'name' => $name,
				);

				$is_field_object = $field instanceof Field;

				if ( ! is_array( $field ) && ! $is_field_object ) {
					$name = $field;

					$field = array(
						'name' => $name,
					);
				}

				// @codingStandardsIgnoreLine.
				$field = pods_config_merge_data( $defaults, $field );

				$field['name'] = trim( $field['name'] );

				if ( pods_v( 'hidden', $field, false, true ) ) {
					$field['type'] = 'hidden';
				}

				if ( isset( $object_fields[ $field['name'] ] ) ) {
					// @codingStandardsIgnoreLine.
					$fields[ $field['name'] ] = pods_config_merge_data( $object_fields[ $field['name'] ], $field );
				} elseif ( isset( $this->fields[ $field['name'] ] ) ) {
					// @codingStandardsIgnoreLine.
					$fields[ $field['name'] ] = pods_config_merge_data( $this->fields[ $field['name'] ], $field );
				}
			}//end foreach

			// Cleanup before get_defined_vars().
			unset( $filter_fields );
		}//end if

		$this->filters = array_keys( $fields );

		$label = $params['label'];

		if ( '' === $label ) {
			$label = __( 'Search', 'pods' );
		}

		$action = $params['action'];

		$search = trim( $params['search'] );

		if ( '' === $search ) {
			$search = sanitize_text_field( pods_v( $pod->search_var, 'get', '' ) );
		}

		ob_start();

		pods_view( PODS_DIR . 'ui/front/filters.php', compact( array_keys( get_defined_vars() ) ) );

		$output = ob_get_clean();

		/**
		 * Filter the HTML output of filters()
		 *
		 * @since unknown
		 *
		 * @param string $output Filter output.
		 * @param array  $params Params array passed to filters().
		 * @param Pods   $obj    Current Pods object.
		 */
		return apply_filters( 'pods_pods_filters', $output, $params, $this );
	}

	/**
	 * Run a helper within a Pod Page or WP Template.
	 *
	 * @param string|array $helper Helper name.
	 * @param string       $value  Value to run the helper on.
	 * @param string       $name   Field name.
	 *
	 * @return mixed Anything returned by the helper
	 * @since 2.0.0
	 */
	public function helper( $helper, $value = null, $name = null ) {

		$params = array(
			'helper'     => $helper,
			'value'      => $value,
			'name'       => $name,
			'deprecated' => false,
		);

		if ( class_exists( 'Pods_Templates' ) ) {
			$params['deprecated'] = Pods_Templates::$deprecated;
		}

		if ( is_array( $helper ) ) {
			$params = array_merge( $params, $helper );
		}

		/**
		 * Allows changing whether to include the Pods object as the second value to the callback.
		 *
		 * @param bool  $include_obj Whether to include the Pods object as the second value to the callback.
		 * @param array $params      Parameters used by Pods::helper() method.
		 *
		 * @since 2.8.0
		 */
		$include_obj = (boolean) apply_filters( 'pods_helper_include_obj', false, $params );

		// Clean up helper callback (if string).
		if ( is_string( $params['helper'] ) ) {
			$params['helper'] = strip_tags( str_replace( array( '`', chr( 96 ) ), "'", $params['helper'] ) );
		}

		if ( ! pods_access_callback_allowed( $params['helper'], $params ) ) {
			return $value;
		}

		if ( ! is_callable( $params['helper'] ) ) {
			if ( ! is_string( $params['helper'] ) ) {
				return '';
			}

			if ( $include_obj ) {
				return apply_filters( $params['helper'], $value, $this );
			}

			return apply_filters( $params['helper'], $value );
		}

		try {
			if ( $include_obj ) {
				return call_user_func( $params['helper'], $value, $this );
			}

			return call_user_func( $params['helper'], $value );
		} catch ( Throwable $exception ) {
			pods_debug_log( $exception );

			if ( pods_is_debug_display() ) {
				throw $exception;
			}
		} catch ( Exception $exception ) {
			pods_debug_log( $exception );

			if ( pods_is_debug_display() ) {
				throw $exception;
			}
		}

		return '';
	}

	/**
	 * Display the pod template (singular) in the context from within a fetch() loop.
	 *
	 * @see   Pods_Templates::template
	 *
	 * @param string      $template_name The template name.
	 * @param string|null $code          Custom template code to use instead.
	 * @param bool        $deprecated    Whether to use deprecated functionality based on old function usage.
	 *
	 * @return mixed Template output
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/template/
	 */
	public function template_singular( $template_name, $code = null, $deprecated = false ) {
		$old_id = $this->id;

		$this->id = $this->id();

		$out = $this->template( $template_name, $code, $deprecated );

		if ( ! empty( $old_id ) ) {
			$this->id = $old_id;
		}

		return $out;
	}

	/**
	 * Display the pod template.
	 *
	 * @see   Pods_Templates::template
	 *
	 * @param string      $template_name The template name.
	 * @param string|null $code          Custom template code to use instead.
	 * @param bool        $deprecated    Whether to use deprecated functionality based on old function usage.
	 * @param bool        $check_access  Whether to check access for Posts that are Password-protected.
	 *
	 * @return mixed Template output
	 *
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/template/
	 */
	public function template( $template_name, $code = null, $deprecated = false, $check_access = false ) {

		$out = null;

		$obj =& $this;

		if ( class_exists( 'Pods_Templates' ) ) {
			$out = Pods_Templates::template( $template_name, $code, $this, $deprecated, $check_access );
		} elseif ( ! empty( $code ) ) {
			// backwards compatibility.
			$code = str_replace( '$this->', '$obj->', $code );

			/**
			 * Filter the template code before running it.
			 *
			 * @param string $code          Template code.
			 * @param string $template_name Template name.
			 * @param Pods  $pod            Pods object.
			 */
			$code = apply_filters( 'pods_templates_pre_template', $code, $template_name, $this );

			/**
			 * Filter the template code before running it.
			 *
			 * @param string $code          Template code.
			 * @param string $template_name Template name.
			 * @param Pods  $pod            Pods object.
			 */
			$code = apply_filters( "pods_templates_pre_template_{$template_name}", $code, $template_name, $this );

			$info = $check_access ? pods_info_from_args( [ 'pods' => $this ] ) : [];

			ob_start();

			if ( ! empty( $code ) ) {
				// Only detail templates need $this->id.
				if ( empty( $this->id ) ) {
					$this->reset();

					while ( $this->fetch() ) {
						$info['item_id'] = $this->id();

						// Ensure the post is not password protected.
						if (
							$check_access
							&& (
								pods_access_bypass_post_with_password( $info )
								|| pods_access_bypass_private_post( $info )
							)
						) {
							continue;
						}

						// @codingStandardsIgnoreLine
						echo $this->do_magic_tags( $code );
					}
				} else {
					$info['item_id'] = $this->id();

					if (
						! $check_access
						|| (
							! pods_access_bypass_post_with_password( $info )
							&& ! pods_access_bypass_private_post( $info )
						)
					) {
						// @codingStandardsIgnoreLine
						echo $this->do_magic_tags( $code );
					}
				}
			}

			$out = ob_get_clean();

			/**
			 * Filter the template output.
			 *
			 * @param string $out           Template output.
			 * @param string $code          Template code.
			 * @param string $template_name Template name.
			 * @param Pods   $pod           Pods object.
			 */
			$out = apply_filters( 'pods_templates_post_template', $out, $code, $template_name, $this );

			/**
			 * Filter the template output.
			 *
			 * @param string $out           Template output.
			 * @param string $code          Template code.
			 * @param string $template_name Template name.
			 * @param Pods   $pod           Pods object.
			 */
			$out = apply_filters( "pods_templates_post_template_{$template_name}", $out, $code, $template_name, $this );

			/**
			 * Filter the final template output.
			 *
			 * @param string $out  Template output.
			 * @param string $code Template code.
			 * @param Pods   $pod  Pods object.
			 */
			$out = apply_filters( 'pods_templates_do_template', $out, $code, $this );
		} elseif ( trim( preg_replace( '/[^a-zA-Z0-9_\-\/]/', '', $template_name ), ' /-' ) === $template_name ) {
			ob_start();

			$default_templates = array(
				'pods/' . $template_name,
				'pods-' . $template_name,
				$template_name,
			);

			/**
			 * Filter the default Pods Template files.
			 *
			 * @param array $default_templates Default Pods Template files.
			 */
			$default_templates = apply_filters( 'pods_template_default_templates', $default_templates );

			// Only detail templates need $this->id.
			if ( empty( $this->id ) ) {
				while ( $this->fetch() ) {
					$info['item_id'] = $this->id();

					// Ensure the post is not password protected.
					if (
						$check_access
						&& (
							pods_access_bypass_post_with_password( $info )
							|| pods_access_bypass_private_post( $info )
						)
					) {
						continue;
					}

					pods_template_part( $default_templates, compact( array_keys( get_defined_vars() ) ) );
				}
			} else {
				$info['item_id'] = $this->id();

				if (
					! $check_access
					|| (
						! pods_access_bypass_post_with_password( $info )
						&& ! pods_access_bypass_private_post( $info )
					)
				) {
					pods_template_part( $default_templates, compact( array_keys( get_defined_vars() ) ) );
				}
			}

			$out = ob_get_clean();

			/**
			 * Filter the template output.
			 *
			 * @param string $out           Template output.
			 * @param string $code          Template code.
			 * @param string $template_name Template name.
			 * @param Pods   $pod           Pods object.
			 */
			$out = apply_filters( 'pods_templates_post_template', $out, $code, $template_name, $this );

			/**
			 * Filter the template output.
			 *
			 * @param string $out           Template output.
			 * @param string $code          Template code.
			 * @param string $template_name Template name.
			 * @param Pods   $pod           Pods object.
			 */
			$out = apply_filters( "pods_templates_post_template_{$template_name}", $out, $code, $template_name, $this );
		}//end if

		/**
		 * Filter the final content.
		 *
		 * @since 2.8.0
		 *
		 * @param string $out  The template content.
		 * @param string $code The original template code.
		 * @param Pods   $pod  The current Pods object.
		 */
		return apply_filters( 'pods_template_content', $out, $code, $this );
	}

	/**
	 * Embed a form to add / edit a pod item from within your theme. Provide an array of $fields to include
	 * and override options where needed. For WP object based Pods, you can pass through the WP object
	 * field names too, such as "post_title" or "post_content" for example.
	 *
	 * @param array  $params    (optional) Fields to show on the form, defaults to all fields.
	 * @param string $label     (optional) Save button label, defaults to "Save Changes".
	 * @param string $thank_you (optional) Thank you URL to send to upon success.
	 *
	 * @return bool|mixed
	 * @since 2.0.0
	 * @link  https://docs.pods.io/code/pods/form/
	 */
	public function form( $params = null, $label = null, $thank_you = null ) {
		// Only show placeholder text if in REST API block preview.
		if ( wp_is_json_request() && did_action( 'rest_api_init' ) ) {
			return '<em>[' . esc_html__( 'This is a placeholder. Filters and form fields are not included in block previews.', 'pods' ) . ']</em>';
		}

		// Check for anonymous submissions.
		if ( ! is_user_logged_in() ) {
			$session_auto_start = pods_session_auto_start();

			// Check if Pods sessions are disabled.
			if ( false === $session_auto_start ) {
				return sprintf(
					'<p><strong>%1$s:</strong> %2$s, <a href="%3$s">%4$s</a> %5$s.</p>',
					esc_html__( 'Error', 'pods' ),
					esc_html__( 'Anonymous form submissions are not enabled for this site', 'pods' ),
					esc_url( wp_login_url( pods_current_url() ) ),
					esc_html__( 'try logging in first', 'pods' ),
					esc_html__( 'or contacting your site administrator', 'pods' )
				);
			}

			// Check if we need to turn on sessions and have visitor refresh page.
			if ( 'auto' === $session_auto_start ) {
				pods_update_setting( 'pods_session_auto_start', '1' );

				return sprintf(
					'<p><strong>%1$s:</strong> %2$s</p>',
					esc_html__( 'Error', 'pods' ),
					esc_html__( 'Please refresh the page to access this form.', 'pods' )
				);
			}

			// Check if the session started properly.
			if ( '' === pods_session_id() ) {
				return sprintf(
					'<p><strong>%1$s:</strong> %2$s</p>',
					esc_html__( 'Error', 'pods' ),
					esc_html__( 'Anonymous form submissions are not compatible with sessions on this site.', 'pods' )
				);
			}
		}

		$defaults = [
			'fields'       => $params,
			'label'        => $label,
			'thank_you'    => $thank_you,
			'fields_only'  => false,
			'output_type'  => 'div',
			'check_access' => true,
			'form_key'     => null,
		];

		if ( is_array( $params ) ) {
			$params = array_merge( $defaults, $params );
		} else {
			$params = $defaults;
		}

		$access_type = $this->exists() ? 'edit' : 'add';

		$return = '';

		// Check if the current user has access to the object.
		if ( ! empty( $params['check_access'] ) ) {
			$dynamic_feature_unrestricted = pods_can_use_dynamic_feature_unrestricted(
				[
					'pods' => $this,
				],
				'form',
				$access_type
			);

			if (
				! pods_current_user_can_access_object(
					[
						'pods' => $this,
					],
					$access_type,
					$params['form_key']
				)
				&& ! $dynamic_feature_unrestricted
			) {
				// Stop display and only return the notice.
				return pods_get_access_user_notice(
					[
						'pods' => $this,
					],
					false,
					esc_html__( 'You do not have access to this embedded form.', 'pods' )
				) ?: '';
			}

			// Show the admin-specific notice that this content may not be visible to others since it is not public.
			if ( ! $dynamic_feature_unrestricted && pods_is_admin() ) {
				// Include the notice in the display output to let the admin know and continue the display.
				$return .= pods_get_access_admin_notice(
					[
						'pods' => $this,
					]
				) ?: '';
			}
		}

		$pod =& $this;

		$params = $this->do_hook( 'form_params', $params );

		$form_fields = $params['fields'];

		if ( null !== $form_fields && is_string( $form_fields ) && 0 < strlen( $form_fields ) ) {
			$form_fields = explode( ',', $form_fields );
		}

		$all_fields = pods_config_get_all_fields( $this->pod_data );

		$default_form = false;

		// Get all fields if form fields are empty.
		if ( empty( $form_fields ) ) {
			$form_fields = $all_fields;

			$default_form = true;
		}

		$fields = [];

		foreach ( $form_fields as $k => $field ) {
			$name = $k;

			$defaults = [
				'name' => $name,
			];

			if ( is_string( $field ) ) {
				$name = $field;

				$field = [
					'name' => $name,
				];
			}

			$field = pods_config_merge_data( $defaults, $field );

			$field['name'] = trim( $field['name'] );

			$default_value = pods_v( 'default', $field );
			$value         = pods_v( 'value', $field );

			if ( empty( $field['name'] ) ) {
				$field['name'] = trim( $name );
			}

			$to_merge = $this->pod_data->get_field( $field['name'] );

			if ( $to_merge ) {
				$field = pods_config_merge_data( $to_merge, $field );

				// Override the name field as the alias should not be used.
				$field['name'] = $to_merge['name'];
			}

			// Never show the ID field.
			if ( $this->pod_data['field_id'] === $field['name'] ) {
				continue;
			}

			// Hide fields from default form.
			if ( 1 === (int) pods_v( 'hide_in_default_form', $field ) ) {
				continue;
			}

			if ( pods_v( 'hidden', $field, false, true ) ) {
				$field['type'] = 'hidden';
			}

			$fields[ $field['name'] ] = $field;

			if ( empty( $this->id ) && null !== $default_value ) {
				$this->row_override[ $field['name'] ] = $default_value;
			} elseif ( ! empty( $this->id ) && null !== $value ) {
				$this->data->row[ $field['name'] ] = $value;
			}
		}//end foreach

		// Cleanup before get_defined_vars().
		unset( $form_fields );

		$fields = $this->do_hook( 'form_fields', $fields, $params );

		$label = $params['label'];

		if ( empty( $label ) ) {
			$label = __( 'Save Changes', 'pods' );
		}

		$thank_you   = $params['thank_you'];
		$fields_only = $params['fields_only'];
		$output_type = $params['output_type'];

		// Sanitize thank_you for security.
		if ( ! empty( $thank_you ) ) {
			// Additional sanitization.
			$thank_you = sanitize_text_field( $thank_you );

			// Fallback to '' so that the logic below can kick in if the thank you URL was not safe.
			$thank_you = pods_enforce_safe_url( $thank_you, '' );
		}

		if ( empty( $output_type ) ) {
			$output_type = 'div';
		}

		PodsForm::$form_counter ++;

		ob_start();

		if ( empty( $thank_you ) ) {
			$success = 'success';

			if ( 1 < PodsForm::$form_counter ) {
				$success .= PodsForm::$form_counter;
			}

			$thank_you = pods_query_arg( array(
				'success*' => null,
				$success   => 1,
			) );

			if ( 1 === (int) pods_v( $success, 'get', 0 ) ) {
				$message = __( 'Form submitted successfully', 'pods' );

				/**
				 * Change the text of the message that appears on successful form submission.
				 *
				 * @param string $message Success message.
				 *
				 * @since 2.7.0
				 */
				$message = apply_filters( 'pods_pod_form_success_message', $message );

				echo '<div id="message" class="pods-form-front-success">' . wp_kses_post( $message ) . '</div>';
			}
		}//end if

		$id = $this->id();

		pods_view( PODS_DIR . 'ui/front/form.php', compact( array_keys( get_defined_vars() ) ) );

		$output = $return . ob_get_clean();

		if ( empty( $this->id ) ) {
			$this->row_override = array();
		}

		return $this->do_hook( 'form', $output, $fields, $label, $thank_you, $this, $this->id() );
	}

	/**
	 * Render a singular view for the Pod item content.
	 *
	 * @param array|string|null $view_fields (optional) Fields to show in the view, defaults to all fields.
	 *
	 * @return mixed
	 * @since 2.3.10
	 */
	public function view( $view_fields = null ) {

		$pod =& $this;

		// Convert comma separated list of fields to an array.
		if ( null !== $view_fields && is_string( $view_fields ) && 0 < strlen( $view_fields ) ) {
			$view_fields = explode( ',', $view_fields );
		}

		$all_fields = pods_config_get_all_fields( $this->pod_data );

		// Get all fields if fields are empty.
		if ( empty( $view_fields ) ) {
			$view_fields = $all_fields;
		}

		$fields = [];

		foreach ( $view_fields as $name => $field ) {
			$defaults = [
				'name' => $name,
			];

			$is_field_object = $field instanceof Field;

			if ( ! is_array( $field ) && ! $is_field_object ) {
				$name = $field;

				$field = [
					'name' => $name,
				];
			}

			$field = pods_config_merge_data( $defaults, $fields );

			$field['name'] = trim( $field['name'] );

			if ( empty( $field['name'] ) ) {
				$field['name'] = trim( $name );
			}

			$to_merge = $this->pod_data->get_field( $field['name'] );

			if ( $to_merge ) {
				$field = pods_config_merge_data( $to_merge, $field );

				// Override the name field as the alias should not be used.
				$field['name'] = $to_merge['name'];
			}

			if ( pods_v( 'hidden', $field, false, true ) || 'hidden' === $field['type'] ) {
				continue;
			} elseif ( ! pods_permission( $field ) ) {
				continue;
			}

			$fields[ $field['name'] ] = $field;
		}//end foreach

		// Cleanup before get_defined_vars().
		unset( $view_fields );

		$output = pods_view( PODS_DIR . 'ui/front/view.php', compact( array_keys( get_defined_vars() ) ), false, 'cache', true );

		return $this->do_hook( 'view', $output, $fields, $this->id() );

	}

	/**
	 * Replace magic tags with their values
	 *
	 * @param string $code The content to evaluate.
	 *
	 * @return string Code with Magic Tags evaluated
	 *
	 * @since 2.0.0
	 */
	public function do_magic_tags( $code ) {

		/**
		 * Filters the Pods magic tags content before the default function.
		 * Allows complete replacement of the Pods magic tag engine.
		 *
		 * @param null   $pre  Default is null which processes magic tags normally. Return any other value to override.
		 * @param string $code The content to evaluate.
		 * @param Pods   $pods The Pods Object.
		 *
		 * @since 2.7.0
		 */
		$pre = apply_filters( 'pods_pre_do_magic_tags', null, $code, $this );
		if ( null !== $pre ) {
			return $pre;
		}

		return preg_replace_callback( '/({@(.*?)})/m', array( $this, 'process_magic_tags' ), $code );
	}

	/**
	 * Replace magic tags with their values
	 *
	 * @param string|array $tag The magic tag to process.
	 *
	 * @return string Code with Magic Tags evaluated
	 *
	 * @since 2.0.2
	 */
	private function process_magic_tags( $tag ) {

		if ( is_array( $tag ) ) {
			if ( ! isset( $tag[2] ) && '' === trim( $tag[2] ) ) {
				return '';
			}

			$tag = $tag[2];
		}

		$tag = trim( $tag, ' {@}' );
		$tag = explode( ',', $tag );

		if ( empty( $tag ) || ! isset( $tag[0] ) || '' === trim( $tag[0] ) ) {
			return '';
		}

		$tag = pods_trim( $tag );

		$field_name = $tag[0];

		$helper_name = '';
		$before      = '';
		$after       = '';

		if ( ! empty( $tag[1] ) ) {
			$value = $this->field( $field_name );

			$helper_name = $tag[1];

			$value = $this->helper( $helper_name, $value, $field_name );
		} else {
			$value = $this->display( $field_name );
		}

		// Process special magic tags but allow "empty" values for numbers.
		if (
			! $value
			&& ! is_numeric( $value )
			&& pods_shortcode_allow_evaluate_tags()
			&& ! $this->fields( $field_name )
		) {
			// Do not pass before and after tags (key 2 and 3) or these get processed twice.
			$value = pods_evaluate_tag( implode( ',', array_slice( $tag, 0, 2 ) ) );
		}

		if ( ! empty( $tag[2] ) ) {
			$before = $tag[2];
		}

		if ( ! empty( $tag[3] ) ) {
			$after = $tag[3];
		}

		/**
		 * Filter the magic tag output for a value.
		 *
		 * @param string $value       Magic tag output for value.
		 * @param string $field_name  Magic tag field name.
		 * @param string $helper_name The helper name.
		 * @param string $before      Before content.
		 * @param string $after       After content.
		 */
		$value = apply_filters( 'pods_do_magic_tags', $value, $field_name, $helper_name, $before, $after );

		if ( is_array( $value ) ) {
			$value = pods_serial_comma( $value, array(
				'field'  => $field_name,
				'fields' => $this->fields,
			) );
		}

		if ( $value || is_numeric( $value ) ) {
			return $before . $value . $after;
		}

		return '';
	}

	/**
	 *
	 * Generate UI for Data Management
	 *
	 * @param mixed $options Array or String containing Pod or Options to be used.
	 * @param bool  $amend   Whether to amend the default UI options or replace entirely.
	 *
	 * @return PodsUI|null UI object or null if custom UI used
	 *
	 * @since 2.3.10
	 */
	public function ui( $options = null, $amend = false ) {

		$num        = '';
		$num_prefix = '';

		if ( empty( $options ) ) {
			$options = array();
		} else {
			$num = pods_v_sanitized( 'num', $options, '' );

			if ( empty( $num ) ) {
				$num = '';
			}

			$num_prefix = pods_v_sanitized( 'num_prefix', $options, '' );

			if ( empty( $num_prefix ) ) {
				$num_prefix = '';
			}
		}

		$check_id = pods_v( $num_prefix . 'id' . $num, 'get', null, true );

		// @codingStandardsIgnoreLine
		if ( null !== $check_id && $this->id() != $check_id ) {
			$this->fetch( $check_id );
		}

		if ( ! empty( $options ) && ! $amend ) {
			$this->ui = $options;

			return pods_ui( $this );
		} elseif ( ! empty( $options ) || 'custom' !== pods_v( 'ui_style', $this->pod_data, 'post_type', true ) ) {
			$actions_enabled = pods_v( 'ui_actions_enabled', $this->pod_data );

			if ( ! empty( $actions_enabled ) ) {
				$actions_enabled = (array) $actions_enabled;
			} else {
				$actions_enabled = array();
			}

			$available_actions = array(
				'add',
				'edit',
				'duplicate',
				'delete',
				'reorder',
				'export',
			);

			if ( ! empty( $actions_enabled ) ) {
				$actions_disabled = array(
					'view' => 'view',
				);

				foreach ( $available_actions as $action ) {
					if ( ! in_array( $action, $actions_enabled, true ) ) {
						$actions_disabled[ $action ] = $action;
					}
				}
			} else {
				$actions_disabled = array(
					'duplicate' => 'duplicate',
					'view'      => 'view',
					'export'    => 'export',
				);

				if ( 1 === pods_v( 'ui_export', $this->pod_data, 0 ) ) {
					unset( $actions_disabled['export'] );
				}
			}//end if

			if ( empty( $options ) ) {
				$author_restrict = false;

				if ( isset( $this->fields['author'] ) && 'pick' === $this->fields['author']['type'] && 'user' === $this->fields['author']['pick_object'] ) {
					$author_restrict = 'author.ID';
				}

				if ( ! pods_is_admin( array( 'pods', 'pods_content' ) ) ) {
					if ( ! current_user_can( 'pods_add_' . $this->pod ) ) {
						$actions_disabled['add'] = 'add';

						if ( 'add' === pods_v( $num_prefix . 'action' . $num ) ) {
							$_GET[ $num_prefix . 'action' . $num ] = 'manage';
						}
					}

					if ( ! $author_restrict && ! current_user_can( 'pods_edit_' . $this->pod ) && ! current_user_can( 'pods_edit_others_' . $this->pod ) ) {
						$actions_disabled['edit'] = 'edit';
					}

					if ( ! $author_restrict && ! current_user_can( 'pods_delete_' . $this->pod ) && ! current_user_can( 'pods_delete_others_' . $this->pod ) ) {
						$actions_disabled['delete'] = 'delete';
					}

					if ( ! current_user_can( 'pods_reorder_' . $this->pod ) ) {
						$actions_disabled['reorder'] = 'reorder';
					}

					if ( ! current_user_can( 'pods_export_' . $this->pod ) ) {
						$actions_disabled['export'] = 'export';
					}
				}//end if
			}//end if

			$_GET[ $num_prefix . 'action' . $num ] = pods_v_sanitized( $num_prefix . 'action' . $num, 'get', pods_v( 'action', $options, 'manage' ), true );

			$index = $this->pod_data['field_id'];
			$label = __( 'ID', 'pods' );

			if ( isset( $this->pod_data['fields'][ $this->pod_data['field_index'] ] ) ) {
				$index = $this->pod_data['field_index'];
				$label = $this->pod_data['fields'][ $this->pod_data['field_index'] ];
			}

			$manage = array(
				$index => $label,
			);

			if ( isset( $this->pod_data['fields']['modified'] ) ) {
				$manage['modified'] = $this->pod_data['fields']['modified']['label'];
			}

			$manage_fields = (array) pods_v( 'ui_fields_manage', $this->pod_data );

			if ( ! empty( $manage_fields ) ) {
				$manage_new = array();

				foreach ( $manage_fields as $manage_field ) {
					if ( isset( $this->pod_data['fields'][ $manage_field ] ) ) {
						$manage_new[ $manage_field ] = $this->pod_data['fields'][ $manage_field ];
					} elseif ( isset( $this->pod_data['object_fields'][ $manage_field ] ) ) {
						$manage_new[ $manage_field ] = $this->pod_data['object_fields'][ $manage_field ];
					} elseif ( $manage_field === $this->pod_data['field_id'] ) {
						$field = array(
							'name'  => $manage_field,
							'label' => 'ID',
							'type'  => 'number',
							'width' => '8%',
						);

						$manage_new[ $manage_field ] = PodsForm::field_setup( $field, null, $field['type'] );
					}
				}

				if ( ! empty( $manage_new ) ) {
					$manage = $manage_new;
				}
			}//end if

			$pod_name = $this->pod;
			$manage   = apply_filters( "pods_admin_ui_fields_{$pod_name}", apply_filters( 'pods_admin_ui_fields', $manage, $this->pod, $this ), $this->pod, $this );

			$icon = pods_v( 'ui_icon', $this->pod_data );

			if ( ! empty( $icon ) ) {
				$icon = pods_image_url( $icon, '32x32' );
			}

			$filters = pods_v( 'ui_filters', $this->pod_data );

			if ( ! empty( $filters ) ) {
				$filters_new = array();

				$filters = (array) $filters;

				foreach ( $filters as $filter_field ) {
					if ( isset( $this->pod_data['fields'][ $filter_field ] ) ) {
						$filters_new[ $filter_field ] = $this->pod_data['fields'][ $filter_field ];
					} elseif ( isset( $this->pod_data['object_fields'][ $filter_field ] ) ) {
						$filters_new[ $filter_field ] = $this->pod_data['object_fields'][ $filter_field ];
					}
				}

				$filters = $filters_new;
			}

			$ui = array(
				'fields'           => array(
					'manage'    => $manage,
					'add'       => $this->pod_data['fields'],
					'edit'      => $this->pod_data['fields'],
					'duplicate' => $this->pod_data['fields'],
				),
				'icon'             => $icon,
				'actions_disabled' => $actions_disabled,
				'actions_bulk'     => array(),
			);

			if ( ! empty( $filters ) ) {
				$ui['fields']['search'] = $filters;
				$ui['filters']          = array_keys( $filters );
				$ui['filters_enhanced'] = true;
			}

			$reorder_field = pods_v( 'ui_reorder_field', $this->pod_data );

			if ( ! empty( $reorder_field ) && in_array( 'reorder', $actions_enabled, true ) && ! in_array( 'reorder', $actions_disabled, true ) && ( ( ! empty( $this->pod_data['object_fields'] ) && isset( $this->pod_data['object_fields'][ $reorder_field ] ) ) || isset( $this->pod_data['fields'][ $reorder_field ] ) ) ) {
				$ui['reorder']     = array( 'on' => $reorder_field );
				$ui['orderby']     = $reorder_field;
				$ui['orderby_dir'] = 'ASC';
			}

			if ( ! empty( $author_restrict ) ) {
				$ui['restrict'] = array( 'author_restrict' => $author_restrict );
			}

			if ( ! in_array( 'export', $ui['actions_disabled'], true ) ) {
				$ui['actions_bulk']['export'] = array(
					'label' => __( 'Export', 'pods' ),
					// callback not needed, Pods has this built-in for export.
				);
			}

			if ( ! in_array( 'delete', $ui['actions_disabled'], true ) ) {
				$ui['actions_bulk']['delete'] = array(
					'label' => __( 'Delete', 'pods' ),
					// callback not needed, Pods has this built-in for delete.
				);
			}

			$detail_url = pods_v( 'detail_url', $this->pod_data );

			if ( is_string( $detail_url ) && 0 < strlen( $detail_url ) ) {
				$ui['actions_custom'] = array(
					'view_url' => array(
						'label' => 'View',
						'link'  => get_site_url() . '/' . $detail_url,
					),
				);
			}

			// @todo Customize the Add New / Manage links to point to their correct menu items.
			$pod_name = $this->pod;
			$ui       = apply_filters( "pods_admin_ui_{$pod_name}", apply_filters( 'pods_admin_ui', $ui, $this->pod, $this ), $this->pod, $this );

			// Override UI options.
			foreach ( $options as $option => $value ) {
				$ui[ $option ] = $value;
			}

			$this->ui = $ui;

			return pods_ui( $this );
		}//end if

		$pod_name = $this->pod;
		do_action( 'pods_admin_ui_custom', $this );
		do_action( "pods_admin_ui_custom_{$pod_name}", $this );

		return null;
	}

	/**
	 * Handle filters / actions for the class
	 *
	 * @see   pods_do_hook
	 *
	 * @param string $name Hook name.
	 *
	 * @return mixed Value filtered
	 *
	 * @since 2.0.0
	 */
	private function do_hook( $name ) {

		$args = func_get_args();

		if ( empty( $args ) ) {
			return false;
		}

		// Remove first argument.
		array_shift( $args );

		return pods_do_hook( 'pods', $name, $args, $this );
	}

	/**
	 * Handle variables that have been deprecated and PodsData vars
	 *
	 * @param string $name Property name.
	 *
	 * @return mixed
	 *
	 * @since 2.0.0
	 */
	#[\ReturnTypeWillChange]
	public function __get( $name ) {
		$name = (string) $name;

		// Handle PodsData properties.
		$supported_pods_data = array(
			'rows',
			'row',
			'pagination',
			'page',
			'page_var',
			'search',
			'search_var',
			'filter_var',
			'search_mode',
			'api',
			'row_number',
			'params',
			'filters',
			'id',
			'limit',
			'offset',
			'total',
			'total_found',
			'sql',
		);

		if ( in_array( $name, $supported_pods_data, true ) ) {
			return $this->data->{$name};
		}

		// Handle alias Pods\Whatsit\Pod properties.
		$supported_pods_object = array(
			'pod'         => 'name',
			'pod_id'      => 'id',
			'fields'      => 'fields',
			'detail_page' => 'detail_url',
			'detail_url'  => 'detail_url',
		);

		if ( isset( $supported_pods_object[ $name ] ) ) {
			if ( ! is_object( $this->pod_data ) ) {
				return null;
			}

			return pods_v( $supported_pods_object[ $name ], $this->pod_data );
		}

		// Handle sending previously mapped PodsData properties directly to their correct place.
		if ( 0 === strpos( $name, 'field_' ) ) {
			if ( ! is_object( $this->pod_data ) ) {
				return null;
			}

			return pods_v( $name, $this->pod_data );
		}

		return null;
	}

	/**
	 * Handle variables that have been deprecated and PodsData vars
	 *
	 * @param string $name  Property name.
	 * @param mixed  $value Property value.
	 *
	 * @since 2.8.0
	 */
	public function __set( $name, $value ): void {
		$name = (string) $name;

		$supported_pods_data = array(
			'rows'        => 'array',
			'row'         => 'array',
			'pagination'  => 'boolean',
			'page'        => 'int',
			'page_var'    => 'string',
			'search'      => 'boolean',
			'search_var'  => 'string',
			'filter_var'  => 'string',
			'search_mode' => 'string',
			'id'          => 'int',
		);

		if ( isset( $supported_pods_data[ $name ] ) ) {
			// Cast the value.
			settype( $value, $supported_pods_data[ $name ] );

			$this->data->{$name} = $value;
		}
	}

	/**
	 * Handle variables that have been deprecated and PodsData vars
	 *
	 * @param string $name Property name.
	 *
	 * @return bool Whether the variable is set or not.
	 *
	 * @since 2.8.0
	 */
	public function __isset( $name ): bool {
		$value = $this->__get( $name );

		return ( null !== $value );
	}

	/**
	 * Handle variables that have been deprecated and PodsData vars
	 *
	 * @param string $name Property name.
	 *
	 * @since 2.8.0
	 */
	public function __unset( $name ): void {
		$this->__set( $name, null );
	}

	/**
	 * Handle methods that have been deprecated and any aliasing.
	 *
	 * @param string $name Function name.
	 * @param array  $arguments Arguments passed to function.
	 *
	 * @return mixed|null
	 *
	 * @since 2.0.0
	 */
	#[\ReturnTypeWillChange]
	public function __call( $name, $arguments ) {

		$name = (string) $name;

		// select > find alias.
		if ( 'select' === $name ) {
			return call_user_func_array( array( $this, 'find' ), $arguments );
		}

		if ( ! $this->deprecated ) {
			require_once PODS_DIR . 'deprecated/classes/Pods.php';

			$this->deprecated = new Pods_Deprecated( $this );
		}

		$pod_class_exists = class_exists( 'Deprecated_Pod' );

		if ( method_exists( $this->deprecated, $name ) ) {
			return call_user_func_array( array( $this->deprecated, $name ), $arguments );
			// @codingStandardsIgnoreLine
		} elseif ( ! $pod_class_exists || Deprecated_Pod::$deprecated_notice ) {
			pods_deprecated( "Pods::{$name}", '2.0' );
		}

		return null;
	}

	/**
	 * Handle casting a Pods() object to string
	 *
	 * @return string Pod type and name in CURIE notation
	 */
	public function __toString(): string {

		$string = '';

		if ( ! empty( $this->pod_data ) ) {
			$string = sprintf( '%s:%s', $this->pod_data['type'], $this->pod_data['name'] );
		}

		return $string;

	}
}
