PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.8.2
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.8.2
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / models / FrmFormState.php

FrmFormState.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.8.2, at classes/models/FrmFormState.php

227 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 die( 'You are not allowed to call this page directly.' );
5 }
6
7 /**
8 * Track form state in an encrypted form field.
9 * The state just holds some basic info, like if a [formidable] shortcode loaded
10 * with a title=1 or description=1 option.
11 *
12 * @since 6.2
13 */
14 class FrmFormState {
15
16 /**
17 * @var FrmFormState $instance
18 */
19 private static $instance;
20
21 /**
22 * @var array
23 */
24 private $state;
25
26 private function __construct() {
27 $this->state = array();
28 }
29
30 /**
31 * @param string $key
32 * @param mixed $value
33 * @return void
34 */
35 public static function set_initial_value( $key, $value ) {
36 if ( is_callable( 'FrmProFormState::set_initial_value' ) ) {
37 // Let Pro handle state.
38 return;
39 }
40
41 self::maybe_initialize();
42 self::$instance->set( $key, $value );
43 }
44
45 /**
46 * @return bool true if just initialized.
47 */
48 private static function maybe_initialize() {
49 if ( empty( self::$instance ) ) {
50 self::$instance = new self();
51 return true;
52 }
53 return false;
54 }
55
56 /**
57 * @param string $key
58 * @param mixed $value
59 * @return void
60 */
61 public function set( $key, $value ) {
62 $this->state[ $key ] = $value;
63 }
64
65 /**
66 * @param string $key
67 * @param mixed $default
68 * @return mixed
69 */
70 public static function get_from_request( $key, $default ) {
71 if ( self::maybe_initialize() ) {
72 self::get_state_from_request();
73 }
74 return self::$instance->get( $key, $default );
75 }
76
77 public function get( $key, $default ) {
78 if ( isset( $this->state[ $key ] ) ) {
79 return $this->state[ $key ];
80 }
81 return $default;
82 }
83
84 /**
85 * Render a basic version of the state field from Pro.
86 * This is required only when submitting with AJAX.
87 * It is used to track the value of a title=1|0 or description=1|0 option in a [formidable] shortcode.
88 *
89 * @param stdClass $form
90 * @return void
91 */
92 public static function maybe_render_state_field( $form ) {
93 if ( is_callable( 'FrmProFormState::maybe_render_state_field' ) ) {
94 // Let Pro handle state when Pro is available.
95 // This way we can also avoid duplicate state fields if Pro isn't up to date.
96 return;
97 }
98
99 if ( empty( $form->options['ajax_submit'] ) ) {
100 // This is only required for AJAX submit.
101 return;
102 }
103
104 if ( empty( self::$instance ) && ! self::get_state_from_request() ) {
105 return;
106 }
107
108 $state_title = ! empty( self::$instance->state['title'] ) ? 1 : 0;
109 $state_description = ! empty( self::$instance->state['description'] ) ? 1 : 0;
110 $settings_title = ! empty( $form->options['show_title'] ) ? 1 : 0;
111 $settings_description = ! empty( $form->options['show_description'] ) ? 1 : 0;
112
113 if ( $state_title === $settings_title && $state_description === $settings_description ) {
114 // Avoid state field if it matches form settings.
115 return;
116 }
117
118 self::$instance->render_state_field();
119 }
120
121 /**
122 * @return bool true if there is valid state data in the request.
123 */
124 private static function get_state_from_request() {
125 $encrypted_state = FrmAppHelper::get_post_param( 'frm_state', '', 'sanitize_text_field' );
126 if ( ! $encrypted_state ) {
127 return false;
128 }
129 $secret = self::get_encryption_secret();
130 $decrypted_state = openssl_decrypt( $encrypted_state, 'AES-128-ECB', $secret );
131 if ( false === $decrypted_state ) {
132 return false;
133 }
134 $decoded_state = json_decode( $decrypted_state, true );
135 if ( ! is_array( $decoded_state ) ) {
136 return false;
137 }
138 foreach ( $decoded_state as $key => $value ) {
139 self::set_initial_value( self::decompressed_key( $key ), $value );
140 }
141 return true;
142 }
143
144 /**
145 * @return void
146 */
147 public function render_state_field() {
148 if ( ! $this->state && ! self::get_state_from_request() ) {
149 return;
150 }
151 $state_string = $this->get_state_string();
152 echo '<input name="frm_state" type="hidden" value="' . esc_attr( $state_string ) . '" />';
153 }
154
155 /**
156 * @return string
157 */
158 private function get_state_string() {
159 $secret = self::get_encryption_secret();
160 $compressed_state = $this->compressed_state();
161 $json_encoded = json_encode( $compressed_state );
162 $encrypted = openssl_encrypt( $json_encoded, 'AES-128-ECB', $secret );
163 return $encrypted;
164 }
165
166 /**
167 * Return state but with shorter keys to use for the state string.
168 *
169 * @return array
170 */
171 private function compressed_state() {
172 $compressed = array();
173 foreach ( $this->state as $key => $value ) {
174 $compressed[ self::compressed_key( $key ) ] = $value;
175 }
176 return $compressed;
177 }
178
179 /**
180 * Get the first character of a key to make the option take less space.
181 * "title" => "t".
182 * "description" => "d".
183 *
184 * @param string $key
185 * @return string
186 */
187 private static function compressed_key( $key ) {
188 return $key[0];
189 }
190
191 /**
192 * Keys are truncated to a single character to make the state string smaller.
193 * Pro supports additional keys include "i" for include_fields and "g" for get params.
194 * To avoid conflicts, we should not add "i" or "g" in Lite for another state property.
195 *
196 * @param string $key
197 * @return string The full key name if one is found. If nothing is found, the $key param is passed back.
198 */
199 private static function decompressed_key( $key ) {
200 switch ( $key ) {
201 case 'd':
202 return 'description';
203 case 't':
204 return 'title';
205 }
206 return $key;
207 }
208
209 /**
210 * @return string
211 */
212 private static function get_encryption_secret() {
213 $secret_key = get_option( 'frm_form_state_key' );
214
215 // If we already have the secret, send it back.
216 if ( false !== $secret_key ) {
217 return base64_decode( $secret_key ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
218 }
219
220 // We don't have a secret, so let's generate one.
221 $secret_key = is_callable( 'sodium_crypto_secretbox_keygen' ) ? sodium_crypto_secretbox_keygen() : wp_generate_password( 32, true, true );
222 update_option( 'frm_form_state_key', base64_encode( $secret_key ), 'no' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
223
224 return $secret_key;
225 }
226 }
227