PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.15
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.15
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.15, at classes/models/FrmFormState.php

243 lines 6.2 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
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 ( ! self::open_ssl_is_installed() ) {
149 return;
150 }
151 if ( ! $this->state && ! self::get_state_from_request() ) {
152 return;
153 }
154 $state_string = $this->get_state_string();
155 echo '<input name="frm_state" type="hidden" value="' . esc_attr( $state_string ) . '" />';
156 }
157
158 /**
159 * @return string
160 */
161 private function get_state_string() {
162 if ( ! self::open_ssl_is_installed() ) {
163 return '';
164 }
165 $secret = self::get_encryption_secret();
166 $compressed_state = $this->compressed_state();
167 $json_encoded = json_encode( $compressed_state );
168 $encrypted = openssl_encrypt( $json_encoded, 'AES-128-ECB', $secret );
169 return $encrypted;
170 }
171
172 /**
173 * Returns true if open SSL is installed.
174 *
175 * @since 6.12
176 * @return bool
177 */
178 private static function open_ssl_is_installed() {
179 return function_exists( 'openssl_encrypt' );
180 }
181
182 /**
183 * Return state but with shorter keys to use for the state string.
184 *
185 * @return array
186 */
187 private function compressed_state() {
188 $compressed = array();
189 foreach ( $this->state as $key => $value ) {
190 $compressed[ self::compressed_key( $key ) ] = $value;
191 }
192 return $compressed;
193 }
194
195 /**
196 * Get the first character of a key to make the option take less space.
197 * "title" => "t".
198 * "description" => "d".
199 *
200 * @param string $key
201 * @return string
202 */
203 private static function compressed_key( $key ) {
204 return $key[0];
205 }
206
207 /**
208 * Keys are truncated to a single character to make the state string smaller.
209 * Pro supports additional keys include "i" for include_fields and "g" for get params.
210 * To avoid conflicts, we should not add "i" or "g" in Lite for another state property.
211 *
212 * @param string $key
213 * @return string The full key name if one is found. If nothing is found, the $key param is passed back.
214 */
215 private static function decompressed_key( $key ) {
216 switch ( $key ) {
217 case 'd':
218 return 'description';
219 case 't':
220 return 'title';
221 }
222 return $key;
223 }
224
225 /**
226 * @return string
227 */
228 private static function get_encryption_secret() {
229 $secret_key = get_option( 'frm_form_state_key' );
230
231 // If we already have the secret, send it back.
232 if ( false !== $secret_key ) {
233 return base64_decode( $secret_key ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
234 }
235
236 // We don't have a secret, so let's generate one.
237 $secret_key = is_callable( 'sodium_crypto_secretbox_keygen' ) ? sodium_crypto_secretbox_keygen() : wp_generate_password( 32, true, true );
238 update_option( 'frm_form_state_key', base64_encode( $secret_key ), 'no' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
239
240 return $secret_key;
241 }
242 }
243