post_type ) { return false; } if ( 'publish' === $form->post_status ) { return true; } return current_user_can( 'read_post', $form_id ) || current_user_can( 'edit_post', $form_id ); } } if ( ! function_exists( 'stacklar_build_form_action_nonces' ) ) { /** * Build the current action nonce map for an executable form. * * @param int $form_id Form post ID. * @param array|null $config Optional prebuilt action configuration. * @return array */ function stacklar_build_form_action_nonces( $form_id, $config = null ) { $form_id = absint( $form_id ); if ( ! $form_id ) { return array(); } if ( ! is_array( $config ) ) { $config = function_exists( 'stacklar_build_form_actions_config' ) ? stacklar_build_form_actions_config( $form_id ) : array(); } $nonces = array(); $actions = isset( $config['actions'] ) && is_array( $config['actions'] ) ? $config['actions'] : array(); foreach ( $actions as $action_slug ) { $action_slug = sanitize_key( (string) $action_slug ); if ( '' !== $action_slug ) { $nonces[ $action_slug ] = wp_create_nonce( $action_slug . '_' . $form_id ); } } if ( ! empty( $config['restrictions']['enabled'] ) ) { $nonces['stacklar_restrictions_complete'] = wp_create_nonce( 'stacklar_restrictions_complete_' . $form_id ); } if ( ! empty( $config['surveyResults']['enabled'] ) ) { $nonces['stacklar_survey_results_complete'] = wp_create_nonce( 'stacklar_survey_results_complete_' . $form_id ); } if ( ! empty( $config['surveyResults']['publicResultsEnabled'] ) ) { $nonces['stacklar_survey_results_public'] = wp_create_nonce( 'stacklar_survey_results_public_' . $form_id ); } if ( ! empty( $config['payment']['statusEnabled'] ) ) { $nonces['stacklar_payment_status'] = wp_create_nonce( 'stacklar_payment_status_' . $form_id ); } if ( ! empty( $config['payment']['discountsEnabled'] ) ) { $nonces['stacklar_payment_discount'] = wp_create_nonce( 'stacklar_payment_discount_' . $form_id ); } return $nonces; } } if ( ! function_exists( 'stacklar_get_safe_registration_roles' ) ) { /** * Return the low-privilege roles that a public registration form may assign. * * The allowlist is intentionally narrow and remains subject to an absolute * capability denylist. A membership plugin may add a role through the filter, * but it cannot turn a content editor or administrator role into a public * registration target. * * @param int $form_id Form post ID. * @return array> Role definitions keyed by slug. */ function stacklar_get_safe_registration_roles( $form_id = 0 ) { if ( ! function_exists( 'wp_roles' ) ) { return array(); } $default_role = sanitize_key( (string) get_option( 'default_role', 'subscriber' ) ); $allowed_roles = array_values( array_filter( array_unique( array( 'subscriber', 'customer', $default_role ) ) ) ); $allowed_roles = apply_filters( 'stacklar_register_user_allowed_roles', $allowed_roles, absint( $form_id ) ); $allowed_roles = is_array( $allowed_roles ) ? array_values( array_unique( array_filter( array_map( 'sanitize_key', $allowed_roles ) ) ) ) : array(); $dangerous_capabilities = array( 'activate_plugins', 'create_users', 'delete_others_pages', 'delete_others_posts', 'edit_dashboard', 'edit_files', 'edit_others_pages', 'edit_others_posts', 'edit_pages', 'edit_plugins', 'edit_posts', 'edit_theme_options', 'edit_themes', 'edit_users', 'export', 'import', 'install_languages', 'install_plugins', 'install_themes', 'list_users', 'manage_categories', 'manage_links', 'manage_options', 'moderate_comments', 'promote_users', 'publish_pages', 'publish_posts', 'remove_users', 'switch_themes', 'unfiltered_html', 'unfiltered_upload', 'update_core', 'update_plugins', 'update_themes', 'upload_files', ); $roles = wp_roles(); $safe = array(); foreach ( $allowed_roles as $role_slug ) { if ( empty( $roles->roles[ $role_slug ] ) || ! is_array( $roles->roles[ $role_slug ] ) ) { continue; } $role_caps = isset( $roles->roles[ $role_slug ]['capabilities'] ) && is_array( $roles->roles[ $role_slug ]['capabilities'] ) ? $roles->roles[ $role_slug ]['capabilities'] : array(); $elevated = false; foreach ( $dangerous_capabilities as $capability ) { if ( ! empty( $role_caps[ $capability ] ) ) { $elevated = true; break; } } if ( ! $elevated ) { $safe[ $role_slug ] = $roles->roles[ $role_slug ]; } } return $safe; } } if ( ! function_exists( 'stacklar_render_saved_secret_hint' ) ) { /** * Tell administrators a secret is stored without printing it. * * @param string $saved_value Stored secret. * @return void */ function stacklar_render_saved_secret_hint( $saved_value ) { if ( '' === (string) $saved_value ) { return; } echo '

' . esc_html__( 'A value is saved. Leave blank to keep it.', 'stacklar-forms' ) . '

'; } } if ( ! function_exists( 'stacklar_is_truthy' ) ) { function stacklar_is_truthy( $value ) { if ( is_bool( $value ) ) { return $value; } if ( is_numeric( $value ) ) { return intval( $value ) === 1; } $value = is_string( $value ) ? strtolower( trim( $value ) ) : ''; return in_array( $value, array( '1', 'true', 'yes', 'on' ), true ); } } if ( ! function_exists( 'stacklar_parse_role_list' ) ) { function stacklar_parse_role_list( $value ) { $roles = array(); if ( is_array( $value ) ) { $roles = $value; } elseif ( is_string( $value ) ) { $roles = array_map( 'trim', explode( ',', $value ) ); } $roles = array_filter( array_map( 'sanitize_key', $roles ) ); return array_values( array_unique( $roles ) ); } } if ( ! function_exists( 'stacklar_current_user_has_role' ) ) { function stacklar_current_user_has_role( array $roles ) { if ( empty( $roles ) ) { return true; } if ( ! function_exists( 'wp_get_current_user' ) ) { return false; } $user = wp_get_current_user(); if ( ! $user || empty( $user->roles ) ) { return false; } foreach ( $user->roles as $role ) { if ( in_array( $role, $roles, true ) ) { return true; } } return false; } } if ( ! function_exists( 'stacklar_get_form_action_settings' ) ) { function stacklar_get_form_action_settings( $form_id ) { static $cache = array(); $form_id = intval( $form_id ); if ( isset( $cache[ $form_id ] ) ) { return function_exists( 'stacklar_multilingual' ) && stacklar_multilingual()->should_translate_runtime( $form_id ) ? stacklar_multilingual()->translate_action_settings( $form_id, $cache[ $form_id ] ) : $cache[ $form_id ]; } $settings = array(); if ( ! $form_id ) { $cache[ $form_id ] = $settings; return $settings; } $post = get_post( $form_id ); if ( ! $post || 'stacklar' !== $post->post_type ) { $cache[ $form_id ] = $settings; return $settings; } $blocks = parse_blocks( $post->post_content ); $walker = static function ( $blocks, &$settings ) use ( &$walker ) { if ( empty( $blocks ) || ! is_array( $blocks ) ) { return; } foreach ( $blocks as $block ) { if ( empty( $block['blockName'] ) ) { continue; } if ( 'stacklar/submit' === $block['blockName'] ) { $attrs = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array(); if ( ! empty( $attrs['actionSettings'] ) && is_array( $attrs['actionSettings'] ) ) { foreach ( $attrs['actionSettings'] as $slug => $values ) { $slug_key = sanitize_key( $slug ); if ( ! $slug_key || ! is_array( $values ) ) { continue; } foreach ( $values as $key => $value ) { $setting_key = sanitize_key( $key ); if ( '' === $setting_key || ( ! is_scalar( $value ) && null !== $value ) ) { continue; } if ( ! is_scalar( $value ) ) { $settings[ $slug_key ][ $setting_key ] = ''; continue; } $raw_value = (string) $value; $settings[ $slug_key ][ $setting_key ] = preg_match( '/(^|_)(message|body|html|content|description)$/', $setting_key ) ? sanitize_textarea_field( $raw_value ) : sanitize_text_field( $raw_value ); } } } } if ( ! empty( $block['innerBlocks'] ) ) { $walker( $block['innerBlocks'], $settings ); } } }; $walker( $blocks, $settings ); $cache[ $form_id ] = $settings; return function_exists( 'stacklar_multilingual' ) && stacklar_multilingual()->should_translate_runtime( $form_id ) ? stacklar_multilingual()->translate_action_settings( $form_id, $settings ) : $settings; } } if ( ! function_exists( 'stacklar_get_action_settings_for_action' ) ) { function stacklar_get_action_settings_for_action( $form_id, $action_slug ) { $all = stacklar_get_form_action_settings( $form_id ); $action_slug = sanitize_key( $action_slug ); return isset( $all[ $action_slug ] ) ? $all[ $action_slug ] : array(); } } if ( ! function_exists( 'stacklar_get_submit_block_attributes' ) ) { function stacklar_get_submit_block_attributes( $form_id ) { static $cache = array(); $form_id = intval( $form_id ); if ( isset( $cache[ $form_id ] ) ) { return $cache[ $form_id ]; } $attrs = array(); if ( ! $form_id ) { $cache[ $form_id ] = $attrs; return $attrs; } $post = get_post( $form_id ); if ( ! $post || 'stacklar' !== $post->post_type ) { $cache[ $form_id ] = $attrs; return $attrs; } $blocks = parse_blocks( $post->post_content ); $walker = static function ( $blocks ) use ( &$walker, &$attrs ) { if ( ! empty( $attrs ) ) { return; } if ( empty( $blocks ) || ! is_array( $blocks ) ) { return; } foreach ( $blocks as $block ) { if ( ! empty( $block['blockName'] ) && 'stacklar/submit' === $block['blockName'] ) { $attrs = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array(); return; } if ( ! empty( $block['innerBlocks'] ) ) { $walker( $block['innerBlocks'] ); } } }; $walker( $blocks ); $cache[ $form_id ] = $attrs; return $attrs; } } if ( ! function_exists( 'stacklar_get_attribution_default_settings' ) ) { function stacklar_get_attribution_default_settings() { return array( 'enabled' => false, 'captureUtm' => true, 'captureLandingPage' => true, 'captureCurrentPage' => true, 'captureReferrer' => true, 'captureBrowser' => true, 'captureJourney' => false, 'journeyLimit' => 10, 'consentRequired' => false, 'anonymizeIp' => false, 'retentionDays' => 0, ); } } if ( ! function_exists( 'stacklar_attribution_is_truthy' ) ) { function stacklar_attribution_is_truthy( $value ) { if ( function_exists( 'stacklar_is_truthy' ) ) { return stacklar_is_truthy( $value ); } if ( is_bool( $value ) ) { return $value; } if ( is_numeric( $value ) ) { return (int) $value === 1; } return in_array( strtolower( trim( (string) $value ) ), array( '1', 'true', 'yes', 'on' ), true ); } } if ( ! function_exists( 'stacklar_sanitize_attribution_settings' ) ) { function stacklar_sanitize_attribution_settings( $raw ) { $defaults = stacklar_get_attribution_default_settings(); $source = is_array( $raw ) ? $raw : array(); $settings = array_merge( $defaults, $source ); foreach ( array( 'enabled', 'captureUtm', 'captureLandingPage', 'captureCurrentPage', 'captureReferrer', 'captureBrowser', 'captureJourney', 'consentRequired', 'anonymizeIp' ) as $key ) { $settings[ $key ] = stacklar_attribution_is_truthy( isset( $source[ $key ] ) ? $source[ $key ] : $defaults[ $key ] ); } $journey_limit = isset( $source['journeyLimit'] ) ? absint( $source['journeyLimit'] ) : $defaults['journeyLimit']; $settings['journeyLimit'] = max( 1, min( 50, $journey_limit ? $journey_limit : $defaults['journeyLimit'] ) ); $retention_days = isset( $source['retentionDays'] ) ? absint( $source['retentionDays'] ) : $defaults['retentionDays']; $settings['retentionDays'] = min( 3650, $retention_days ); return $settings; } } if ( ! function_exists( 'stacklar_get_form_attribution_settings' ) ) { function stacklar_get_form_attribution_settings( $form_id ) { $attrs = stacklar_get_submit_block_attributes( $form_id ); return stacklar_sanitize_attribution_settings( isset( $attrs['attribution'] ) && is_array( $attrs['attribution'] ) ? $attrs['attribution'] : array() ); } } if ( ! function_exists( 'stacklar_is_form_test_mode' ) ) { function stacklar_is_form_test_mode( $form_id ) { $attrs = stacklar_get_submit_block_attributes( $form_id ); return ! empty( $attrs['testMode'] ) && stacklar_is_truthy( $attrs['testMode'] ); } } if ( ! function_exists( 'stacklar_anonymize_ip_address' ) ) { function stacklar_anonymize_ip_address( $ip ) { $ip = trim( (string) $ip ); if ( '' === $ip ) { return ''; } if ( false !== strpos( $ip, ':' ) ) { $packed = @inet_pton( $ip ); if ( false === $packed || strlen( $packed ) !== 16 ) { return sanitize_text_field( $ip ); } $bytes = unpack( 'C16', $packed ); for ( $i = 9; $i <= 16; $i++ ) { $bytes[ $i ] = 0; } $packed = pack( 'C16', $bytes[1], $bytes[2], $bytes[3], $bytes[4], $bytes[5], $bytes[6], $bytes[7], $bytes[8], $bytes[9], $bytes[10], $bytes[11], $bytes[12], $bytes[13], $bytes[14], $bytes[15], $bytes[16] ); $masked = @inet_ntop( $packed ); return false === $masked ? sanitize_text_field( $ip ) : $masked; } if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) { $parts = explode( '.', $ip ); $parts[3] = '0'; return implode( '.', $parts ); } return sanitize_text_field( $ip ); } } if ( ! function_exists( 'stacklar_request_value_to_string' ) ) { function stacklar_request_value_to_string( $value ) { if ( is_array( $value ) ) { if ( empty( $value ) ) { return ''; } $is_list = array_values( $value ) === $value; $parts = array(); foreach ( $value as $key => $item ) { $item_value = stacklar_request_value_to_string( $item ); if ( '' === $item_value ) { continue; } $parts[] = $is_list ? $item_value : sanitize_key( (string) $key ) . ': ' . $item_value; } return implode( $is_list ? ' | ' : '; ', $parts ); } if ( is_bool( $value ) ) { return $value ? '1' : '0'; } if ( null === $value ) { return ''; } return is_scalar( $value ) ? (string) $value : ''; } } if ( ! function_exists( 'stacklar_flatten_request_context' ) ) { function stacklar_flatten_request_context( array $context, $prefix = 'request' ) { $flat = array(); foreach ( $context as $key => $value ) { $key_part = is_int( $key ) ? (string) ( $key + 1 ) : sanitize_key( (string) $key ); if ( '' === $key_part ) { continue; } $token = sanitize_key( $prefix . '_' . $key_part ); if ( '' === $token ) { continue; } $flat[ $token ] = stacklar_request_value_to_string( $value ); if ( is_array( $value ) ) { $flat = array_merge( $flat, stacklar_flatten_request_context( $value, $token ) ); } } return $flat; } } if ( ! function_exists( 'stacklar_get_public_action_settings_allowlist' ) ) { /** * Action setting keys the frontend reads from the public form config. * * Everything else stays server-side, so a newly added setting (an API key, * a webhook URL, a recipient list) is never published unless it is listed * here. The "*" entry applies to every action. * * @return array */ function stacklar_get_public_action_settings_allowlist() { $allowlist = array( // src/frontend/submit.js decides which actions to run from these. '*' => array( 'conditions_enabled', 'conditions', 'conditions_logic' ), 'stacklar_submit_post' => array( 'editlinkenabled' ), 'stacklar_paypal' => array( 'flow', 'currency', 'amountfield', 'planid' ), 'stacklar_stripe' => array( 'flow', 'currency', 'amountfield', 'paymentbuttonlabel', 'confirmbuttonlabel', 'checkoutbuttonlabel', 'cancel_message', 'error_message', ), 'stacklar_woocommerce' => array( 'success_message', 'cancel_message', 'error_message' ), 'stacklar_redirect' => array( 'url', 'url_field', 'message', 'open_new_tab', 'delay_ms' ), ); /** * Filters the action setting keys exposed to the browser. * * @param array $allowlist Setting keys per action slug; "*" applies to all. */ $allowlist = apply_filters( 'stacklar_public_action_settings_allowlist', $allowlist ); return is_array( $allowlist ) ? $allowlist : array(); } } if ( ! function_exists( 'stacklar_filter_public_action_settings' ) ) { function stacklar_filter_public_action_settings( array $settings ) { $allowlist = stacklar_get_public_action_settings_allowlist(); $common = isset( $allowlist['*'] ) && is_array( $allowlist['*'] ) ? $allowlist['*'] : array(); /** * Filters setting keys that must stay hidden even when allowlisted. * * @param array $hidden Setting keys per action slug. */ $hidden = apply_filters( 'stacklar_public_hidden_action_settings', array() ); $hidden = is_array( $hidden ) ? $hidden : array(); $public = array(); foreach ( $settings as $slug => $values ) { $slug_key = sanitize_key( $slug ); if ( ! $slug_key || ! is_array( $values ) ) { continue; } $allowed = array_merge( $common, isset( $allowlist[ $slug_key ] ) && is_array( $allowlist[ $slug_key ] ) ? $allowlist[ $slug_key ] : array() ); $denied = isset( $hidden[ $slug_key ] ) && is_array( $hidden[ $slug_key ] ) ? $hidden[ $slug_key ] : array(); $allowed = array_diff( array_map( 'sanitize_key', $allowed ), array_map( 'sanitize_key', $denied ) ); $public[ $slug_key ] = array(); foreach ( $allowed as $setting_key ) { if ( '' !== $setting_key && array_key_exists( $setting_key, $values ) ) { $public[ $slug_key ][ $setting_key ] = $values[ $setting_key ]; } } } return $public; } } if ( ! function_exists( 'stacklar_parse_action_conditions' ) ) { function stacklar_parse_action_conditions( $raw ) { if ( empty( $raw ) ) { return array(); } if ( is_string( $raw ) ) { $decoded = json_decode( $raw, true ); if ( ! is_array( $decoded ) ) { $decoded = json_decode( wp_unslash( $raw ), true ); } } elseif ( is_array( $raw ) ) { $decoded = $raw; } else { $decoded = array(); } if ( ! is_array( $decoded ) ) { return array(); } $conditions = array(); foreach ( $decoded as $row ) { if ( ! is_array( $row ) ) { continue; } $field = isset( $row['field'] ) ? sanitize_key( preg_replace( '/\[\]$/', '', (string) $row['field'] ) ) : ''; if ( '' === $field ) { continue; } $operator = isset( $row['operator'] ) ? sanitize_key( (string) $row['operator'] ) : 'equals'; $allowed = array( 'equals', 'not_equals', 'contains', 'not_contains', 'empty', 'not_empty', 'greater_than', 'less_than', 'starts_with', 'ends_with' ); if ( ! in_array( $operator, $allowed, true ) ) { $operator = 'equals'; } $conditions[] = array( 'field' => $field, 'operator' => $operator, 'value' => isset( $row['value'] ) && is_scalar( $row['value'] ) ? sanitize_text_field( (string) $row['value'] ) : '', ); } return $conditions; } } if ( ! function_exists( 'stacklar_action_conditions_enabled' ) ) { function stacklar_action_conditions_enabled( array $settings ) { if ( empty( $settings['conditions_enabled'] ) || ! stacklar_is_truthy( $settings['conditions_enabled'] ) ) { return false; } return ! empty( stacklar_parse_action_conditions( isset( $settings['conditions'] ) ? $settings['conditions'] : '' ) ); } } if ( ! function_exists( 'stacklar_action_condition_sanitize_value' ) ) { function stacklar_action_condition_sanitize_value( $value ) { if ( is_array( $value ) ) { $clean = array(); foreach ( $value as $key => $item ) { $clean_key = is_string( $key ) ? sanitize_key( $key ) : $key; if ( '' === $clean_key && ! is_int( $key ) ) { continue; } $clean[ $clean_key ] = stacklar_action_condition_sanitize_value( $item ); } return $clean; } if ( is_bool( $value ) ) { return $value ? '1' : '0'; } if ( null === $value || ! is_scalar( $value ) ) { return ''; } return sanitize_text_field( (string) $value ); } } if ( ! function_exists( 'stacklar_action_condition_payload_from_request' ) ) { function stacklar_action_condition_payload_from_request() { $reserved = array( 'action', 'stacklar_id', '_stacklar_nonce', '_wp_http_referer', 'stacklar_actions', '_stacklar_nonce', 'stacklar_action_settings', 'stacklar_autosave_token', '_stacklar_autosave_nonce', '_stacklar_config_nonce', '_stacklar_recaptcha_nonce', 'g-recaptcha-response', 'cf-turnstile-response', ); $reserved = array_fill_keys( $reserved, true ); $payload = array(); // Called by the form actions after they verified the request nonce. foreach ( $_POST as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $key_str = is_string( $key ) ? $key : (string) $key; if ( isset( $reserved[ $key_str ] ) ) { continue; } if ( 0 === strpos( $key_str, '__stacklar_' ) || 0 === strpos( $key_str, '_stacklar_' ) ) { continue; } if ( substr( $key_str, -13 ) === '_intl_display' ) { continue; } $payload_key = sanitize_key( preg_replace( '/\[\]$/', '', $key_str ) ); if ( '' === $payload_key ) { continue; } $payload[ $payload_key ] = stacklar_action_condition_sanitize_value( wp_unslash( $value ) ); } return $payload; } } if ( ! function_exists( 'stacklar_action_condition_stringify' ) ) { function stacklar_action_condition_stringify( $value ) { if ( is_array( $value ) || is_object( $value ) ) { $json = wp_json_encode( $value ); return false === $json ? '' : $json; } if ( is_bool( $value ) ) { return $value ? '1' : '0'; } if ( null === $value || ! is_scalar( $value ) ) { return ''; } return (string) $value; } } if ( ! function_exists( 'stacklar_action_condition_values_for_field' ) ) { function stacklar_action_condition_values_for_field( array $payload, $field ) { $field = sanitize_key( preg_replace( '/\[\]$/', '', (string) $field ) ); if ( '' === $field || ! array_key_exists( $field, $payload ) ) { return array(); } $value = $payload[ $field ]; if ( is_array( $value ) ) { $values = array(); array_walk_recursive( $value, function ( $item ) use ( &$values ) { $values[] = stacklar_action_condition_stringify( $item ); } ); return $values; } return array( stacklar_action_condition_stringify( $value ) ); } } if ( ! function_exists( 'stacklar_action_condition_values_empty' ) ) { function stacklar_action_condition_values_empty( array $values ) { if ( empty( $values ) ) { return true; } foreach ( $values as $value ) { if ( '' !== trim( (string) $value ) ) { return false; } } return true; } } if ( ! function_exists( 'stacklar_evaluate_action_condition' ) ) { function stacklar_evaluate_action_condition( array $condition, array $payload ) { $values = stacklar_action_condition_values_for_field( $payload, $condition['field'] ); $expected = isset( $condition['value'] ) ? (string) $condition['value'] : ''; $operator = isset( $condition['operator'] ) ? sanitize_key( $condition['operator'] ) : 'equals'; $matches = static function ( callable $callback ) use ( $values ) { foreach ( $values as $value ) { if ( $callback( (string) $value ) ) { return true; } } return false; }; switch ( $operator ) { case 'not_equals': return ! $matches( static function ( $value ) use ( $expected ) { return $value === $expected; } ); case 'contains': return $matches( static function ( $value ) use ( $expected ) { return false !== strpos( $value, $expected ); } ); case 'not_contains': return ! $matches( static function ( $value ) use ( $expected ) { return false !== strpos( $value, $expected ); } ); case 'empty': return stacklar_action_condition_values_empty( $values ); case 'not_empty': return ! stacklar_action_condition_values_empty( $values ); case 'greater_than': return $matches( static function ( $value ) use ( $expected ) { return is_numeric( $value ) && is_numeric( $expected ) && floatval( $value ) > floatval( $expected ); } ); case 'less_than': return $matches( static function ( $value ) use ( $expected ) { return is_numeric( $value ) && is_numeric( $expected ) && floatval( $value ) < floatval( $expected ); } ); case 'starts_with': return $matches( static function ( $value ) use ( $expected ) { return 0 === strpos( $value, $expected ); } ); case 'ends_with': return $matches( static function ( $value ) use ( $expected ) { if ( '' === $expected ) { return true; } return substr( $value, -strlen( $expected ) ) === $expected; } ); case 'equals': default: return $matches( static function ( $value ) use ( $expected ) { return $value === $expected; } ); } } } if ( ! function_exists( 'stacklar_block_attribute_default' ) ) { /** * Default value of a Stacklar block attribute. * * Saved block comments omit attributes left at their default, so server * code that reads attrs from parse_blocks() needs the block.json default. * * @param string $block_name Block name, e.g. "stacklar/hidden". * @param string $key Attribute key. * @return mixed|null Default value, or null when none is declared. */ function stacklar_block_attribute_default( $block_name, $key ) { static $defaults = array(); $block_name = (string) $block_name; if ( ! isset( $defaults[ $block_name ] ) ) { $attributes = array(); if ( class_exists( 'WP_Block_Type_Registry' ) ) { $type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); if ( $type && is_array( $type->attributes ) ) { $attributes = $type->attributes; } } if ( empty( $attributes ) && 0 === strpos( $block_name, 'stacklar/' ) && defined( 'STACKLAR_PATH' ) ) { $metadata = STACKLAR_PATH . 'blocks/' . sanitize_key( substr( $block_name, 9 ) ) . '/block.json'; $decoded = is_readable( $metadata ) ? json_decode( (string) file_get_contents( $metadata ), true ) : null; if ( is_array( $decoded ) && isset( $decoded['attributes'] ) && is_array( $decoded['attributes'] ) ) { $attributes = $decoded['attributes']; } } $defaults[ $block_name ] = $attributes; } $schema = isset( $defaults[ $block_name ][ $key ] ) ? $defaults[ $block_name ][ $key ] : null; return is_array( $schema ) && array_key_exists( 'default', $schema ) ? $schema['default'] : null; } } if ( ! function_exists( 'stacklar_field_conditions_met' ) ) { /** * Whether a block's own show/hide conditions currently show it. * * Blocks store conditions in `conditional`, joined by `conditionalLogic` * ("and" by default); `conditionalEnabled` set to false turns them off. * * @param array $attrs Block attributes. * @param array $payload Submitted payload the conditions read from. * @return bool */ function stacklar_field_conditions_met( array $attrs, array $payload ) { $conditions = isset( $attrs['conditional'] ) && is_array( $attrs['conditional'] ) ? $attrs['conditional'] : array(); if ( empty( $conditions ) || ( array_key_exists( 'conditionalEnabled', $attrs ) && empty( $attrs['conditionalEnabled'] ) ) ) { return true; } $results = array(); foreach ( $conditions as $condition ) { if ( ! is_array( $condition ) || empty( $condition['field'] ) || empty( $condition['operator'] ) ) { continue; } $results[] = stacklar_evaluate_action_condition( $condition, $payload ); } if ( empty( $results ) ) { return true; } if ( 'or' === ( isset( $attrs['conditionalLogic'] ) ? sanitize_key( $attrs['conditionalLogic'] ) : 'and' ) ) { return in_array( true, $results, true ); } return ! in_array( false, $results, true ); } } if ( ! function_exists( 'stacklar_action_conditions_should_run' ) ) { function stacklar_action_conditions_should_run( array $settings, array $payload ) { if ( ! stacklar_action_conditions_enabled( $settings ) ) { return true; } $conditions = stacklar_parse_action_conditions( isset( $settings['conditions'] ) ? $settings['conditions'] : '' ); if ( empty( $conditions ) ) { return true; } $logic = isset( $settings['conditions_logic'] ) && 'or' === sanitize_key( (string) $settings['conditions_logic'] ) ? 'or' : 'and'; foreach ( $conditions as $condition ) { $matched = stacklar_evaluate_action_condition( $condition, $payload ); if ( 'or' === $logic && $matched ) { return true; } if ( 'and' === $logic && ! $matched ) { return false; } } return 'and' === $logic; } } if ( ! function_exists( 'stacklar_build_server_actions_config' ) ) { function stacklar_build_server_actions_config( $form_id ) { $attrs = stacklar_get_submit_block_attributes( $form_id ); $raw_actions = array(); if ( isset( $attrs['actions'] ) && is_array( $attrs['actions'] ) ) { $raw_actions = $attrs['actions']; } $actions = array(); foreach ( $raw_actions as $action ) { $slug = sanitize_key( $action ); if ( $slug ) { $actions[] = $slug; } } $actions = array_values( array_unique( $actions ) ); // Email is the only action in Stacklar Forms; a form saved with // Stacklar Forms Pro keeps its other actions, and they are skipped here. $actions = array_values( array_intersect( $actions, array( 'stacklar_mail' ) ) ); $actions = apply_filters( 'stacklar_server_action_order', $actions, $form_id, $attrs ); $actions = array_values( array_unique( array_filter( array_map( 'sanitize_key', is_array( $actions ) ? $actions : array() ) ) ) ); $action_settings = array(); if ( ! empty( $attrs['actionSettings'] ) && is_array( $attrs['actionSettings'] ) ) { foreach ( $attrs['actionSettings'] as $slug => $values ) { $slug_key = sanitize_key( $slug ); if ( ! $slug_key || ! is_array( $values ) ) { continue; } foreach ( $values as $key => $value ) { $setting_key = sanitize_key( $key ); if ( '' === $setting_key ) { continue; } if ( is_scalar( $value ) ) { $action_settings[ $slug_key ][ $setting_key ] = sanitize_text_field( $value ); } } } } $messages = array(); if ( ! empty( $attrs['messageSuccessText'] ) ) { $messages['success'] = sanitize_text_field( $attrs['messageSuccessText'] ); } if ( ! empty( $attrs['messageErrorText'] ) ) { $messages['error'] = sanitize_text_field( $attrs['messageErrorText'] ); } $protection = function_exists( 'stacklar_submission_protection_service' ) ? stacklar_submission_protection_service()->get_form_settings( $form_id ) : array(); $restrictions = function_exists( 'stacklar_restrictions_service' ) ? stacklar_restrictions_service()->get_public_settings( $form_id ) : array(); $survey_results = function_exists( 'stacklar_survey_results_service' ) ? stacklar_survey_results_service()->get_public_settings( $form_id ) : array(); $partial_submissions = class_exists( 'Stacklar_Abandonments', false ) ? Stacklar_Abandonments::get_public_settings( $form_id ) : array(); $payment = function_exists( 'stacklar_payment_status_service' ) ? stacklar_payment_status_service()->get_public_payment_config( $form_id ) : array(); $attribution = function_exists( 'stacklar_get_form_attribution_settings' ) ? stacklar_get_form_attribution_settings( $form_id ) : array(); $test_mode = function_exists( 'stacklar_is_form_test_mode' ) ? stacklar_is_form_test_mode( $form_id ) : false; return array( 'actions' => $actions, 'lockedActions' => array(), 'actionSettings' => $action_settings, 'messages' => $messages, 'protection' => $protection, 'restrictions' => $restrictions, 'surveyResults' => $survey_results, 'partialSubmissions' => $partial_submissions, 'payment' => $payment, 'attribution' => $attribution, 'testMode' => array( 'enabled' => (bool) $test_mode, ), ); } } if ( ! function_exists( 'stacklar_build_form_actions_config' ) ) { function stacklar_build_form_actions_config( $form_id ) { $config = function_exists( 'stacklar_build_server_actions_config' ) ? stacklar_build_server_actions_config( $form_id ) : array(); if ( empty( $config ) || ! is_array( $config ) ) { return array( 'actions' => array(), 'lockedActions' => array(), 'actionSettings' => array(), 'messages' => array(), 'protection' => array(), 'restrictions' => array(), 'surveyResults' => array(), 'partialSubmissions' => array(), 'payment' => array(), 'attribution' => stacklar_get_attribution_default_settings(), 'testMode' => array( 'enabled' => false ), ); } $config['actionSettings'] = stacklar_filter_public_action_settings( isset( $config['actionSettings'] ) && is_array( $config['actionSettings'] ) ? $config['actionSettings'] : array() ); if ( ! empty( $config['lockedActions'] ) && is_array( $config['lockedActions'] ) ) { foreach ( $config['lockedActions'] as $locked_action ) { $locked_key = sanitize_key( (string) $locked_action ); if ( isset( $config['actionSettings'][ $locked_key ] ) ) { unset( $config['actionSettings'][ $locked_key ] ); } } } if ( ! isset( $config['protection'] ) || ! is_array( $config['protection'] ) ) { $config['protection'] = array(); } if ( ! isset( $config['restrictions'] ) || ! is_array( $config['restrictions'] ) ) { $config['restrictions'] = array(); } if ( ! isset( $config['surveyResults'] ) || ! is_array( $config['surveyResults'] ) ) { $config['surveyResults'] = array(); } if ( ! isset( $config['partialSubmissions'] ) || ! is_array( $config['partialSubmissions'] ) ) { $config['partialSubmissions'] = array(); } if ( ! isset( $config['payment'] ) || ! is_array( $config['payment'] ) ) { $config['payment'] = array(); } $config['attribution'] = function_exists( 'stacklar_sanitize_attribution_settings' ) ? stacklar_sanitize_attribution_settings( isset( $config['attribution'] ) && is_array( $config['attribution'] ) ? $config['attribution'] : array() ) : array(); $config['testMode'] = array( 'enabled' => ! empty( $config['testMode']['enabled'] ), ); return $config; } } if ( ! function_exists( 'stacklar_sanitize_css_value' ) ) { /** * Validate one CSS value built from block attributes (a colour, length, * font family, box shorthand, ...). * * The value may only use characters that cannot end the declaration, the * rule or the