$fields Fields in display order. * * @return array The steps. */ public function resolve( array $fields ): array { $groups = array(); $current = array( 'title' => '', 'description' => '', 'fields' => array(), ); foreach ( $fields as $field ) { if ( ! $field instanceof Field ) { continue; } if ( FieldTypes::STEP !== $field->type() ) { $current['fields'][] = $field; continue; } $groups[] = $current; $current = array( 'title' => $field->label(), 'description' => $field->description(), 'fields' => array(), ); } $groups[] = $current; $steps = array(); /* * Empty groups are dropped rather than rendered. A break before the first * field only titles the step that follows it, two breaks in a row are one * break, and a trailing break adds nothing: none of those should give the * visitor a page with nothing on it and a Next button. * * The indexes are handed out here, after the dropping, because they are * what the browser sends back and what the step validation looks up. The * renderer and the endpoint both come through this method, so neither can * end up counting steps the other does not. */ foreach ( $groups as $group ) { if ( array() === $group['fields'] ) { continue; } $steps[] = new Step( count( $steps ), $group['title'], $group['description'], $group['fields'] ); } return $steps; } /** * Determines whether a field list produces more than one step. * * @param array $fields Fields in display order. * * @return bool True when the form is multi-step. */ public function is_multi_step( array $fields ): bool { return count( $this->resolve( $fields ) ) > 1; } /** * Returns the fields of one step. * * @param array $fields Fields in display order. * @param int $step Zero based step index. * * @return array The fields, empty when the step does not exist. */ public function fields_for_step( array $fields, int $step ): array { $steps = $this->resolve( $fields ); return isset( $steps[ $step ] ) ? $steps[ $step ]->fields() : array(); } }