) { remove_filter( 'frm_allowed_form_input_html', $allow_separator_tags_filter ); } } self::trigger_shortcode_atts( $atts, $display, $args, $replace_with ); FrmFieldsHelper::sanitize_embedded_shortcodes( compact( 'entry' ), $replace_with ); if ( is_null( $replace_with ) ) { $replace_with = ''; } $content = str_replace( $shortcodes[0][ $short_key ], $replace_with, $content ); } } /** * When get_display_value is called, a lot of HTML is stripped from entries submitted by users * who cannot edit entries. Since the implode happens earlier in self::replace_single_shortcode * we need to allow for whatever tags amy be in the separator. * * @since 6.8 * * @param string $sep Separator used to implode array values. * * @return Closure|false False when there are no tags to add to the filter. */ private static function get_allow_separator_tags_filter( $sep ) { $tags = self::get_tags_used_in_string( $sep ); if ( ! $tags ) { // If there are no tags there is no need to add this filter. return false; } return function ( $allowed_html ) use ( $tags ) { foreach ( $tags as $tag ) { if ( ! isset( $allowed_html[ $tag ] ) ) { $allowed_html[ $tag ] = array(); } } return $allowed_html; }; } /** * Check for HTML tags used in a string. * * @since 6.8 * * @param string $string * * @return array */ private static function get_tags_used_in_string( $string ) { $tag_pattern = '/<\s*([a-zA-Z]+)[^>]*>|<\/\s*([a-zA-Z]+)\s*>/'; preg_match_all( $tag_pattern, $string, $matches ); $tags = array_filter( array_merge( $matches[1], $matches[2] ) ); $unique_tags = array_unique( $tags ); return array_values( $unique_tags ); } /** * Maybe insert the show="value" option to an [if x] shortcode if the field has options * that are displayed as images. * This allows you to match [if x equals="Option 1"] even though the output/default compare value is HTML. * * @since 6.7 * * @param array $atts * @param stdClass $field * @param bool $conditional * * @return void */ private static function maybe_add_show_value_option_to_shortcode( &$atts, $field, $conditional ) { if ( ! $conditional ) { // Only apply this to [if x] shortcodes. A regular [x] shortcode should output an image. return; } if ( empty( $field->options ) || empty( $field->field_options['image_options'] ) ) { // This field does not have image options, so exit early. return; } if ( isset( $atts['show'] ) || ! isset( $atts['equals'] ) ) { // Do not overwrite any show attributes if one is already set, and only do this for equals conditions. return; } if ( str_contains( $atts['equals'], 'frm_image_option_container' ) && str_contains( $atts['equals'], ' $tag ) { self::replace_single_shortcode( $shortcodes, $short_key, $tag, $entry_args['entry'], $entry_args['view'], $args, $content ); } } /** * Replace the calendar date shortcode with the event date. * * @param string $content * @param string $date * * @return string */ public static function replace_calendar_date_shortcode( $content, $date ) { preg_match_all( "/\[(calendar_date|calendar_end_date)\b(.*?)(?:(\/))?\]/s", $content, $matches, PREG_PATTERN_ORDER ); if ( empty( $matches[0] ) ) { return $content; } foreach ( $matches[0] as $short_key => $tag ) { $atts = FrmShortcodeHelper::get_shortcode_attribute_array( $matches[2][ $short_key ] ); self::do_shortcode_event_date( $content, $atts, $matches, $short_key, array( 'event_date' => $date ) ); if ( is_callable( 'FrmViewsCalendarHelper::do_shortcode_end_event_date' ) ) { FrmViewsCalendarHelper::do_shortcode_end_event_date( $content, $atts, $matches, $short_key, array( 'event_date' => $date ) ); } } return $content; } /** * Replace the calendar date shortcode with the event date. * Function used to render the shortcode for event date in FrmProContent::replace_single_shortcode * * @param string $content * @param array $atts * @param array $shortcodes * @param int $short_key * @param array $args Passed in FrmProContent::replace_single_shortcode when called. * * @return void */ public static function do_shortcode_event_date( &$content, $atts, $shortcodes, $short_key, $args ) { $event_date_get_param = FrmAppHelper::get_param( 'frmev-start', '', 'get', 'sanitize_text_field' ); // Modern Calendar. $format = $atts['format'] ?? get_option( 'date_format' ); $event_date = $args['event_date'] ?? $event_date_get_param; // Check for $args['event_date'] first to support Legacy Calendar. if ( $event_date ) { $event_date = FrmProFieldsHelper::get_date( $event_date, $format ); } $content = str_replace( $shortcodes[0][ $short_key ], $event_date, $content ); } /** * @param array $atts * @param array $shortcodes * @param array $args */ public static function do_shortcode_entry_count( &$content, $atts, $shortcodes, $short_key, $args ) { $content = str_replace( $shortcodes[0][ $short_key ], $args['record_count'] ?? '', $content ); } /** * @param array $atts * @param array $shortcodes * @param array $args */ public static function do_shortcode_detaillink( &$content, $atts, $shortcodes, $short_key, $args, $display ) { if ( $display ) { $detail_link = self::get_detail_link( $args, $display ); $content = str_replace( 'frm_type === 'id' ? $entry['entry_id'] : $entry['entry_key']; $param = ! empty( $display->frm_param ) ? $display->frm_param : 'entry'; $detail_link = self::get_pretty_url( compact( 'param', 'param_value' ) ); } return apply_filters( 'frmpro_detaillink_shortcode', $detail_link, $args ); } /** * Make the view urls pretty * * @param array $atts * * @return string */ public static function get_pretty_url( $atts ) { global $post; $base_url = untrailingslashit( $post ? get_permalink( $post->ID ) : FrmAppHelper::get_server_value( 'REQUEST_URI' ) ); if ( ! is_front_page() && self::rewriting_on() && ! str_contains( $base_url, '?' ) ) { return $base_url . '/' . $atts['param'] . '/' . $atts['param_value']; } return esc_url_raw( add_query_arg( $atts['param'], $atts['param_value'], $base_url ) ); } private static function rewriting_on() { $permalink_structure = get_option( 'permalink_structure' ); return ! empty( $permalink_structure ); } public static function add_rewrite_endpoint() { $rewrite_params = self::get_rewrite_params(); if ( ! $rewrite_params ) { return; } foreach ( $rewrite_params as $param ) { add_rewrite_endpoint( $param, EP_PERMALINK | EP_PAGES ); } add_action( 'request', 'FrmProContent::fix_home_page_query' ); } /** * This is a workaround for a bug in WordPress Core * https://core.trac.wordpress.org/ticket/23867 * * @since 2.2.10 * * @param array $query */ public static function fix_home_page_query( $query ) { $included_params = array_intersect( self::get_rewrite_params(), array_keys( $query ) ); if ( $included_params ) { foreach ( $included_params as $key ) { $_GET[ $key ] = $query[ $key ]; unset( $query[ $key ] ); } } return $query; } /* * Get the detail link parameter names from every view. * * @since 2.2.8 * * @return array */ /** * @return array */ private static function get_rewrite_params() { if ( ! function_exists( 'load_formidable_views' ) ) { return array(); } global $wpdb; $params = FrmDb::get_col( $wpdb->postmeta, array( 'meta_key' => 'frm_param' ), 'meta_value' ); $params = self::remove_reserved_words( $params ); return array_filter( array_unique( $params ) ); } /** * Removes reserved words from a list of params. * * @param array $params A list of params. * * @return array The list of params with reserved words removed. */ private static function remove_reserved_words( $params ) { return array_diff( $params, FrmFormsHelper::reserved_words() ); } /** * @param string $content * @param array $atts * @param array $shortcodes * @param int $short_key * @param array $args * * @return void */ public static function do_shortcode_editlink( &$content, $atts, $shortcodes, $short_key, $args ) { global $post; $replace_with = ''; $link_text = $atts['label'] ?? false; if ( ! $link_text ) { $link_text = $atts['link_text'] ?? __( 'Edit' ); } $class = $atts['class'] ?? ''; $page_id = $atts['page_id'] ?? ( $post ? $post->ID : 0 ); if ( ( isset( $atts['location'] ) && $atts['location'] === 'front' ) || ! empty( $atts['prefix'] ) || ! empty( $atts['page_id'] ) ) { $edit_atts = $atts; $edit_atts['id'] = isset( $args['foreach_loop'] ) ? $args['entry']->parent_item_id : $args['entry']->id; $edit_atts['page_id'] = $page_id; $replace_with = FrmProEntriesController::entry_edit_link( $edit_atts ); } else { if ( $args['entry']->post_id ) { $replace_with = get_edit_post_link( $args['entry']->post_id ); } elseif ( current_user_can( 'frm_edit_entries' ) ) { $replace_with = FrmProEntry::admin_edit_link( $args['entry']->id ); } if ( $replace_with ) { $replace_with = '' . $link_text . ''; } } $content = str_replace( $shortcodes[0][ $short_key ], $replace_with, $content ); } /** * @param string $content * @param array $atts * @param array $shortcodes * @param int $short_key * @param array $args * * @return void */ public static function do_shortcode_deletelink( &$content, $atts, $shortcodes, $short_key, $args ) { global $post; $page_id = $atts['page_id'] ?? ( $post ? $post->ID : 0 ); if ( ! isset( $atts['label'] ) ) { $atts['label'] = false; } $delete_atts = $atts; $delete_atts['id'] = $args['entry']->id; $delete_atts['page_id'] = $page_id; $replace_with = FrmProEntriesController::entry_delete_link( $delete_atts ); $content = str_replace( $shortcodes[0][ $short_key ], $replace_with, $content ); } /** * @param array $atts * @param array $shortcodes * @param array $args */ public static function do_shortcode_evenodd( &$content, $atts, $shortcodes, $short_key, $args ) { $content = str_replace( $shortcodes[0][ $short_key ], $args['odd'], $content ); } /** * @param array $atts * @param array $shortcodes * @param array $args */ public static function do_shortcode_post_id( &$content, $atts, $shortcodes, $short_key, $args ) { $content = str_replace( $shortcodes[0][ $short_key ], $args['entry']->post_id ?? '', $content ); } /** * @param array $atts * @param array $shortcodes * @param array $args */ public static function do_shortcode_parent_id( &$content, $atts, $shortcodes, $short_key, $args ) { $content = str_replace( $shortcodes[0][ $short_key ], $args['entry']->parent_item_id, $content ); } /** * @param array $atts * @param array $shortcodes * @param array $args */ public static function do_shortcode_id( &$content, $atts, $shortcodes, $short_key, $args ) { $content = str_replace( $shortcodes[0][ $short_key ], $args['entry']->id, $content ); } /** * Process a [created-at] shortcode. * * @param string $content * @param array $atts * @param array $shortcodes * @param int $short_key * @param array $args * * @return void */ public static function do_shortcode_created_at( &$content, $atts, $shortcodes, $short_key, $args ) { if ( isset( $atts['format'] ) ) { $time_format = ' '; } else { $atts['format'] = get_option( 'date_format' ); $time_format = ''; } if ( $args['conditional'] ) { if ( in_array( $args['tag'], array( 'created_at', 'updated_at' ), true ) ) { $atts = self::replace_magic_timestamp_shortcode_values( $atts, $args['tag'], $args['entry'] ); } $atts['short_key'] = $shortcodes[0][ $short_key ]; self::check_conditional_shortcode( $content, $args['entry']->{$args['tag']}, $atts, $args['tag'] ); return; } if ( isset( $atts['time_ago'] ) ) { // $time_ago values can include 1-7, as well as d,w,h,s and their extended day,week,hour,second names as well. $time_ago = is_numeric( $atts['time_ago'] ) ? absint( $atts['time_ago'] ) : sanitize_key( $atts['time_ago'] ); $date = FrmAppHelper::human_time_diff( strtotime( $args['entry']->{$args['tag']} ), '', $time_ago ); } else { $date = FrmAppHelper::get_formatted_time( $args['entry']->{$args['tag']}, $atts['format'], $time_format ); } $content = str_replace( $shortcodes[0][ $short_key ], $date, $content ); } /** * Convert "created_at" used for [if updated_at] tag conditions. * Also converts "updated_at" used for [if created_at] tag conditions. * This way you can use a condition like [if updated_at greater_than="created_at"][/if updated_at]. * And [if created_at less_than="updated_at"][/if created_at]. * * @since 6.7 * * @param array $atts * @param string $tag * @param stdClass $entry * * @return array */ private static function replace_magic_timestamp_shortcode_values( $atts, $tag, $entry ) { $value_to_replace = 'created_at' === $tag ? 'updated_at' : 'created_at'; $conditions = array_intersect( array_keys( $atts ), self::get_conditions() ); foreach ( $conditions as $att_key ) { if ( $value_to_replace === $atts[ $att_key ] ) { $atts[ $att_key ] = FrmAppHelper::get_localized_date( 'Y-m-d H:i:s', $entry->$value_to_replace ); } } return $atts; } /** * @param array $atts * @param array $shortcodes * @param array $args */ public static function do_shortcode_get( &$content, $atts, $shortcodes, $short_key, $args ) { $replace_with = FrmFieldsHelper::process_get_shortcode( $atts ); if ( $args['conditional'] ) { $atts['short_key'] = $shortcodes[0][ $short_key ]; self::check_conditional_shortcode( $content, $replace_with, $atts, $args['tag'], 'if' ); } else { $content = str_replace( $shortcodes[0][ $short_key ], $replace_with, $content ); } } /** * @param array $atts * @param array $args */ public static function do_shortcode_updated_at( &$content, $atts, $shortcodes, $short_key, $args ) { self::do_shortcode_created_at( $content, $atts, $shortcodes, $short_key, $args ); } /** * @param array $atts * @param array $shortcodes * @param array $args */ public static function do_shortcode_created_by( &$content, $atts, $shortcodes, $short_key, $args ) { $replace_with = FrmFieldsHelper::get_display_value( $args['entry']->{$args['tag']}, (object) array( 'type' => 'user_id' ), $atts ); if ( $args['conditional'] ) { $atts['short_key'] = $shortcodes[0][ $short_key ]; self::check_conditional_shortcode( $content, $args['entry']->{$args['tag']}, $atts, $args['tag'] ); } else { $content = str_replace( $shortcodes[0][ $short_key ], $replace_with, $content ); } } /** * @param array $atts * @param array $args */ public static function do_shortcode_updated_by( &$content, $atts, $shortcodes, $short_key, $args ) { self::do_shortcode_created_by( $content, $atts, $shortcodes, $short_key, $args ); } /** * Process the is_draft shortcode * * @since 2.0.22 * * @param string $content * @param array $atts * @param array $shortcodes * @param int $short_key * @param array $args * * @return void */ public static function do_shortcode_is_draft( &$content, $atts, $shortcodes, $short_key, $args ) { if ( $args['conditional'] ) { if ( ! $atts ) { $atts['equals'] = 1; } $atts['short_key'] = $shortcodes[0][ $short_key ]; self::check_conditional_shortcode( $content, $args['entry']->is_draft, $atts, 'is_draft' ); } else { $content = str_replace( $shortcodes[0][ $short_key ], $args['entry']->is_draft, $content ); } } /** * @since 3.0 * * @param string $content * @param array $atts * @param array $shortcodes * @param int $short_key * @param array $args */ public static function do_shortcode_entry_position( &$content, $atts, $shortcodes, $short_key, $args ) { if ( ! isset( $args['count'] ) ) { $args['count'] = 1; } if ( $args['conditional'] ) { $atts['short_key'] = $shortcodes[0][ $short_key ]; self::check_conditional_shortcode( $content, $args['count'], $atts, $args['tag'] ); } else { $content = str_replace( $shortcodes[0][ $short_key ], $args['count'], $content ); } } /** * When a value is saved as an array, allow show=something to return a specified value from the array * * @since 2.0.23 * * @param array|string $replace_with * @param array $atts * * @return void */ private static function maybe_get_show_from_array( &$replace_with, $atts ) { if ( ! is_array( $replace_with ) || ! isset( $atts['show'] ) ) { return; } if ( $atts['show'] === 'country_code' && ! empty( $replace_with['country'] ) ) { $replace_with = FrmProAddressesController::get_country_code( $replace_with['country'] ); return; } if ( isset( $replace_with[ $atts['show'] ] ) ) { $replace_with = $replace_with[ $atts['show'] ]; } elseif ( ! empty( $atts['blank'] ) ) { $replace_with = ''; } } /** * @param string $content * @param string $replace_with * @param array $atts * @param string $tag * @param string $condition * @param array $args * * @return void */ public static function check_conditional_shortcode( &$content, $replace_with, $atts, $tag, $condition = 'if', $args = array() ) { $defaults = array( 'field' => false ); $args = wp_parse_args( $args, $defaults ); $checking_if_condition = 'if' === $condition; if ( $checking_if_condition ) { $replace_with = self::conditional_replace_with_value( $replace_with, $atts, $args['field'], $tag ); $replace_with = apply_filters( 'frm_conditional_value', $replace_with, $atts, $args['field'], $tag ); } $start_pos = strpos( $content, $atts['short_key'] ); // Replace identical conditional and foreach shortcodes in this loop while ( $start_pos !== false ) { $start_pos_len = strlen( $atts['short_key'] ); $end_tag = self::get_matching_end_tag( $content, $start_pos + $start_pos_len, $condition, $tag ); if ( false === $end_tag ) { return; } $end_pos = $end_tag['end_pos']; $end_pos_len = $end_tag['end_pos_len']; $total_len = $end_pos + $end_pos_len - $start_pos; $is_empty = $replace_with === '' || is_null( $replace_with ) || false === $replace_with; $substring_args = compact( 'content', 'start_pos', 'start_pos_len', 'end_pos' ); if ( $is_empty ) { $replacement = ''; if ( $checking_if_condition ) { $substring = self::get_conditional_substring( $substring_args ); if ( self::conditional_substring_contains_else( $substring ) ) { $replacement = self::get_conditional_substring_half( $substring, true ); } } $content = substr_replace( $content, $replacement, $start_pos, $total_len ); } elseif ( 'foreach' === $condition ) { $content_len = $end_pos - ( $start_pos + $start_pos_len ); $repeat_content = substr( $content, $start_pos + $start_pos_len, $content_len ); self::foreach_shortcode( $replace_with, $args, $repeat_content, $atts ); $content = substr_replace( $content, $repeat_content, $start_pos, $total_len ); } else { $substring = self::get_conditional_substring( $substring_args ); if ( self::conditional_substring_contains_else( $substring ) ) { $replacement = self::get_conditional_substring_half( $substring, false ); $content = substr_replace( $content, $replacement, $start_pos, $total_len ); } else { $content = substr_replace( $content, '', $end_pos, $end_pos_len ); $content = substr_replace( $content, '', $start_pos, $start_pos_len ); } } $start_pos = strpos( $content, $atts['short_key'] ); } } /** * Find the closing tag that belongs to an opening conditional or foreach shortcode. * * Shortcodes of the same type that are nested inside the block are skipped, so * [if get param="a"][if get param="b"]x[/if get][/if get] closes on the second * [/if get] instead of the first one in the content. When the content is not * balanced, the first closing tag is used so the previous behavior is kept. * * @since 6.35 * * @param string $content * @param int $search_from The position directly after the opening tag. * @param string $condition Either 'if' or 'foreach'. * @param string $tag The shortcode tag, for example 'get' or a field id. * * @return array|false Array with 'end_pos' and 'end_pos_len' keys, or false when there is no closing tag. */ private static function get_matching_end_tag( $content, $search_from, $condition, $tag ) { $first_end_tag = self::get_first_end_tag( $content, $search_from, $condition, $tag ); if ( false === $first_end_tag ) { return false; } $pattern = '/\[(\/)?' . $condition . '\b[^\]]*\]/s'; $found = preg_match_all( $pattern, $content, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE, $search_from ); if ( ! $found ) { return $first_end_tag; } $depth = 1; foreach ( $matches[0] as $key => $match ) { $is_end_tag = '' !== $matches[1][ $key ][0]; $depth += $is_end_tag ? -1 : 1; if ( 0 === $depth ) { return array( 'end_pos' => $match[1], 'end_pos_len' => strlen( $match[0] ), ); } } return $first_end_tag; } /** * Get the first closing tag after a position, preferring the tag-specific closing tag. * * @since 6.35 * * @param string $content * @param int $search_from The position to start searching from. * @param string $condition Either 'if' or 'foreach'. * @param string $tag The shortcode tag, for example 'get' or a field id. * * @return array|false Array with 'end_pos' and 'end_pos_len' keys, or false when there is no closing tag. */ private static function get_first_end_tag( $content, $search_from, $condition, $tag ) { $end_tag = '[/' . $condition . ' ' . $tag . ']'; $end_pos = strpos( $content, $end_tag, $search_from ); if ( false === $end_pos ) { $end_tag = '[/' . $condition . ']'; $end_pos = strpos( $content, $end_tag, $search_from ); if ( false === $end_pos ) { return false; } } return array( 'end_pos' => $end_pos, 'end_pos_len' => strlen( $end_tag ), ); } /** * Get either the left or the right substring for if conditions containing an [else] shortcode. * * @since 5.0.14 * * @param string $substring * @param bool $else if true, the substring after [else] is returned. if false, the substring before it is returned. * * @return string */ private static function get_conditional_substring_half( $substring, $else = false ) { $else_pos = self::get_else_position( $substring ); if ( false === $else_pos ) { return $else ? '' : $substring; } if ( ! $else ) { return substr( $substring, 0, $else_pos ); } return substr( $substring, $else_pos + strlen( '[else]' ) ); } /** * Get the position of the [else] shortcode that belongs to this condition. * * An [else] inside a nested [if] block belongs to that block, so it is skipped here. * * @since 6.35 * * @param string $substring The content inside an if condition. * * @return false|int */ private static function get_else_position( $substring ) { $found = preg_match_all( '/\[(\/)?if\b[^\]]*\]|\[else\]/s', $substring, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE ); if ( ! $found ) { return false; } $depth = 0; foreach ( $matches[0] as $key => $match ) { if ( '[else]' === $match[0] ) { if ( 0 === $depth ) { return $match[1]; } continue; } $is_end_tag = '' !== $matches[1][ $key ][0]; if ( ! $is_end_tag ) { ++$depth; } elseif ( $depth > 0 ) { --$depth; } } return false; } /** * Get the text inside of an if condition shortcode. * * @since 5.0.14 * * @param array $args expects keys 'content', 'start_pos', 'start_pos_len', 'end_pos'. * * @return string */ private static function get_conditional_substring( $args ) { $start = $args['start_pos'] + $args['start_pos_len']; $length = $args['end_pos'] - $start; return substr( $args['content'], $start, $length ); } /** * Check if content has an else shortcode. * * @since 5.0.14 * * @param string $substring * * @return bool */ private static function conditional_substring_contains_else( $substring ) { return false !== self::get_else_position( $substring ); } /** * Loop through each entry linked through a repeating field when using [foreach]. * * @param array|string $replace_with * @param array $args * @param string $repeat_content * @param array $atts */ public static function foreach_shortcode( $replace_with, $args, &$repeat_content, $atts = array() ) { $foreach_content = ''; $sub_entries = is_array( $replace_with ) ? $replace_with : explode( ',', $replace_with ); if ( ! empty( $atts['order'] ) && 'desc' === $atts['order'] ) { $sub_entries = array_reverse( $sub_entries ); } foreach ( $sub_entries as $sub_entry ) { $sub_entry = trim( $sub_entry ); if ( ! is_numeric( $sub_entry ) ) { continue; } $entry = FrmEntry::getOne( $sub_entry ); if ( ! $entry ) { continue; } $args['foreach_loop'] = true; $shortcodes = FrmProDisplaysHelper::get_shortcodes( $repeat_content, $entry->form_id ); $repeating_content = $repeat_content; foreach ( $shortcodes[0] as $short_key => $tag ) { self::replace_single_shortcode( $shortcodes, $short_key, $tag, $entry, $args['display'], $args, $repeating_content ); } if ( ( $foreach_content || '0' === $foreach_content ) && isset( $atts['sep'] ) ) { $foreach_content .= wp_kses_post( $atts['sep'] ); } $foreach_content .= $repeating_content; } $repeat_content = $foreach_content; } /** * Returns a list of conditions used in Conditionals. * * @return array */ public static function get_conditions() { return array( 'equals', 'not_equal', 'not_equals', 'like', 'not_like', 'contains', // This is an alias of 'like'. 'does_not_contain', // This is an alias of 'not_like'. 'less_than', 'less_than_or_equal_to', 'greater_than', 'greater_than_or_equal_to', 'starts_with', 'ends_with', ); } /** * @param array|string|null $replace_with * @param array $atts * @param false|stdClass $field * @param string $tag * * @return mixed */ public static function conditional_replace_with_value( $replace_with, $atts, $field, $tag ) { $conditions = self::get_conditions(); if ( $field && $field->type === 'data' ) { $show_id = is_numeric( $replace_with ) && ! empty( $atts['show'] ) && 'id' === $atts['show']; // $replace_with is already in the expected format if show="id" is included, so skip all of this display value logic. if ( ! $show_id ) { $old_replace_with = $replace_with; // Only get the displayed value if it hasn't been set yet. if ( is_numeric( $replace_with ) || ( ! is_null( $replace_with ) && is_numeric( str_replace( array( ',', ' ' ), array( '', '' ), $replace_with ) ) ) || is_array( $replace_with ) ) { $replace_with = FrmFieldsHelper::get_display_value( $replace_with, $field, $atts ); if ( $old_replace_with == $replace_with ) { $replace_with = ''; } } // Get the linked field to properly evaluate conditions. if ( $replace_with !== '' && ! empty( $atts['show'] ) ) { $show_field = FrmField::getOne( $atts['show'] ); if ( $show_field && in_array( $show_field->type, array( 'time', 'date', 'user_id' ), true ) ) { $field = $show_field; unset( $atts['show'] ); } } } } if ( ( $field && $field->type === 'user_id' ) || in_array( $tag, array( 'updated_by', 'created_by' ), true ) ) { // Check if conditional is for current user. if ( isset( $atts['equals'] ) && $atts['equals'] === 'current' ) { $atts['equals'] = get_current_user_id(); } if ( isset( $atts['not_equal'] ) && $atts['not_equal'] === 'current' ) { $atts['not_equal'] = get_current_user_id(); } if ( isset( $atts['show'] ) ) { $atts['blank'] = $atts['blank'] ?? 1; $replace_with = FrmFieldsHelper::get_display_value( $replace_with, $field, $atts ); } } elseif ( self::is_timestamp_tag( $tag ) || ( $field && $field->type === 'date' ) ) { self::prepare_date_for_eval( $conditions, $tag, $atts ); } elseif ( $field && $field->type === 'time' ) { $formatted_time = false; foreach ( $conditions as $att_name ) { if ( isset( $atts[ $att_name ] ) && $atts[ $att_name ] != '' ) { if ( strtolower( $atts[ $att_name ] ) === 'now' ) { $atts[ $att_name ] = FrmProAppHelper::get_date( 'H:i' ); } else { $atts[ $att_name ] = gmdate( 'H:i', strtotime( $atts[ $att_name ] ) ); } if ( ! $formatted_time ) { $replace_with = FrmProAppHelper::format_time( $replace_with, 'H:i' ); $formatted_time = true; } } } } elseif ( $field && 'file' === $field->type && is_numeric( $replace_with ) ) { $replace_with = FrmFieldsHelper::get_display_value( $replace_with, $field, $atts ); } else { // Compare properly with &. FrmAppHelper::decode_specialchars( $replace_with ); } self::eval_conditions( $conditions, $atts, $replace_with, $field ); return $replace_with; } /** * @param string $tag * * @return false|int */ private static function is_timestamp_tag( $tag ) { return preg_match( '/^(created[-|_]at|updated[-|_]at)$/', $tag ); } /** * @param array $conditions * @param string $tag * @param array $atts * * @return void */ private static function prepare_date_for_eval( $conditions, $tag, &$atts ) { foreach ( $conditions as $att_name ) { if ( isset( $atts[ $att_name ] ) && $atts[ $att_name ] != '' && ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', trim( $atts[ $att_name ] ) ) ) { if ( self::is_timestamp_tag( $tag ) ) { self::get_gmt_for_filter( $att_name, $atts[ $att_name ] ); } elseif ( strtolower( $atts[ $att_name ] ) === 'now' ) { $atts[ $att_name ] = FrmProAppHelper::get_date( 'Y-m-d' ); } else { $atts[ $att_name ] = gmdate( 'Y-m-d', strtotime( $atts[ $att_name ] ) ); } } unset( $att_name ); } } /** * @param string $compare * @param string $where_val * * @return void */ public static function get_gmt_for_filter( $compare, &$where_val ) { $original_value = $where_val; if ( $where_val === 'NOW' ) { $where_val = current_time( 'mysql', 1 ); } $compare = strtolower( $compare ); if ( str_contains( $compare, 'like' ) ) { return; } $where_val = gmdate( 'Y-m-d H:i:s', strtotime( $where_val ) ); // If using less than or equal to, set the time to the end of the day. if ( $compare === '<=' || $compare === 'less_than' ) { $where_val = str_replace( '00:00:00', '23:59:59', $where_val ); } // Convert date to GMT since that is the format in the DB. if ( self::should_convert_to_gmt( $original_value ) ) { $where_val = get_gmt_from_date( $where_val ); } } /** * Avoid converting the value twice if the compare value was a relative value like * '-1 hour', '-10 minutes', or '-30 seconds'. * * @since 6.7.1 * * @param string $value The original value being passed as a shortcode compare value. * * @return bool */ private static function should_convert_to_gmt( $value ) { $substrings = array( 'hour', 'minute', 'second' ); foreach ( $substrings as $substring ) { if ( false !== stripos( $value, $substring ) ) { return false; } } return true; } /** * Run all of the eval functions beginning with eval_ and ending with _condition. * * @param array $conditions * @param array $atts * @param string|null $replace_with * @param stdClass $field * * @return void */ private static function eval_conditions( $conditions, $atts, &$replace_with, $field ) { foreach ( $conditions as $condition ) { if ( ! isset( $atts[ $condition ] ) ) { continue; } if ( str_contains( $atts[ $condition ], '[' ) && isset( $atts['entry'] ) && is_object( $atts['entry'] ) && isset( $atts['entry']->form_id ) ) { $atts[ $condition ] = FrmFormsController::filter_content( $atts[ $condition ], $atts['entry']->form_id, $atts['entry'] ); if ( str_contains( $atts[ $condition ], '[' ) ) { $atts[ $condition ] = do_shortcode( $atts[ $condition ] ); } } self::maybe_swap_condition_alias( $condition, $atts ); if ( 'param' === $atts[ $condition ] && isset( $atts['param'] ) ) { $atts[ $condition ] = FrmFieldsHelper::process_get_shortcode( $atts ); } $function = 'eval_' . $condition . '_condition'; self::$function( $atts, $replace_with, $field ); } } /** * Map 'contains' to 'like' and 'does_not_contain' to 'not_like'. * * @since 6.8 * * @param string $condition * @param array $atts * * @return void */ private static function maybe_swap_condition_alias( &$condition, &$atts ) { if ( 'contains' === $condition ) { $condition = 'like'; $atts['like'] = $atts['contains']; unset( $atts['contains'] ); return; } if ( 'does_not_contain' !== $condition ) { return; } $condition = 'not_like'; $atts['not_like'] = $atts['does_not_contain']; unset( $atts['does_not_contain'] ); } /** * @param array $atts * @param string|null $replace_with * @param stdClass $field * * @return void */ private static function eval_equals_condition( $atts, &$replace_with, $field ) { if ( $replace_with != $atts['equals'] ) { if ( $field && in_array( $field->type, array( 'data', 'quiz_score' ), true ) ) { $replace_with = FrmFieldsHelper::get_display_value( $replace_with, $field, $atts ); if ( $replace_with != $atts['equals'] ) { $replace_with = ''; } } elseif ( isset( $field->field_options['post_field'] ) && $field->field_options['post_field'] === 'post_category' ) { $cats = explode( ', ', $replace_with ); $replace_with = ''; foreach ( $cats as $cat ) { if ( $atts['equals'] == strip_tags( $cat ) ) { $replace_with = true; return; } } } else { $replace_with = ''; } } elseif ( $atts['equals'] == '' && $replace_with == '' ) { // If the field is blank, give it a value $replace_with = true; } } /** * Not equals term is exactly dose what not equal and it's added for convention only. * * @since 5.4.3. * * @param array $atts condition attributes. * @param string|null $replace_with string to check condition against. * @param stdClass $field field. * * @return void */ private static function eval_not_equals_condition( $atts, &$replace_with, $field ) { $atts['not_equal'] = $atts['not_equals']; unset( $atts['not_equals'] ); self::eval_not_equal_condition( $atts, $replace_with, $field ); } /** * @param array $atts * @param string|null $replace_with * @param stdClass $field * * @return void */ private static function eval_not_equal_condition( $atts, &$replace_with, $field ) { if ( $field && 'quiz_score' === $field->type ) { $replace_with = FrmFieldsHelper::get_display_value( $replace_with, $field, $atts ); } if ( $replace_with == $atts['not_equal'] ) { $replace_with = ''; } elseif ( $replace_with == '' && $atts['not_equal'] !== '' ) { $replace_with = true; } elseif ( $replace_with && isset( $field->field_options['post_field'] ) && $field->field_options['post_field'] === 'post_category' ) { $cats = explode( ', ', $replace_with ); foreach ( $cats as $cat ) { if ( $atts['not_equal'] == strip_tags( $cat ) ) { $replace_with = ''; return; } unset( $cat ); } } } /** * @param array $atts * @param string|null $replace_with * * @return void */ private static function eval_like_condition( $atts, &$replace_with ) { if ( $atts['like'] == '' ) { return; } if ( ! is_string( $replace_with ) || stripos( $replace_with, $atts['like'] ) === false ) { $replace_with = ''; } } /** * @param array $atts * @param string|null $replace_with * * @return void */ private static function eval_not_like_condition( $atts, &$replace_with ) { if ( $atts['not_like'] == '' ) { return; } if ( $replace_with == '' ) { $replace_with = true; } elseif ( stripos( $replace_with, $atts['not_like'] ) !== false ) { $replace_with = ''; } } /** * @param array $atts */ private static function eval_less_than_condition( $atts, &$field_value ) { if ( $field_value >= $atts['less_than'] ) { // Condition is false $field_value = ''; } } /** * @param array $atts */ private static function eval_less_than_or_equal_to_condition( $atts, &$field_value ) { $condition_is_true = $field_value <= $atts['less_than_or_equal_to']; if ( ! $condition_is_true ) { $field_value = ''; } } /** * @param array $atts */ private static function eval_greater_than_condition( $atts, &$field_value ) { if ( $field_value <= $atts['greater_than'] ) { // Condition is false $field_value = ''; } } /** * @param array $atts */ private static function eval_greater_than_or_equal_to_condition( $atts, &$field_value ) { $condition_is_true = $field_value >= $atts['greater_than_or_equal_to']; if ( ! $condition_is_true ) { $field_value = ''; } } /** * Perform a case-insensitive starts with check. * * @since 6.8 * * @param array $atts * @param array|string $replace_with * * @return void */ private static function eval_starts_with_condition( $atts, &$replace_with ) { if ( ! is_string( $replace_with ) ) { $replace_with = ''; return; } $haystack = $replace_with; $needle = $atts['starts_with']; if ( $needle && stripos( $haystack, $needle ) !== 0 ) { $replace_with = ''; } } /** * Perform a case-insensitive ends with check. * * @since 6.8 * * @param array $atts * @param array|string $replace_with * * @return void */ private static function eval_ends_with_condition( $atts, &$replace_with ) { if ( ! is_string( $replace_with ) ) { $replace_with = ''; return; } $haystack = strtolower( $replace_with ); $needle = strtolower( $atts['ends_with'] ); if ( $needle && ! str_ends_with( $haystack, $needle ) ) { $replace_with = ''; } } /** * @param array $atts * @param false|WP_Post $display * @param array $args * @param array|string $replace_with * * @return void */ public static function trigger_shortcode_atts( $atts, $display, $args, &$replace_with ) { $frm_atts = array( 'remove_accents', 'sanitize', 'sanitize_url', 'truncate', 'clickable', ); $included_atts = array_intersect( $frm_atts, array_keys( $atts ) ); foreach ( $included_atts as $included_att ) { if ( '0' === $atts[ $included_att ] ) { // Skip any option that uses 0 so sanitize_url=0 does not encode. continue; } $function = 'atts_' . $included_att; $replace_with = self::$function( $replace_with, $atts, $display, $args ); } } /** * Converts all accent characters to ASCII characters. * * @since 6.3.1 * * @param string $replace_with The text to remove accents from. * * @return string */ public static function atts_remove_accents( $replace_with ) { return remove_accents( $replace_with ); } public static function atts_sanitize( $replace_with ) { return sanitize_title_with_dashes( $replace_with ); } public static function atts_sanitize_url( $replace_with ) { return urlencode( $replace_with ); } /** * @param array $atts * @param array $args * * @return string */ public static function atts_truncate( $replace_with, $atts, $display, $args ) { $more_link_text = $atts['more_text'] ?? $atts['more_link_text'] ?? '. . .'; if ( ! empty( $atts['no_link'] ) ) { return FrmAppHelper::truncate( $replace_with, (int) $atts['truncate'], 3, $more_link_text ); } // If we're on the listing page of a Dynamic View, use detail link for truncate link if ( $display && $display->frm_show_count === 'dynamic' && $args['show'] === 'all' ) { $detail_link = self::get_detail_link( $atts, $display ); $more_link_text = ' ' . $more_link_text . ''; return FrmAppHelper::truncate( $replace_with, (int) $atts['truncate'], 3, $more_link_text ); } if ( ! $replace_with ) { return $replace_with; } $clean_text = trim( wp_strip_all_tags( $replace_with ) ); if ( ! $clean_text ) { return ''; } $part_one = FrmAppHelper::truncate( $clean_text, (int) $atts['truncate'], 3, '' ); $pos = strpos( $clean_text, $part_one ); // Only replace the first occurrence of the string. $part_two = substr_replace( $clean_text, '', $pos, strlen( $part_one ) ); if ( $part_two ) { return $part_one . ' ' . $more_link_text . '' . $part_two . ''; } return $replace_with; } public static function atts_clickable( $replace_with ) { return make_clickable( $replace_with ); } }