src/properties/class-papi-property-repeater.php
public function update_value( $values, $repeater_slug, $post_id ) {
		$rows = intval( papi_data_get( $post_id, $repeater_slug, $this->get_meta_type() ) );

		if ( ! is_array( $values ) ) {
			$values = [];
		}

		list( $results, $trash ) = $this->get_results( $rows, $repeater_slug, $post_id );

		// Delete trash values.
		foreach ( $trash as $meta ) {
			papi_data_delete( $post_id, $meta->meta_key, $this->get_meta_type() );
		}

		$top_property = new Papi_Core_Property;

		foreach ( $values as $index => $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}

			$top_property->slug = $repeater_slug . '_' . $index;

			foreach ( $row as $slug => $value ) {
				if ( papi_is_property_type_key( $slug ) ) {
					continue;
				}

				// Property type slug may have a prefixed underscore.
				$property_type_slug = papi_get_property_type_key_f( $slug );

				if ( ! isset( $row[$property_type_slug] ) ) {
					// If property type slug isn't prefixed we should try without.
					$property_type_slug = papi_get_property_type_key( $slug );

					if ( ! isset( $row[$property_type_slug] ) ) {
						continue;
					}
				}

				if ( ! isset( $row[$property_type_slug] ) ) {
					continue;
				}

				// Get real property slug
				$property_slug = $this->get_child_slug( $repeater_slug, $slug );

				// Get property type
				$property_type_value = $row[$property_type_slug]->type;
				$property_type       = papi_get_property_type( $row[$property_type_slug] );

				if ( ! is_object( $property_type ) ) {
					continue;
				}

				// Set top property.
				$property_type->set_parent_property( $top_property );

				// Create cache key.
				$cache_key = sprintf( '%s_%s', $top_property->slug, $slug );
				if ( $parent_property = $this->get_parent_property() ) {
					$cache_key = sprintf( '%s_%s', $parent_property->get_slug( true ), $cache_key );
				}

				// Delete cache if any.
				papi_cache_delete( $cache_key, $post_id, papi_get_meta_type() );

				// Unserialize if needed.
				$value = papi_maybe_json_decode( maybe_unserialize( $value ) );

				// Run update value on each property type class.
				$value = $property_type->update_value( $value, $property_slug, $post_id );

				// Run update value on each property type filter.
				$row[$slug] = papi_filter_update_value( $property_type_value, $value, $property_slug, $post_id, papi_get_meta_type() );

				if ( is_array( $row[$slug] ) ) {
					foreach ( $row[$slug] as $key => $val ) {
						if ( ! is_string( $key ) ) {
							continue;
						}

						unset( $row[$slug][$key] );

						$key = preg_replace( '/^\_/', '', $key );

						$row[$slug][$key] = $val;
					}
				}
			}

			// Remove unnecessary property type keys if any is left.
			foreach ( $row as $slug => $value ) {
				if ( papi_is_property_type_key( $slug ) ) {
					unset( $row[$slug] );
				}
			}

			$values[$index] = $row;
		}

		$values = papi_property_to_array_slugs( $values, $repeater_slug );

		// Find out which keys that should be deleted.
		$trash = array_diff( array_keys( papi_to_array( $results ) ), array_keys( papi_to_array( $values ) ) );

		// Delete unwanted (trash) values.
		foreach ( array_keys( $trash ) as $trash_key ) {
			papi_data_delete( $post_id, $trash_key, $this->get_meta_type() );
		}

		// It's safe to remove all rows in the database here.
		$this->remove_repeater_rows( $post_id, $repeater_slug );

		return $values;
	}
public function update_value( $values, $repeater_slug, $post_id ) {
		$rows = intval( papi_data_get( $post_id, $repeater_slug, $this->get_meta_type() ) );

		if ( ! is_array( $values ) ) {
			$values = [];
		}

		list( $results, $trash ) = $this->get_results( $rows, $repeater_slug, $post_id );

		// Delete trash values.
		foreach ( $trash as $meta ) {
			papi_data_delete( $post_id, $meta->meta_key, $this->get_meta_type() );
		}

		$top_property = new Papi_Core_Property;

		foreach ( $values as $index => $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}

			$top_property->slug = $repeater_slug . '_' . $index;

			foreach ( $row as $slug => $value ) {
				if ( papi_is_property_type_key( $slug ) ) {
					continue;
				}

				// Property type slug may have a prefixed underscore.
				$property_type_slug = papi_get_property_type_key_f( $slug );

				if ( ! isset( $row[$property_type_slug] ) ) {
					// If property type slug isn't prefixed we should try without.
					$property_type_slug = papi_get_property_type_key( $slug );

					if ( ! isset( $row[$property_type_slug] ) ) {
						continue;
					}
				}

				if ( ! isset( $row[$property_type_slug] ) ) {
					continue;
				}

				// Get real property slug
				$property_slug = $this->get_child_slug( $repeater_slug, $slug );

				// Get property type
				$property_type_value = $row[$property_type_slug]->type;
				$property_type       = papi_get_property_type( $row[$property_type_slug] );

				if ( ! is_object( $property_type ) ) {
					continue;
				}

				// Set top property.
				$property_type->set_parent_property( $top_property );

				// Create cache key.
				$cache_key = sprintf( '%s_%s', $top_property->slug, $slug );
				if ( $parent_property = $this->get_parent_property() ) {
					$cache_key = sprintf( '%s_%s', $parent_property->get_slug( true ), $cache_key );
				}

				// Delete cache if any.
				papi_cache_delete( $cache_key, $post_id, papi_get_meta_type() );

				// Unserialize if needed.
				$value = papi_maybe_json_decode( maybe_unserialize( $value ) );

				// Run update value on each property type class.
				$value = $property_type->update_value( $value, $property_slug, $post_id );

				// Run update value on each property type filter.
				$row[$slug] = papi_filter_update_value( $property_type_value, $value, $property_slug, $post_id, papi_get_meta_type() );

				if ( is_array( $row[$slug] ) ) {
					foreach ( $row[$slug] as $key => $val ) {
						if ( ! is_string( $key ) ) {
							continue;
						}

						unset( $row[$slug][$key] );

						$key = preg_replace( '/^\_/', '', $key );

						$row[$slug][$key] = $val;
					}
				}
			}

			// Remove unnecessary property type keys if any is left.
			foreach ( $row as $slug => $value ) {
				if ( papi_is_property_type_key( $slug ) ) {
					unset( $row[$slug] );
				}
			}

			$values[$index] = $row;
		}

		$values = papi_property_to_array_slugs( $values, $repeater_slug );

		// Find out which keys that should be deleted.
		$trash = array_diff( array_keys( papi_to_array( $results ) ), array_keys( papi_to_array( $values ) ) );

		// Delete unwanted (trash) values.
		foreach ( array_keys( $trash ) as $trash_key ) {
			papi_data_delete( $post_id, $trash_key, $this->get_meta_type() );
		}

		// It's safe to remove all rows in the database here.
		$this->remove_repeater_rows( $post_id, $repeater_slug );

		return $values;
	}
Update value before it's saved to the database.

@param mixed  $values
@param string $repeater_slug
@param int    $post_id

@return array