$field['dynamic_post_type'], 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC', ], $field, $form_id ), true ); foreach ( $posts as $post ) { $choices[] = [ 'value' => $post->ID, 'label' => wpforms_get_post_title( $post ), 'depth' => isset( $post->depth ) ? absint( $post->depth ) : 1, ]; } } elseif ( $field['dynamic_choices'] === 'taxonomy' ) { if ( empty( $field['dynamic_taxonomy'] ) ) { return false; } $terms = wpforms_get_hierarchical_object( apply_filters( 'wpforms_dynamic_choice_taxonomy_args', [ 'taxonomy' => $field['dynamic_taxonomy'], 'hide_empty' => false, ], $field, $form_data ), true ); foreach ( $terms as $term ) { $choices[] = [ 'value' => $term->term_id, 'label' => wpforms_get_term_name( $term ), 'depth' => isset( $term->depth ) ? absint( $term->depth ) : 1, ]; } } return $choices; } /** * Build and return either a taxonomy or post type object that is * nested to accommodate any hierarchy. * * @since 1.3.9 * @since 1.5.0 Return array only. Empty array of no data. * * @param array $args Object arguments to pass to data retrieval function. * @param bool $flat Preserve hierarchy or not. False by default - preserve it. * * @return array */ function wpforms_get_hierarchical_object( $args = [], $flat = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded if ( empty( $args['taxonomy'] ) && empty( $args['post_type'] ) ) { return []; } $children = []; $parents = []; $ref_parent = ''; $ref_name = ''; $number = 0; if ( ! empty( $args['post_type'] ) ) { $defaults = [ 'posts_per_page' => - 1, 'orderby' => 'title', 'order' => 'ASC', ]; $args = wp_parse_args( $args, $defaults ); $items = get_posts( $args ); $ref_parent = 'post_parent'; $ref_id = 'ID'; $ref_name = 'post_title'; $number = ! empty( $args['posts_per_page'] ) ? $args['posts_per_page'] : 0; } elseif ( ! empty( $args['taxonomy'] ) ) { $defaults = [ 'hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC', ]; $args = wp_parse_args( $args, $defaults ); $items = get_terms( $args ); $ref_parent = 'parent'; $ref_id = 'term_id'; $ref_name = 'name'; $number = ! empty( $args['number'] ) ? $args['number'] : 0; } if ( empty( $items ) || is_wp_error( $items ) ) { return []; } foreach ( $items as $item ) { if ( $item->{$ref_parent} ) { $children[ $item->{$ref_id} ] = $item; $children[ $item->{$ref_id} ]->ID = (int) $item->{$ref_id}; } else { $parents[ $item->{$ref_id} ] = $item; $parents[ $item->{$ref_id} ]->ID = (int) $item->{$ref_id}; } } $children_count = count( $children ); $is_limited = $number > 1; // We can't guarantee that all children have a parent if there is a limit in the request. // Hence, we have to make sure that there is a parent for every child. if ( $is_limited && $children_count ) { foreach ( $children as $child ) { // The current WP_Post or WP_Term object to operate on. $current = $child; // The current object's parent is already in the list of parents or children. if ( ! empty( $parents[ $child->{$ref_parent} ] ) || ! empty( $children[ $child->{$ref_parent} ] ) ) { continue; } do { // Set the current object to the previous iteration's parent object. $current = ! empty( $args['post_type'] ) ? get_post( $current->{$ref_parent} ) : get_term( $current->{$ref_parent} ); if ( $current->{$ref_parent} === 0 ) { // We've reached the top of the hierarchy. $parents[ $current->{$ref_id} ] = $current; $parents[ $current->{$ref_id} ]->ID = (int) $current->{$ref_id}; } else { // We're still in the middle of the hierarchy. $children[ $current->{$ref_id} ] = $current; $children[ $current->{$ref_id} ]->ID = (int) $current->{$ref_id}; } } while ( $current->{$ref_parent} > 0 ); } } while ( $children_count >= 1 ) { foreach ( $children as $child ) { _wpforms_get_hierarchical_object_search( $child, $parents, $children, $ref_parent ); // $children is modified by reference, so we need to recount to make sure we met the limits. $children_count = count( $children ); } } // Sort nested child objects alphabetically using natural order, applies only // to ordering by entry title or term name. if ( in_array( $args['orderby'], [ 'title', 'name' ], true ) ) { _wpforms_sort_hierarchical_object( $parents, $args['orderby'], $args['order'] ); } if ( $flat ) { $parents_flat = []; _wpforms_get_hierarchical_object_flatten( $parents, $parents_flat, $ref_name ); $parents = $parents_flat; } return $is_limited ? array_slice( $parents, 0, $number ) : $parents; } /** * Sort a nested array of objects. * * @since 1.6.5 * * @param array $objects An array of objects to sort. * @param string $orderby The object field to order by. * @param string $order Order direction. */ function _wpforms_sort_hierarchical_object( &$objects, $orderby, $order ) { // Map WP_Query/WP_Term_Query orderby to WP_Post/WP_Term property. $map = [ 'title' => 'post_title', 'name' => 'name', ]; foreach ( $objects as $object ) { if ( ! isset( $object->children ) ) { continue; } uasort( $object->children, static function ( $a, $b ) use ( $map, $orderby, $order ) { /** * This covers most cases and works for most languages. For some – e.g. European languages * that use extended latin charset (Polish, German etc) it will sort the objects into 2 * groups – base and extended, properly sorted within each group. Making it even more * robust requires either additional PHP extensions to be installed on the server * or using heavy (and slow) conversions and computations. */ return $order === 'ASC' ? strnatcasecmp( $a->{$map[ $orderby ]}, $b->{$map[ $orderby ]} ) : strnatcasecmp( $b->{$map[ $orderby ]}, $a->{$map[ $orderby ]} ); } ); _wpforms_sort_hierarchical_object( $object->children, $orderby, $order ); } } /** * Search a given array and find the parent of the provided object. * * @since 1.3.9 * * @param object $child Current child. * @param array $parents Parents list. * @param array $children Children list. * @param string $ref_parent Parent reference. */ function _wpforms_get_hierarchical_object_search( $child, &$parents, &$children, $ref_parent ) { foreach ( $parents as $id => $parent ) { if ( $parent->ID === $child->{$ref_parent} ) { if ( empty( $parent->children ) ) { $parents[ $id ]->children = [ $child->ID => $child, ]; } else { $parents[ $id ]->children[ $child->ID ] = $child; } unset( $children[ $child->ID ] ); } elseif ( ! empty( $parent->children ) && is_array( $parent->children ) ) { _wpforms_get_hierarchical_object_search( $child, $parent->children, $children, $ref_parent ); } } } /** * Flatten a hierarchical object. * * @since 1.3.9 * * @param array $array Array to process. * @param array $output Processed output. * @param string $ref_name Name reference. * @param int $level Nesting level. */ function _wpforms_get_hierarchical_object_flatten( $array, &$output, $ref_name = 'name', $level = 0 ) { foreach ( $array as $key => $item ) { $indicator = apply_filters( 'wpforms_hierarchical_object_indicator', '—' ); $item->{$ref_name} = str_repeat( $indicator, $level ) . ' ' . $item->{$ref_name}; $item->depth = $level + 1; $output[ $item->ID ] = $item; if ( ! empty( $item->children ) ) { _wpforms_get_hierarchical_object_flatten( $item->children, $output, $ref_name, $level + 1 ); unset( $output[ $item->ID ]->children ); } } } /** * Get sanitized post title or "no title" placeholder. * * The placeholder is prepended with post ID. * * @since 1.7.6 * * @param WP_Post|object $post Post object. * * @return string Post title. */ function wpforms_get_post_title( $post ) { /* translators: %d - post ID. */ return wpforms_is_empty_string( trim( $post->post_title ) ) ? sprintf( __( '#%d (no title)', 'wpforms-lite' ), absint( $post->ID ) ) : $post->post_title; } /** * Get sanitized term name or "no name" placeholder. * * The placeholder is prepended with term ID. * * @since 1.7.6 * * @param WP_Term $term Term object. * * @return string Term name. */ function wpforms_get_term_name( $term ) { /* translators: %d - taxonomy term ID. */ return wpforms_is_empty_string( trim( $term->name ) ) ? sprintf( __( '#%d (no name)', 'wpforms-lite' ), absint( $term->term_id ) ) : trim( $term->name ); } /** * Return information about pages if the form has multiple pages. * * @since 1.3.7 * * @param WP_Post|array $form Form data. * * @return false|array Page Break details or false. */ function wpforms_get_pagebreak_details( $form = false ) { if ( ! wpforms()->is_pro() ) { return false; } $details = []; $pages = 1; if ( is_object( $form ) && ! empty( $form->post_content ) ) { $form_data = wpforms_decode( $form->post_content ); } elseif ( is_array( $form ) ) { $form_data = $form; } if ( empty( $form_data['fields'] ) ) { return false; } foreach ( $form_data['fields'] as $field ) { if ( $field['type'] !== 'pagebreak' ) { continue; } if ( empty( $field['position'] ) ) { $pages ++; $details['total'] = $pages; $details['pages'][] = $field; } elseif ( $field['position'] === 'top' ) { $details['top'] = $field; } elseif ( $field['position'] === 'bottom' ) { $details['bottom'] = $field; } } if ( ! empty( $details ) ) { $details['top'] = empty( $details['top'] ) ? [] : $details['top']; $details['bottom'] = empty( $details['bottom'] ) ? [] : $details['bottom']; $details['current'] = 1; return $details; } return false; } /** * Return available builder fields. * * @since 1.8.5 * * @param string $group Group name. * * @return array */ function wpforms_get_builder_fields( $group = '' ) { $fields = [ 'standard' => [ 'group_name' => esc_html__( 'Standard Fields', 'wpforms-lite' ), 'fields' => [], ], 'fancy' => [ 'group_name' => esc_html__( 'Fancy Fields', 'wpforms-lite' ), 'fields' => [], ], 'payment' => [ 'group_name' => esc_html__( 'Payment Fields', 'wpforms-lite' ), 'fields' => [], ], ]; /** * Allows developers to modify content of the the Add Field tab. * * With this filter developers can add their own fields or even fields groups. * * @since 1.4.0 * * @param array $fields { * Fields data multidimensional array. * * @param array $standard Standard fields group. * @param string $group_name Group name. * @param array $fields Fields array. * * @param array $fancy Fancy fields group. * @param string $group_name Group name. * @param array $fields Fields array. * * @param array $payment Payment fields group. * @param string $group_name Group name. * @param array $fields Fields array. * } */ $fields = apply_filters( 'wpforms_builder_fields_buttons', $fields ); // phpcs:ignore WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity // If a group is not specified, return all fields. if ( empty( $group ) ) { return $fields; } // If a group is specified, return only fields from that group. if ( isset( $fields[ $group ] ) ) { return $fields[ $group ]['fields']; } return []; } /** * Get payments fields. * * @since 1.8.5 * * @return array */ function wpforms_get_payments_fields() { // Some fields are added dynamically only when the corresponding payment add-on is active. // However, we need to be aware of all possible payment fields, even if they are not currently available. return [ 'payment-single', 'payment-multiple', 'payment-checkbox', 'payment-select', 'payment-total', 'payment-coupon', 'credit-card', // Legacy Credit Card field. 'authorize_net', 'paypal-commerce', 'square', 'stripe-credit-card', ]; }