You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

506 lines
13 KiB
PHP

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
/**
* Helper functions to work with form fields, generic and specific to certain field types.
*
* @since 1.8.0
*/
/**
* Determine if we should show the "Show Values" toggle for checkbox, radio, or
* select fields in form builder. Legacy.
*
* @since 1.5.0
*
* @return bool
*/
function wpforms_show_fields_options_setting() {
return apply_filters( 'wpforms_fields_show_options_setting', false );
}
/**
* Return field choice properties for field configured with dynamic choices.
*
* @since 1.4.5
*
* @param array $field Field settings.
* @param int $form_id Form ID.
* @param array $form_data Form data and settings.
*
* @return false|array
*/
function wpforms_get_field_dynamic_choices( $field, $form_id, $form_data = [] ) {
if ( empty( $field['dynamic_choices'] ) ) {
return false;
}
$choices = [];
if ( $field['dynamic_choices'] === 'post_type' ) {
if ( empty( $field['dynamic_post_type'] ) ) {
return false;
}
$posts = wpforms_get_hierarchical_object(
apply_filters(
'wpforms_dynamic_choice_post_type_args',
[
'post_type' => $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', '&mdash;' );
$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',
];
}