PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13.1 1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 All 212 releases
charitable / includes / admin / campaign-builder / ajax-actions.php

ajax-actions.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at includes/admin/campaign-builder/ajax-actions.php

1,515 lines 58.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // Exit if accessed directly.
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 /**
9 * Ajax actions related to the campaign builder used in by admin.
10 *
11 * @package Charitable
12 * @since 1.8.0
13 * @version 1.8.9.1
14 */
15
16 /**
17 * Get and return template data via ajax.
18 *
19 * @since 1.8.0
20 */
21 function charitable_get_campaign_builder_template_data() {
22
23 if ( empty( $_POST['id'] ) ) { // phpcs:ignore
24 wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'charitable' ) );
25 }
26
27 $builder_template = new Charitable_Campaign_Builder_Templates();
28 $template_id = ! empty( $_POST['id'] ) ? esc_attr( $_POST['id'] ) : false; // phpcs:ignore
29
30 if ( ! $template_id ) {
31 wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'charitable' ) );
32 }
33
34 // get the title if passed so we can create a temp $campaign_data array to custom populare a template down the road.
35 $campaign_title = ! empty( $_POST['title'] ) ? esc_html( $_POST['title'] ) : false; // phpcs:ignore
36 $campaign_data = array();
37 if ( $campaign_title ) {
38 $campaign_data['title'] = $campaign_title;
39 }
40
41 $no_cache = apply_filters( 'charitable_campaign_builder_ajax_template_no_cache', true );
42
43 $template_data = $builder_template->get_template_data( $template_id, $campaign_data, $no_cache );
44
45 if ( $template_data ) {
46
47 wp_send_json_success( $template_data );
48
49 } else {
50
51 wp_send_json_error( esc_html__( 'Something went wrong while attempting to get template information.', 'charitable' ) );
52 }
53 }
54 add_action( 'wp_ajax_charitable_get_campaign_builder_template_data', 'charitable_get_campaign_builder_template_data' );
55
56 /**
57 * Get and return template preview via ajax.
58 *
59 * @since 1.8.0
60 */
61 function charitable_get_campaign_builder_template_preview() {
62
63 if ( empty( $_POST['id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
64 wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'charitable' ) );
65 }
66
67 $template_id = intval( $_POST['id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
68
69 if ( 0 === $template_id ) {
70 wp_send_json_error( esc_html__( 'Something went wrong with getting the template while performing this action.', 'charitable' ) );
71 }
72
73 $builder_template = new Charitable_Campaign_Builder_Templates();
74 $template_preview = $builder_template->get_template_preview( $template_id );
75
76 if ( $template_preview ) {
77
78 $response = array( 'html' => $template_preview );
79
80 wp_send_json_success( $response );
81
82 } else {
83
84 wp_send_json_error( esc_html__( 'Something went wrong while attempting to get the template information.', 'charitable' ) );
85
86 }
87 }
88 add_action( 'wp_ajax_charitable_get_campaign_builder_template_preview', 'charitable_get_campaign_builder_template_preview' );
89
90
91 /**
92 * Save a campaign via ajax.
93 *
94 * @since 1.8.0
95 */
96 function charitable_save_campaign() {
97
98 // Run a security check.
99 if ( ! check_ajax_referer( 'charitable-builder', 'nonce', false ) ) {
100 wp_send_json_error( esc_html__( 'Your session expired. Please reload the builder.', 'charitable' ) );
101 }
102
103 // Check for permissions.
104 if ( ! charitable_current_user_can( 'edit_campaigns' ) ) {
105 wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'charitable' ) );
106 }
107
108 // Check for form data.
109 if ( empty( $_POST['data'] ) ) {
110 wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'charitable' ) );
111 }
112
113 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, decoded and sanitized in loop
114 // $campaign_post = json_decode( wp_unslash( $_POST['data'] ), ARRAY_A );
115 $campaign_post = array_column( json_decode( wp_unslash( $_POST['data'] ), ARRAY_A ), null, 'name' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, decoded and sanitized in loop
116 $is_preview = isset( $_POST['preview'] ) && 'true' === $_POST['preview'] ? true : false;
117 $data = [
118 'fields' => [],
119 ];
120
121 $campaign_settings_v2 = array();
122
123 // Note: We have to first get the layout from the template because the incoming data is only the fields the user has placed into the form.
124 // If we don't do this, if the user doesn't put anything into a particular column or row, that won't be included in the POST and we'll lose that data.
125
126 $campaign_settings_v2 = charitable_template_layout_to_campaign_layout( $campaign_settings_v2, $campaign_post );
127
128 // this tracks where tabs might be in the layout.
129 $campaign_tabs_location = array();
130
131 if ( $campaign_post ) {
132
133 foreach ( $campaign_post as $post_field_name => $post_field_data ) {
134
135 // This process has to check for various formats of fields that appear in the form on the screen hence the multiple if statements.
136
137 if ( strpos( $post_field_name, '__' ) !== false && strpos( $post_field_name, 'xxx' ) === false ) { // this covers fields that are like EXAMPLE__EXAMPLE__EXAMPLE.
138
139 $array_level = explode( '__', $post_field_name );
140 if ( count( $array_level ) === 4 ) {
141 $campaign_settings_v2[ $array_level[0] ][ $array_level[1] ][ $array_level[2] ][ $array_level[3] ] = $post_field_data['value'];
142 } elseif ( count( $array_level ) === 3 ) {
143 $campaign_settings_v2[ $array_level[0] ][ $array_level[1] ][ $array_level[2] ] = $post_field_data['value'];
144 } elseif ( count( $array_level ) === 2 ) {
145 $campaign_settings_v2[ $array_level[0] ][ $array_level[1] ] = $post_field_data['value'];
146 } elseif ( count( $array_level ) === 2 ) {
147 $campaign_settings_v2[ $array_level[0] ] = $post_field_data['value'];
148 }
149
150 } elseif ( strpos( $post_field_name, '[donation_amounts]' ) !== false && strpos( $post_field_name, '[campaign_suggested_donations_default]' ) !== false ) {
151
152 $campaign_settings_v2['settings']['donation-options']['suggested_donations_default'] = $post_field_data['value'];
153
154 } elseif ( strpos( $post_field_name, 'donation_amounts' ) !== false && strpos( $post_field_name, '_fields' ) !== false ) {
155
156 continue;
157
158 } elseif ( strpos( $post_field_name, '[donation_amounts]' ) !== false && strpos( $post_field_name, '[campaign_suggested_donations_default]' ) === false ) {
159
160 $post_field_name = str_replace( 'settings]', '', $post_field_name );
161 $post_field_name = str_replace( '[donation-options]', '', $post_field_name );
162 $post_field_name = str_replace( '[donation_amounts]', '', $post_field_name );
163
164 $output = charitable_convert_campaign_string_to_array( $post_field_name, $post_field_data['value'] );
165 $_tmp_array = $output[ array_key_first( $output ) ];
166
167 $first_key = esc_attr( array_key_first( $output ) );
168 $second_key = esc_attr( array_key_first( $_tmp_array ) );
169
170 // this would avoid field 0 which should be a hidden field for the purpose of adding dynamic rows via Javascript.
171 if ( 0 === intval( $first_key ) ) {
172 continue;
173 }
174
175 $value = ( 'amount' === esc_attr( $second_key ) ) ? charitable_sanitize_amount( $post_field_data['value'] ) : wp_strip_all_tags( $post_field_data['value'] );
176
177 // if ( intval( $first_key ) !== 0 ) { // this would avoid field 0 which should be a hidden field for the purpose of adding dynamic rows via Javascript.
178 $campaign_settings_v2['settings']['donation-options']['donation_amounts'][ $first_key ][ $second_key ] = $value;
179 // }
180
181 } elseif ( strpos( $post_field_name, '[recurring_donation_amounts]' ) !== false && strpos( $post_field_name, '[campaign_suggested_recurring_donations_default]' ) !== false ) {
182
183 $campaign_settings_v2['settings']['donation-options']['suggested_recurring_donations_default'] = $post_field_data['value'];
184
185 } elseif ( strpos( $post_field_name, '[recurring_donation_amounts]' ) !== false && strpos( $post_field_name, '[campaign_suggested_recurring_donations_default]' ) === false ) {
186
187 $post_field_name = str_replace( 'settings]', '', $post_field_name );
188 $post_field_name = str_replace( '[donation-options]', '', $post_field_name );
189 $post_field_name = str_replace( '[recurring_donation_amounts]', '', $post_field_name );
190
191 $output = charitable_convert_campaign_string_to_array( $post_field_name, $post_field_data['value'] );
192 $_tmp_array = $output[ array_key_first( $output ) ];
193
194 $first_key = esc_attr( array_key_first( $output ) );
195 $second_key = esc_attr( array_key_first( $_tmp_array ) );
196
197 // this would avoid field 0 which should be a hidden field for the purpose of adding dynamic rows via Javascript.
198 if ( 0 === intval( $first_key ) ) {
199 continue;
200 }
201
202 $value = ( 'amount' === esc_attr( $second_key ) ) ? charitable_sanitize_amount( $post_field_data['value'] ) : wp_strip_all_tags( $post_field_data['value'] );
203
204 // if ( intval( $first_key ) !== 0 ) { // this would avoid field 0 which should be a hidden field for the purpose of adding dynamic rows via Javascript.
205 $campaign_settings_v2['settings']['donation-options']['recurring_donation_amounts'][ $first_key ][ $second_key ] = $value;
206 // }
207
208 } elseif ( strpos( $post_field_name, '[recurring_donation_periods_to_display]' ) !== false ) {
209
210 $post_field_name = str_replace( 'settings[', '', $post_field_name );
211 $post_field_name = str_replace( 'donation-options]', '', $post_field_name );
212 $post_field_name = str_replace( '[recurring_donation_periods_to_display]', '', $post_field_name );
213
214 $output = charitable_convert_campaign_string_to_array_new( $post_field_name, $post_field_data['value'] );
215
216 $first_key = esc_attr( array_key_first( $output ) );
217 $value = wp_strip_all_tags( $post_field_data['value'] );
218
219 $campaign_settings_v2['settings']['donation-options']['recurring_donation_periods_to_display'][ $first_key ] = $value;
220
221 } elseif ( strpos( $post_field_name, '[row]' ) !== false && strpos( $post_field_name, '[column]' ) !== false && strpos( $post_field_name, '[row-type-tabs]' ) === false ) {
222
223 $post_field_name = str_replace( 'layout[row]', '', $post_field_name );
224 $post_field_name = str_replace( '[column]', '', $post_field_name );
225 $post_field_name = str_replace( '[section]', '', $post_field_name );
226 $post_field_name = str_replace( '[fields]', '', $post_field_name );
227
228 $output = charitable_convert_campaign_string_to_array_new( $post_field_name, $post_field_data['value'], 'photo' );
229
230 $row_type = array_key_first( $output );
231 $row_id = array_key_first( $output[ $row_type ] );
232 $row_css = array_key_first( $output[ $row_type ][ $row_id ] );
233 $column_id = array_key_first( $output[ $row_type ][ $row_id ][ $row_css ] );
234 $section_type = array_key_first( $output[ $row_type ][ $row_id ][ $row_css ][ $column_id ] );
235 $section_id = array_key_first( $output[ $row_type ][ $row_id ][ $row_css ][ $column_id ][ $section_type ] );
236
237 $row_type = str_replace( 'row-type-', '', $row_type );
238 $section_type = str_replace( 'section-type-', '', $section_type );
239
240 if ( 'row' === $row_type || 'header' === $row_type ) {
241
242 $campaign_settings_v2['layout']['rows'][ $row_id ]['type'] = ( false !== $row_type ) ? esc_attr( $row_type ) : 'row';
243
244 if ( 'tabs' === $section_type ) {
245
246 $tab_id = array_key_first( $output[ 'row-type-' . $row_type ][ $row_id ][ $row_css ][ $column_id ][ 'section-type-' . $section_type ][ $section_id ]['tabs'] );
247 $field_id_key = array_key_first( $output[ 'row-type-' . $row_type ][ $row_id ][ $row_css ][ $column_id ][ 'section-type-' . $section_type ][ $section_id ]['tabs'][ $tab_id ] );
248
249 $campaign_settings_v2['layout']['rows'][ $row_id ]['type'] = $row_type;
250 $campaign_settings_v2['layout']['rows'][ $row_id ]['css_class'] = esc_attr( $row_css );
251 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['type'] = 'tabs';
252 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['tabs'][ $tab_id ]['title'] = $campaign_post[ 'tabs__' . $tab_id . '__title' ]['value'];
253 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['tabs'][ $tab_id ]['type'] = 'fields';
254 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['tabs'][ $tab_id ]['slug'] = '';
255 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['tabs'][ $tab_id ]['fields'][] = $field_id_key;
256
257 $campaign_tabs_location = array(
258 'row_id' => $row_id,
259 'column_id' => $column_id,
260 'section_id' => $section_id,
261 );
262
263 } elseif ( 'fields' === $section_type ) {
264
265 $field_id_key = array_key_first( $output[ 'row-type-' . $row_type ][ $row_id ][ $row_css ][ $column_id ][ 'section-type-' . $section_type ][ $section_id ] );
266
267 $campaign_settings_v2['layout']['rows'][ $row_id ]['type'] = $row_type;
268 $campaign_settings_v2['layout']['rows'][ $row_id ]['css_class'] = esc_attr( $row_css );
269 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['type'] = 'fields';
270 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['fields'][] = $field_id_key;
271
272 }
273
274 $campaign_settings_v2['layout']['rows'][ $row_id ]['fields'][ $field_id_key ] = $post_field_data['value'];
275
276 }
277
278 } elseif ( strpos( $post_field_name, '[row]' ) !== false && strpos( $post_field_name, '[blocks]' ) !== false && strpos( $post_field_name, '[row-type-tabs]' ) !== false ) {
279
280 $post_field_name = str_replace( 'layout[row][row-type-tabs][999]', '', $post_field_name );
281 $post_field_name = str_replace( '[blocks]', '', $post_field_name );
282
283 $output = charitable_convert_campaign_string_to_array( $post_field_name, $post_field_data['value'] );
284
285 $_tmp_array = $output[ array_key_first( $output ) ];
286
287 $tab_key = array_key_first( $output );
288 $field_key = array_key_first( $_tmp_array );
289
290 $campaign_settings_v2['layout']['rows'][999]['type'] = 'tabs';
291 $campaign_settings_v2['layout']['rows'][999]['fields'][ $field_key ] = $post_field_data['value'];
292 $campaign_settings_v2['layout']['rows'][999]['tabs'][ $tab_key ][] = $field_key;
293
294 } elseif ( strpos( $post_field_name, '_fields' ) !== false ) {
295
296 $post_field_name = str_replace( '_fields', '', $post_field_name );
297
298 $output = charitable_convert_campaign_string_to_array( $post_field_name, $post_field_data['value'] );
299
300 $_tmp_array = $output[ array_key_first( $output ) ];
301 $_tmp_array_2 = is_array( $_tmp_array ) ? $_tmp_array[ array_key_first( $_tmp_array ) ] : false;
302
303 $first_key = array_key_first( $output );
304 $second_key = array_key_first( $_tmp_array );
305 $third_key = $_tmp_array_2 !== false && is_array( $_tmp_array_2 ) && '' !== array_key_first( $_tmp_array_2 ) ? array_key_first( $_tmp_array_2 ) : false;
306
307 // Santitize here.
308 // todo: this needs to be cleaned up.
309 if ( in_array( $second_key, array( 'css_class' ), true ) ) {
310 // Remove all characters from this string that would not make it a valid css value or attribute.
311 $post_field_data['value'] = str_replace( array( '"', "'", '`' ), '', $post_field_data['value'] );
312 $post_field_data['value'] = preg_replace( '/[^a-zA-Z0-9_,\/$:\-\.!\s;]/', '', $post_field_data['value'] );
313 $post_field_data['value'] = esc_attr( $post_field_data['value'] );
314 } elseif ( in_array( $second_key, array( 'background_image' ), true ) ) {
315 $post_field_data['value'] = esc_url_raw( $post_field_data['value'] );
316 } elseif ( in_array( $second_key, array( 'locked' ), true ) ) {
317 $post_field_data['value'] = ! empty( $post_field_data['value'] ) ? 1 : 0;
318 }
319
320 if ( null !== $third_key && false !== $third_key && '' !== trim( $third_key ) ) {
321 $campaign_settings_v2['fields'][ $first_key ][ $second_key ][ $third_key ] = $post_field_data['value'];
322 } else {
323 $campaign_settings_v2['fields'][ $first_key ][ $second_key ] = $post_field_data['value'];
324 }
325
326 } elseif ( strpos( $post_field_name, '][' ) !== false ) { // this covers EXAMPLE[EXAMPLE][EXAMPLE].
327
328 $output = array();
329 $pointer = &$output;
330 $input = $post_field_name;
331 $counter = ( substr_count( $input, '[' ) ) - 1;
332 while ( ( $index = strpos( $input, '[' ) ) !== false ) {
333 if ( $index != 0 ) {
334 $key = substr( $input, 0, $index );
335 $pointer[ $key ] = array();
336 $pointer = &$pointer[ $key ];
337 $input = substr( $input, $index );
338 continue;
339 }
340 $end_index = strpos( $input, ']' );
341 $array_key = substr( $input, $index + 1, $end_index - 1 );
342 if ( $counter <= 1 ) {
343 $pointer[ $array_key ] = array();
344 } elseif ( is_array( $pointer ) ) {
345 $pointer[ $array_key ] = isset( $post_field_data['value'] ) ? $post_field_data['value'] : false;
346 }
347 if ( is_array( $pointer ) ) {
348 $pointer = &$pointer[ $array_key ];
349 }
350 $input = substr( $input, $end_index + 1 );
351 ++$counter;
352 }
353
354 $campaign_settings_v2 = array_merge_recursive( $campaign_settings_v2, $output );
355
356 } elseif ( strpos( $post_field_name, 'xxx' ) === false ) {
357
358 $campaign_settings_v2[ $post_field_name ] = $post_field_data['value'];
359
360 }
361
362 }
363 }
364
365 if ( empty( $campaign_tabs_location ) ) {
366 // either there is NO tabs in this template, or there's at least one tab but has no fields in it. Let's got hunting for tabs.
367 foreach ( $campaign_settings_v2['layout']['rows'] as $row_id => $row ) {
368 foreach ( $row['columns'] as $column_id => $column ) {
369 foreach ( $column['sections'] as $section_id => $section ) {
370 if ( 'tabs' === $section['type'] ) {
371 $campaign_tabs_location = array(
372 'row_id' => $row_id,
373 'column_id' => $column_id,
374 'section_id' => $section_id,
375 );
376 break;
377 }
378 }
379 }
380 }
381 }
382
383 // We still check the tabs location because the above might not have found any tabs (likely because on purpose the template doesn't have any).
384 if ( ! empty( $campaign_tabs_location ) ) {
385 // get the list of current tabs in the layout from what we remembered/recorded from the location when we when through things above.
386 $campaign_tabs = $campaign_settings_v2['layout']['rows'][ $campaign_tabs_location['row_id'] ]['columns'][ $campaign_tabs_location['column_id'] ]['sections'][ $campaign_tabs_location['section_id'] ]['tabs'];
387 if ( ! empty( $campaign_tabs ) && ! empty( $campaign_settings_v2['tabs'] ) ) {
388 foreach ( $campaign_settings_v2['tabs'] as $settings_campaign_tab_id => $settings_campaign_tab ) {
389 // if there is no tab in the tabs field in a section, but there's a tab registered in the "settings" tab area, then add it to the layout (likely an empty tab?).
390 if ( ! isset( $campaign_tabs[ $settings_campaign_tab_id ] ) || empty( $campaign_tabs[ $settings_campaign_tab_id ] ) ) {
391 // tab section - ensure even empty tabs are included.
392 $campaign_settings_v2['layout']['rows'][ $campaign_tabs_location['row_id'] ]['columns'][ $campaign_tabs_location['column_id'] ]['sections'][ $campaign_tabs_location['section_id'] ]['tabs'][ $settings_campaign_tab_id ] = array(
393 'title' => $settings_campaign_tab['title'],
394 'type' => 'fields',
395 'slug' => '',
396 'fields' => array(),
397 );
398 }
399 }
400 }
401 }
402
403 // Remove any tabs in the layout tab section that aren't in the main tabs directory.
404 if ( ! empty( $campaign_tabs_location ) ) {
405 $campaign_tabs = $campaign_settings_v2['layout']['rows'][ $campaign_tabs_location['row_id'] ]['columns'][ $campaign_tabs_location['column_id'] ]['sections'][ $campaign_tabs_location['section_id'] ]['tabs'];
406 foreach ( $campaign_tabs as $section_tab_id => $campaign_tab ) {
407 // if there is a tab in the tabs field in a section, but there's no tab registered in the "settings" tab area, then remove it from the layout.
408 if ( ! isset( $campaign_settings_v2['tabs'][ $section_tab_id ] ) || empty( $campaign_settings_v2['tabs'][ $section_tab_id ] ) ) {
409 unset( $campaign_tabs[ $section_tab_id ] );
410 }
411 }
412 $campaign_settings_v2['layout']['rows'][ $campaign_tabs_location['row_id'] ]['columns'][ $campaign_tabs_location['column_id'] ]['sections'][ $campaign_tabs_location['section_id'] ]['tabs'] = $campaign_tabs;
413 }
414
415 // If there are tabs in the section/layout but the tab directory is empty, we need to clear out the section/layout tabs.
416 if ( ! empty( $campaign_tabs_location ) && empty( $campaign_settings_v2['tabs'] ) ) {
417 $campaign_settings_v2['layout']['rows'][ $campaign_tabs_location['row_id'] ]['columns'][ $campaign_tabs_location['column_id'] ]['sections'][ $campaign_tabs_location['section_id'] ]['tabs'] = array();
418 }
419
420 // tags.
421 $custom_tax_terms = array();
422 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, decoded and sanitized in loop
423 if ( ! empty( $_POST['data'] ) ) {
424 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, decoded and sanitized in loop
425 $_tmp_data = json_decode( wp_unslash( $_POST['data'] ) );
426 foreach ( $_tmp_data as $index => $object ) {
427 if ( $object->name === 'settings[general][tags]' ) {
428 $custom_tax_terms[] = intval( $object->value );
429 }
430 }
431 $campaign_settings_v2['settings']['general']['tags'] = $custom_tax_terms;
432 }
433
434 // categories.
435 $custom_cat_terms = array();
436 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, decoded and sanitized in loop
437 if ( ! empty( $_POST['data'] ) ) {
438 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, decoded and sanitized in loop
439 $_tmp_data = json_decode( wp_unslash( $_POST['data'] ) );
440 foreach ( $_tmp_data as $index => $object ) {
441 if ( strpos( $object->name, 'categories-' ) !== false ) {
442 $custom_cat_terms[] = intval( $object->value );
443 }
444 }
445 }
446
447 // Starting cleaning up.
448
449 // Remove any donation option that doesn't have an amount.
450 if ( isset( $campaign_settings_v2['settings']['donation-options']['donation_amounts'] ) ) {
451 foreach ( $campaign_settings_v2['settings']['donation-options']['donation_amounts'] as $key => $info ) {
452 if ( isset( $info['amount'] ) && 0 === intval( $info['amount'] ) ) {
453 unset( $campaign_settings_v2['settings']['donation-options']['donation_amounts'][ $key ] );
454 }
455 }
456 }
457
458 // Remove any fields that are associated with feedback forms.
459 foreach ( $campaign_settings_v2 as $key => $info ) {
460 if ( strpos( $key, '-feedback-form' ) !== false ) {
461 unset( $campaign_settings_v2[ $key ] );
462 }
463 }
464
465 // convert date formats.
466 if ( isset( $campaign_settings_v2['settings']['general']['end_date'] ) ) {
467 $campaign_settings_v2['settings']['general']['end_date'] = Charitable_Campaign::sanitize_campaign_end_date( $campaign_settings_v2['settings']['general']['end_date'] );
468 }
469
470 // Sanitize some misc fields.
471 $campaign_settings_v2['settings']['general']['goal'] = ! empty( $campaign_settings_v2['settings']['general']['goal'] ) ? Charitable_Currency::get_instance()->sanitize_monetary_amount( (string) $campaign_settings_v2['settings']['general']['goal'] ) : false;
472 $campaign_settings_v2['settings']['general']['goal'] = '0' == $campaign_settings_v2['settings']['general']['goal'] ? '' : $campaign_settings_v2['settings']['general']['goal'];
473
474 // if the money values are negative, make it positive.
475 if ( ! empty( $campaign_settings_v2['settings']['general']['goal'] ) && '-' === substr( $campaign_settings_v2['settings']['general']['goal'], 0, 1 ) ) {
476 $campaign_settings_v2['settings']['general']['goal'] = substr( $campaign_settings_v2['settings']['general']['goal'], 1 );
477 }
478 if ( ! empty( $campaign_settings_v2['settings']['donation-options']['minimum_donation_amount'] ) && '-' === substr( $campaign_settings_v2['settings']['donation-options']['minimum_donation_amount'], 0, 1 ) ) {
479 $campaign_settings_v2['settings']['donation-options']['minimum_donation_amount'] = substr( $campaign_settings_v2['settings']['donation-options']['minimum_donation_amount'], 1 );
480 }
481
482 $campaign_settings_v2['field_id'] = charitable_find_highest_field_key( $campaign_settings_v2 );
483
484 // Update OLD v1 campaign data.
485 if ( intval( $campaign_post['id']['value'] ) === 0 ) {
486 // new campaign.
487 $campaign_id = charitable_create_campaign(
488 array(
489 'title' => isset( $campaign_post['title']['value'] ) ? $campaign_post['title']['value'] : false,
490 'description' => isset( $campaign_post['setings_desc']['value'] ) ? $campaign_post['setings_desc']['value'] : false,
491 )
492 );
493 } else {
494 // existing campaign.
495 $campaign_id = charitable_create_campaign(
496 array(
497 'ID' => isset( $campaign_post['id']['value'] ) ? $campaign_post['id']['value'] : false,
498 'title' => isset( $campaign_post['title']['value'] ) ? $campaign_post['title']['value'] : false,
499 'description' => isset( $campaign_post['setings_desc'] ) ? $campaign_post['setings_desc']['value'] : false,
500 )
501 );
502 }
503
504 // Update campaign data for v1.
505 $processor = new Charitable_Campaign_Processor();
506 $processor->campaign_id = $campaign_id;
507
508 // Base meta values.
509 $base_meta_keys = array(
510 'goal' => 0,
511 'end_date' => 0,
512 'suggested_donations' => array(),
513 'suggested_donations_default' => 0,
514 'allow_custom_donations' => 0,
515 'minimum_donation_amount' => 0,
516 'donate_button_text' => '',
517 ); // taken from class-charitable-campaign-processor.php.
518 foreach ( $base_meta_keys as $base_meta_key => $base_meta_value ) {
519 $value = charitable_search_settings( $campaign_settings_v2['settings'], $base_meta_key );
520 if ( $value ) {
521 $processor->save_meta_field( $value, $base_meta_key );
522 } else {
523 $processor->save_meta_field( $base_meta_value, $base_meta_key );
524 }
525 }
526
527 if ( ! empty( $campaign_settings_v2['settings']['donation-options']['donation_amounts'] ) ) {
528 update_post_meta( $campaign_id, '_campaign_suggested_donations', ( $campaign_settings_v2['settings']['donation-options']['donation_amounts'] ) );
529 } else {
530 update_post_meta( $campaign_id, '_campaign_suggested_donations', ( array() ) );
531 }
532
533 if ( ! empty( $campaign_settings_v2['settings']['donation-options']['suggested_donations_default'] ) ) {
534 $suggest_donation_row_id = intval( $campaign_settings_v2['settings']['donation-options']['suggested_donations_default'] );
535 $array_to_serialize = array( html_entity_decode( charitable_format_money( $campaign_settings_v2['settings']['donation-options']['donation_amounts'][ $suggest_donation_row_id ]['amount'] ) ) );
536 update_post_meta( $campaign_id, '_campaign_suggested_donations_default', $array_to_serialize );
537 } else {
538 update_post_meta( $campaign_id, '_campaign_suggested_donations_default', ( serialize( array() ) ) );
539 }
540
541 /* descriptions */
542 if ( ! empty( $campaign_settings_v2['settings']['general']['description'] ) ) {
543 update_post_meta( $campaign_id, '_campaign_description', trim( $campaign_settings_v2['settings']['general']['description'] ) ); // todo: santitize this.
544 } else {
545 update_post_meta( $campaign_id, '_campaign_description', false );
546 }
547 if ( ! empty( $campaign_settings_v2['settings']['general']['extended_description'] ) ) {
548 $campaign_post_update = array(
549 'ID' => $campaign_id,
550 'post_content' => $campaign_settings_v2['settings']['general']['extended_description'],
551 );
552 } elseif ( ! empty( $campaign_settings_v2['settings']['general']['description'] ) ) {
553 $campaign_post_update = array(
554 'ID' => $campaign_id,
555 'post_content' => $campaign_settings_v2['settings']['general']['description'],
556 );
557 } else {
558 $campaign_post_update = array(
559 'ID' => $campaign_id,
560 'post_content' => '',
561 );
562 }
563
564 /* update campaign author which should be the campaign_creator_id, if it exists (otherwise leave it alone) */
565 if ( ! empty( $campaign_settings_v2['settings']['general']['campaign_creator_id'] ) ) {
566 $campaign_post_update['post_author'] = intval( $campaign_settings_v2['settings']['general']['campaign_creator_id'] );
567 }
568
569 /* tags */
570 wp_set_post_terms( $campaign_id, $custom_tax_terms, 'campaign_tag' );
571
572 /* categories */
573 wp_set_post_terms( $campaign_id, $custom_cat_terms, 'campaign_category' );
574
575 // Update the post status of the campaign.
576 $post_status = false;
577 // In the next statement, false means simply nothing was passed and if that happens we don't change anything.
578 $desired_post_status = isset( $_POST['status'] ) ? esc_attr( $_POST['status'] ) : false; // phpcs:ignore
579
580 if ( $desired_post_status ) {
581
582 switch ( $desired_post_status ) {
583 case 'draft':
584 case 'pending':
585 case 'publish':
586 case 'private':
587 case 'future':
588 $post_status = $desired_post_status;
589 break;
590
591 case 'switch-draft':
592 $post_status = 'draft';
593 break;
594
595 case 'switch-review':
596 $post_status = 'pending';
597 break;
598
599 default:
600 break;
601 }
602
603 if ( $post_status ) {
604 $campaign_post_update['post_status'] = apply_filters( 'charitable_campaign_builder_post_status_on_save', $post_status, $campaign_id );
605 }
606
607 $campaign_settings_v2['post_status'] = $post_status;
608 $campaign_settings_v2['post_status_label'] = ucfirst( $post_status );
609
610 }
611
612 // IF this isn't a preview request, update the post into the database.
613 if ( ! $is_preview ) :
614 wp_update_post( $campaign_post_update );
615 endif;
616
617 /* donation summary */
618 $summary_hide_keys = array(
619 'campaign_hide_amount_donated',
620 'campaign_hide_number_of_donors',
621 'campaign_hide_percent_raised',
622 'campaign_hide_time_remaining',
623 );
624
625 if ( ! empty( $campaign_settings_v2['settings']['campaign-summary'] ) ) {
626 foreach ( $summary_hide_keys as $meta_key ) {
627 if ( ! empty( $campaign_settings_v2['settings']['campaign-summary'][ $meta_key ] ) ) {
628 update_post_meta( $campaign_id, '_' . $meta_key, array( str_replace( 'campaign_', '', $meta_key ) ) );
629 } else {
630 update_post_meta( $campaign_id, '_' . $meta_key, '' );
631 }
632 }
633 } else {
634 foreach ( $summary_hide_keys as $meta_key ) {
635 update_post_meta( $campaign_id, '_' . $meta_key, '' );
636 }
637 }
638
639 if ( ! empty( $campaign_settings_v2['settings']['campaign-summary']['donation_button_text'] ) ) {
640 update_post_meta( $campaign_id, '_campaign_donate_button_text', trim( wp_strip_all_tags( $campaign_settings_v2['settings']['campaign-summary']['donation_button_text'] ) ) );
641 } else {
642 update_post_meta( $campaign_id, '_campaign_donate_button_text', false );
643 }
644
645 if ( $campaign_id && 0 === intval( $campaign_settings_v2['id'] ) ) {
646 $campaign_settings_v2['id'] = intval( $campaign_id );
647 }
648
649 // Adjust campaign creator ID if applicable.
650 if ( isset( $campaign_settings_v2['settings']['campaign-creator']['campaign_creator_id'] ) ) {
651 $creator_id = intval( $campaign_settings_v2['settings']['campaign-creator']['campaign_creator_id'] );
652 if ( 0 !== $creator_id ) { // user id must exist in the posted data.
653 $user = get_userdata( $creator_id ); // user must exist.
654 if ( $user !== false ) {
655 $arg = array(
656 'ID' => $campaign_id,
657 'post_author' => $creator_id,
658 );
659 wp_update_post( $arg );
660 }
661 }
662
663 }
664
665 if ( isset( $campaign_settings_v2['tabs'] ) && is_array( $campaign_settings_v2['tabs'] ) ) :
666 $campaign_settings_v2['tab_order'] = array_keys( $campaign_settings_v2['tabs'] );
667 endif;
668
669 if ( isset( $campaign_settings_v2['layout']['_header']['fields'] ) && is_array( $campaign_settings_v2['layout']['_header']['fields'] ) ) :
670 $campaign_settings_v2['layout']['_header']['order'] = array_keys( $campaign_settings_v2['layout']['_header']['fields'] );
671 endif;
672 if ( isset( $campaign_settings_v2['layout']['_tabs'] ) && is_array( $campaign_settings_v2['layout']['_tabs'] ) ) :
673 foreach ( $campaign_settings_v2['layout']['_tabs'] as $tab_id => $tab_info ) :
674 $campaign_settings_v2['layout']['_tabs'][ $tab_id ]['order'] = array_keys( $campaign_settings_v2['layout']['_tabs'][ $tab_id ]['fields'] );
675 endforeach;
676 endif;
677
678
679 // todo: remove this at some point - this was likely just because of testing.
680 unset( $campaign_settings_v2['settings]'] );
681
682 // Allow addons to update, sync, or override settings before they are saved.
683 $campaign_settings_v2 = apply_filters( 'charitable_campaign_builder_save_campaign_settings', $campaign_settings_v2, $campaign_id );
684
685 // Save the settings as post metadata.
686 if ( $is_preview ) {
687 // If this is a preview request, save the settings as a transient ONLY.
688 set_transient( 'charitable_campaign_preview_' . $campaign_id, $campaign_settings_v2, DAY_IN_SECONDS );
689 } else {
690 // IF this isn't a preview request, update the post into the database and update the transient (the preview at this point should match the real thing).
691 set_transient( 'charitable_campaign_preview_' . $campaign_id, $campaign_settings_v2, DAY_IN_SECONDS );
692 update_post_meta( $campaign_id, 'campaign_settings_v2', $campaign_settings_v2 );
693 }
694
695 /**
696 * Fires after updating campaign data.
697 *
698 * @since 1.8.0
699 *
700 * @param int $campaign_id campaign ID.
701 * @param array $data campaign data.
702 */
703 do_action( 'charitable_builder_save_campaign', $campaign_id, $campaign_settings_v2, $campaign_post, $is_preview );
704
705 if ( ! $campaign_id ) {
706 wp_send_json_error( esc_html__( 'Something went wrong while saving the campaign.', 'charitable' ) );
707 }
708
709 // Response data, start with the campaign settings.
710 $response_data = $campaign_settings_v2;
711 $response_data['redirect'] = admin_url( 'admin.php?page=charitable-overview' );
712 $response_data['campaign_id'] = $campaign_id;
713 $response_data['preview_url'] = charitable_get_campaign_preview_url( $campaign_id, true ); // get_preview_post_link( $campaign_id );
714 $response_data['permalink'] = get_permalink( $campaign_id );
715
716 /**
717 * Allows filtering ajax response data after form was saved.
718 *
719 * @since 1.8.0
720 *
721 * @param array $response_data The data to be sent in the response.
722 * @param int $campaign_id Campaign ID.
723 * @param array $data Campaign data.
724 */
725 $response_data = apply_filters(
726 'charitable_builder_save_campaign_response_data',
727 $response_data,
728 $campaign_id,
729 $data
730 );
731
732 wp_send_json_success( $response_data );
733 }
734 add_action( 'wp_ajax_charitable_save_campaign', 'charitable_save_campaign' );
735
736 /**
737 * Converts the template layout to campaign layout.
738 *
739 * @param array $campaign_settings_v2 The campaign settings.
740 * @param array $campaign_post The campaign post data.
741 *
742 * @return array The updated campaign settings.
743 */
744 function charitable_template_layout_to_campaign_layout( $campaign_settings_v2, $campaign_post ) {
745
746 $builder_template = new Charitable_Campaign_Builder_Templates();
747 $template_id = ! empty( $campaign_post['template_id']['value'] ) ? esc_attr( $campaign_post['template_id']['value'] ) : false;
748 $template_data = ( false !== $template_id ) ? $builder_template->get_template_data( $template_id ) : array();
749
750 $campaign_settings_v2['layout']['rows'] = array();
751
752 // Ensure fields array exists to prevent undefined key errors.
753 if ( ! isset( $campaign_settings_v2['fields'] ) ) {
754 $campaign_settings_v2['fields'] = array();
755 }
756
757 $row_id = 0;
758 $column_id = 0;
759 $section_id = 0;
760
761 // Ensure template_data layout is an array before iterating.
762 if ( ! is_array( $template_data ) || ! isset( $template_data['layout'] ) || ! is_array( $template_data['layout'] ) ) {
763 $template_data['layout'] = array();
764 }
765
766 foreach ( $template_data['layout'] as $row_id => $row ) :
767
768 $campaign_settings_v2['layout']['rows'][ $row_id ]['type'] = ! empty( $row['type'] ) ? $row['type'] : 'row';
769 $campaign_settings_v2['layout']['rows'][ $row_id ]['css_class'] = ! empty( $row['css_class'] ) ? $row['css_class'] : false;
770 $campaign_settings_v2['layout']['rows'][ $row_id ]['background_image'] = ! empty( $row['background_image'] ) ? esc_url_raw( $row['background_image'] ) : '';
771 $campaign_settings_v2['layout']['rows'][ $row_id ]['locked'] = ! empty( $row['locked'] ) ? 1 : 0;
772
773 foreach ( $row['columns'] as $column_key => $column ) :
774
775 foreach ( $column as $section_key => $section ) :
776
777 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'] = array();
778
779 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['type'] = esc_attr( $section['type'] );
780
781 switch ( $section['type'] ) {
782 case 'fields':
783 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['fields'] = array(); // [0] => 1 /// (array) $section['fields'];
784
785 break;
786 case 'tabs':
787 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['tabs'] = array(); // [0] => 1 /// (array) $section['fields'];
788
789 if ( ! empty( $section['tabs'] ) ) {
790 foreach ( $section['tabs'] as $tab_id => $tab_info ) :
791 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ][ $section['type'] ][ $tab_id ] = array(
792 'title' => ! empty( $campaign_post[ 'tabs__' . $tab_id . '__title' ]['value'] ) ? sanitize_text_field( $campaign_post[ 'tabs__' . $tab_id . '__title' ]['value'] ) : $tab_info['title'],
793 'type' => $tab_info['type'],
794 'slug' => sanitize_title( $tab_info['slug'] ),
795 'fields' => array(),
796 );
797 endforeach;
798 }
799
800 break;
801 default:
802 // $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ][ $section['type'] ] = $section[ $section['type'] ];
803 break;
804 }
805
806 ++$column_id;
807 ++$section_id;
808
809 endforeach;
810
811 // $column_id++;
812
813 endforeach;
814
815 $campaign_settings_v2['layout']['rows'][ $row_id ]['fields'] = array();
816
817 ++$row_id;
818
819 endforeach;
820
821 /*
822 * Copy template field data to campaign settings to preserve complex data
823 * structures. This is especially important for fields with nested data
824 * like list fields.
825 *
826 * Ported from Charitable Pro (1.8.13): without this second pass,
827 * $campaign_settings_v2['fields'] is left empty, so a freshly created
828 * visual campaign has row/column/section skeletons that reference field
829 * ids with no field DATA (headline, content, etc.) behind them at all —
830 * content.php has nothing to render for any of them.
831 */
832 if ( ! empty( $template_data['layout'] ) && is_array( $template_data['layout'] ) ) {
833 if ( charitable_is_debug() ) {
834 error_log( 'Charitable: Copying template field data to campaign settings' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
835 }
836
837 $field_id = 0;
838
839 foreach ( $template_data['layout'] as $row ) {
840 if ( ! empty( $row['columns'] ) && is_array( $row['columns'] ) ) {
841 foreach ( $row['columns'] as $column ) {
842 if ( ! empty( $column ) && is_array( $column ) ) {
843 foreach ( $column as $section ) {
844 if ( ! empty( $section['type'] ) ) {
845 if ( 'fields' === $section['type'] && ! empty( $section['fields'] ) ) {
846 // Process regular fields.
847 foreach ( $section['fields'] as $field_data ) {
848 if ( is_array( $field_data ) && ! empty( $field_data['type'] ) ) {
849 // Copy the field data to preserve complex structures.
850 $campaign_settings_v2['fields'][ $field_id ] = $field_data;
851
852 if ( 'list' === $field_data['type'] && ! empty( $field_data['list_items'] ) && charitable_is_debug() ) {
853 error_log( 'Charitable: Copied list field ' . $field_id . ' from template with list_items: ' . print_r( $field_data['list_items'], true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r
854 }
855 }
856
857 ++$field_id;
858 }
859 } elseif ( 'tabs' === $section['type'] && ! empty( $section['tabs'] ) ) {
860 // Process tab fields.
861 foreach ( $section['tabs'] as $tab ) {
862 if ( ! empty( $tab['fields'] ) && is_array( $tab['fields'] ) ) {
863 foreach ( $tab['fields'] as $field_data ) {
864 if ( is_array( $field_data ) && ! empty( $field_data['type'] ) ) {
865 // Copy the field data to preserve complex structures.
866 $campaign_settings_v2['fields'][ $field_id ] = $field_data;
867
868 if ( 'list' === $field_data['type'] && ! empty( $field_data['list_items'] ) && charitable_is_debug() ) {
869 error_log( 'Charitable: Copied list field ' . $field_id . ' from template with list_items: ' . print_r( $field_data['list_items'], true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r
870 }
871 }
872
873 ++$field_id;
874 }
875 }
876 }
877 }
878 }
879 }
880 }
881 }
882 }
883 }
884
885 if ( charitable_is_debug() ) {
886 $fields_count = isset( $campaign_settings_v2['fields'] ) && is_array( $campaign_settings_v2['fields'] ) ? count( $campaign_settings_v2['fields'] ) : 0;
887 error_log( 'Charitable: Template field data copying complete. Total fields: ' . $fields_count ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
888 }
889 }
890
891 return $campaign_settings_v2;
892 }
893
894 /**
895 * Searches for a key in a multidimensional array.
896 *
897 * @param array $array The array to search.
898 * @param mixed $key The key to search for.
899 * @return mixed|null The value of the key if found, null otherwise.
900 */
901 function charitable_search_settings( $array, $key ) {
902 // If the key exists in the current level of the array, return its value.
903 if ( array_key_exists( $key, $array ) ) {
904 return $array[ $key ];
905 }
906
907 // Iterate over each element in the array.
908 foreach ( $array as $value ) {
909 // If the element is an array, recursively search it for the key.
910 if ( is_array( $value ) ) {
911 $result = charitable_search_settings( $value, $key );
912
913 // If the key is found in the nested array, return its value.
914 if ( $result !== null ) {
915 return $result;
916 }
917 }
918 }
919
920 // The key was not found in the array.
921 return null;
922 }
923
924
925 /**
926 * Find the highest field ID field so we can keep the max number accurate.
927 *
928 * @since 1.8.0
929 *
930 * @param array $array Settings data.
931 */
932 function charitable_find_highest_field_key( $array ) {
933 $highest_key = null;
934
935 // Iterate through each element in the array.
936 foreach ( $array as $key => $value ) {
937 // Check if the current element is an array.
938 if ( is_array( $value ) ) {
939 // Recursively call the function to search through nested arrays.
940 $field_key = charitable_find_highest_field_key( $value );
941
942 // Check if the key is numeric and greater than the current highest key.
943 if ( is_numeric( $field_key ) && ( $highest_key === null || $field_key > $highest_key ) ) {
944 $highest_key = $field_key;
945 }
946 } else {
947 // Check if the key is numeric and greater than the current highest key.
948 if ( is_numeric( $key ) && ( $highest_key === null || $key > $highest_key ) ) {
949 $highest_key = $key;
950 }
951 }
952 }
953
954 return $highest_key;
955 }
956
957 /**
958 * A very particular util array for the builder.
959 *
960 * @since 1.8.0
961 *
962 * @param string $string The string from the settings data.
963 * @param string $value Value.
964 * @param string $end_key End key.
965 */
966 function charitable_convert_campaign_string_to_array( $string, $value, $end_key = 'amount' ) {
967
968 $keys = explode( '][', trim( $string, '[]' ) ); // split the string into an array of keys.
969 $array = [];
970 $current = &$array;
971 foreach ( $keys as $key ) {
972 $current[ $key ] = [];
973 if ( $key === $end_key ) {
974 $current[ $key ] = $value; // $value;
975 }
976 $current = &$current[ $key ];
977 }
978
979 return $array;
980 }
981
982 /**
983 * Convert a campaign string to an array.
984 *
985 * @param string $input The input string to convert.
986 * @param mixed $value The default value to use for each key.
987 * @param string $end_key The key to use for the final value.
988 * @return array The resulting array.
989 */
990 function charitable_convert_campaign_string_to_array_new( $input, $value, $end_key = 'amount' ) { // phpcs:ignore
991
992 $output = array();
993 $pointer = &$output;
994
995 while ( ( $index = strpos( $input, '[' ) ) !== false ) {
996 if ( $index != 0 ) {
997 $key = substr( $input, 0, $index );
998 $pointer[ $key ] = array();
999 $pointer = &$pointer[ $key ];
1000 $input = substr( $input, $index );
1001 continue;
1002 }
1003 $end_index = strpos( $input, ']' );
1004 $array_key = substr( $input, $index + 1, $end_index - 1 );
1005 $pointer[ $array_key ] = array();
1006 $pointer = &$pointer[ $array_key ];
1007 $input = substr( $input, $end_index + 1 );
1008 }
1009
1010 return( $output );
1011 }
1012
1013 /**
1014 * Create a new form.
1015 *
1016 * @since 1.8.0
1017 */
1018 function charitable_new_campaign() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
1019
1020 check_ajax_referer( 'charitable-builder', 'nonce' );
1021
1022 if ( empty( $_POST['title'] ) ) {
1023 wp_send_json_error(
1024 [
1025 'error_type' => 'missing_campaign_title',
1026 'message' => esc_html__( 'No form name provided.', 'charitable' ),
1027 ]
1028 );
1029 }
1030
1031 $campaign_title = sanitize_text_field( wp_unslash( $_POST['title'] ) );
1032 $campaign_template = empty( $_POST['template'] ) ? 'blank' : sanitize_text_field( wp_unslash( $_POST['template'] ) );
1033
1034 if ( ! charitable()->get( 'builder_templates' )->is_valid_template( $campaign_template ) ) {
1035 wp_send_json_error(
1036 [
1037 'error_type' => 'invalid_template',
1038 'message' => esc_html__( 'The template you selected is currently not available, but you can try again later. If you continue to have trouble, please reach out to support.', 'charitable' ),
1039 ]
1040 );
1041 }
1042
1043 /*
1044 * Does a campaign with this title already exist? This has to run *before* the new
1045 * campaign is created, otherwise it would match the new campaign itself.
1046 *
1047 * Replaces get_page_by_title(), which was deprecated in WP 6.2 and was being passed
1048 * the post type 'charitable' rather than 'campaign' — so it never matched anything and
1049 * the disambiguating suffix below never ran.
1050 */
1051 $charitable_existing_titles = new WP_Query(
1052 [
1053 'post_type' => Charitable::CAMPAIGN_POST_TYPE,
1054 'post_status' => 'any',
1055 'title' => $campaign_title,
1056 'posts_per_page' => 1,
1057 'fields' => 'ids',
1058 'no_found_rows' => true,
1059 'update_post_meta_cache' => false,
1060 'update_post_term_cache' => false,
1061 ]
1062 );
1063
1064 $title_exists = ! empty( $charitable_existing_titles->posts );
1065 $campaign_id = charitable()->get( 'form' )->add(
1066 $campaign_title,
1067 [],
1068 [
1069 'template' => $campaign_template,
1070 ]
1071 );
1072
1073 if ( $title_exists ) {
1074
1075 // Skip creating a revision for this action.
1076 remove_action( 'post_updated', 'wp_save_post_revision' );
1077
1078 wp_update_post(
1079 [
1080 'ID' => $campaign_id,
1081 'post_title' => $campaign_title . ' (ID #' . $campaign_id . ')',
1082 ]
1083 );
1084
1085 // Restore the initial revisions state.
1086 add_action( 'post_updated', 'wp_save_post_revision', 10, 1 );
1087 }
1088
1089 if ( ! $campaign_id ) {
1090 wp_send_json_error(
1091 [
1092 'error_type' => 'cant_create_campaign',
1093 'message' => esc_html__( 'Error creating form.', 'charitable' ),
1094 ]
1095 );
1096 }
1097
1098 if ( charitable_current_user_can( 'edit_campaign_single', $campaign_id ) ) {
1099 wp_send_json_success(
1100 [
1101 'id' => $campaign_id,
1102 'redirect' => add_query_arg(
1103 [
1104 'view' => 'fields',
1105 'campaign_id' => $campaign_id,
1106 'newform' => '1',
1107 ],
1108 admin_url( 'admin.php?page=charitable-builder' )
1109 ),
1110 ]
1111 );
1112 }
1113
1114 if ( charitable_current_user_can( 'view_campaigns' ) ) {
1115 wp_send_json_success( [ 'redirect' => admin_url( 'admin.php?page=charitable-overview' ) ] );
1116 }
1117
1118 wp_send_json_success( [ 'redirect' => admin_url() ] );
1119 }
1120
1121 add_action( 'wp_ajax_charitable_new_campaign', 'charitable_new_campaign' );
1122
1123 /**
1124 * Update form template.
1125 *
1126 * @since 1.0.0
1127 */
1128 function charitable_update_campaign_template() {
1129
1130 // Run a security check.
1131 check_ajax_referer( 'charitable-builder', 'nonce' );
1132
1133 // Check for form name.
1134 if ( empty( $_POST['campaign_id'] ) ) {
1135 wp_send_json_error(
1136 [
1137 'error_type' => 'invalid_campaign_id',
1138 'message' => esc_html__( 'No form ID provided.', 'charitable' ),
1139 ]
1140 );
1141 }
1142
1143 $campaign_id = absint( $_POST['campaign_id'] );
1144 $campaign_template = empty( $_POST['template'] ) ? 'blank' : sanitize_text_field( wp_unslash( $_POST['template'] ) );
1145
1146 if ( ! charitable()->get( 'builder_templates' )->is_valid_template( $campaign_template ) ) {
1147 wp_send_json_error(
1148 [
1149 'error_type' => 'invalid_template',
1150 'message' => esc_html__( 'The template you selected is currently not available, but you can try again later. If you continue to have trouble, please reach out to support.', 'charitable' ),
1151 ]
1152 );
1153 }
1154
1155 $data = charitable()->get( 'form' )->get(
1156 $campaign_id,
1157 [
1158 'content_only' => true,
1159 ]
1160 );
1161
1162 if ( ! empty( $_POST['title'] ) ) {
1163 $data['settings']['campaign_title'] = sanitize_text_field( wp_unslash( $_POST['title'] ) );
1164 }
1165
1166 $updated = (bool) charitable()->get( 'form' )->update(
1167 $campaign_id,
1168 $data,
1169 [
1170 'template' => $campaign_template,
1171 ]
1172 );
1173
1174 if ( $updated ) {
1175 wp_send_json_success(
1176 [
1177 'id' => $campaign_id,
1178 'redirect' => add_query_arg(
1179 [
1180 'view' => 'fields',
1181 'campaign_id' => $campaign_id,
1182 ],
1183 admin_url( 'admin.php?page=charitable-builder' )
1184 ),
1185 ]
1186 );
1187 }
1188
1189 wp_send_json_error(
1190 [
1191 'error_type' => 'cant_update',
1192 'message' => esc_html__( 'Error updating form template.', 'charitable' ),
1193 ]
1194 );
1195 }
1196
1197 add_action( 'wp_ajax_charitable_update_campaign_template', 'charitable_update_campaign_template' );
1198
1199 /**
1200 * Campaign Builder update next field ID.
1201 *
1202 * @since 1.8.0
1203 */
1204 function charitable_builder_increase_next_field_id() {
1205
1206 // Run a security check.
1207 check_ajax_referer( 'charitable-builder', 'nonce' );
1208
1209 // Check for permissions.
1210 if ( ! charitable_current_user_can( 'edit_campaigns' ) ) {
1211 wp_send_json_error();
1212 }
1213
1214 // Check for required items.
1215 if ( empty( $_POST['campaign_id'] ) ) {
1216 wp_send_json_error();
1217 }
1218
1219 $args = [];
1220
1221 // In the case of duplicating the Layout field that contains a bunch of fields,
1222 // we need to set the next `field_id` to the desired value which is passed via POST argument.
1223 if ( ! empty( $_POST['field_id'] ) ) {
1224 $args['field_id'] = absint( $_POST['field_id'] );
1225 }
1226
1227 charitable()->get( 'form' )->next_field_id( absint( $_POST['campaign_id'] ), $args );
1228
1229 wp_send_json_success();
1230 }
1231
1232 add_action( 'wp_ajax_charitable_builder_increase_next_field_id', 'charitable_builder_increase_next_field_id' );
1233
1234 /**
1235 * Campaign Builder Dynamic Choices option toggle.
1236 *
1237 * This can be triggered with select/radio/checkbox fields.
1238 *
1239 * @since 1.8.0
1240 */
1241 function charitable_builder_dynamic_choices() {
1242
1243 // Run a security check.
1244 check_ajax_referer( 'charitable-builder', 'nonce' );
1245
1246 // Check for permissions.
1247 if ( ! charitable_current_user_can( 'edit_campaigns' ) ) {
1248 wp_send_json_error();
1249 }
1250
1251 // Check for valid/required items.
1252 if ( ! isset( $_POST['field_id'] ) || empty( $_POST['type'] ) || ! in_array( $_POST['type'], [ 'post_type', 'taxonomy' ], true ) ) {
1253 wp_send_json_error();
1254 }
1255
1256 $type = sanitize_key( $_POST['type'] );
1257 $id = absint( $_POST['field_id'] );
1258
1259 // Fetch the option row HTML to be returned to the builder.
1260 $field = new Charitable_Field_Select( false );
1261 $field_args = [
1262 'id' => $id,
1263 'dynamic_choices' => $type,
1264 ];
1265 $option_row = $field->field_option( 'dynamic_choices_source', $field_args, [], false );
1266
1267 wp_send_json_success(
1268 [
1269 'markup' => $option_row,
1270 ]
1271 );
1272 }
1273
1274 add_action( 'wp_ajax_charitable_builder_dynamic_choices', 'charitable_builder_dynamic_choices' );
1275
1276 /**
1277 * Content preview via ajax.
1278 *
1279 * @since 1.8.0
1280 */
1281 function charitable_builder_tab_content_preview() {
1282 // phpcs:disable WordPress.Security.NonceVerification.Missing
1283 $type = isset( $_POST['type'] ) ? sanitize_key( wp_unslash( $_POST['type'] ) ) : '';
1284 // phpcs:enable WordPress.Security.NonceVerification.Missing
1285 $output = charitable_builder_tab_content_preview_by_type( $type );
1286
1287 wp_send_json_success(
1288 [
1289 'output' => $output,
1290 ]
1291 );
1292 }
1293
1294 add_action( 'wp_ajax_charitable_tab_content_preview', 'charitable_builder_tab_content_preview' );
1295
1296 /**
1297 * Content preview via ajax.
1298 *
1299 * @since 1.8.0
1300 */
1301 function charitable_builder_field_content_preview() {
1302 // phpcs:disable WordPress.Security.NonceVerification.Missing
1303 if ( ! empty( $_POST['field_type'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below
1304
1305 $type = 'Charitable_Field_' . str_replace( ' ', '_', ( ucwords( str_replace( '-', ' ', sanitize_key( wp_unslash( $_POST['field_type'] ) ) ) ) ) );
1306
1307 if ( class_exists( $type ) ) :
1308
1309 $class = new $type();
1310 $field_id = isset( $_POST['field_id'] ) ? intval( wp_unslash( $_POST['field_id'] ) ) : 0; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1311 $campaign_data = isset( $_POST['campaign_id'] ) ? get_post_meta( intval( wp_unslash( $_POST['campaign_id'] ) ), 'campaign_settings_v2', true ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1312 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Array data, sanitized in field_preview method
1313 $field_data = $_POST;
1314
1315 ob_start();
1316
1317 echo $class->field_preview( $field_data, $campaign_data, $field_id, false, true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1318
1319 $output = ob_get_clean();
1320
1321 wp_send_json_success(
1322 [
1323 'output' => $output,
1324 ]
1325 );
1326
1327 endif;
1328 // phpcs:enable WordPress.Security.NonceVerification.Missing
1329 }
1330
1331 wp_send_json_error();
1332 }
1333
1334 add_action( 'wp_ajax_charitable_builder_field_content_preview', 'charitable_builder_field_content_preview' );
1335
1336 /**
1337 * Perform test connection to verify that the current web host can successfully
1338 * make outbound SSL connections.
1339 *
1340 * @since 1.8.0
1341 */
1342 function charitable_verify_ssl_ajax() {
1343
1344 // Run a security check.
1345 check_ajax_referer( 'charitable-admin', 'nonce' );
1346
1347 // Check for permissions.
1348 if ( ! charitable_current_user_can() ) {
1349 wp_send_json_error(
1350 [
1351 'msg' => esc_html__( 'You do not have permission to perform this operation.', 'charitable' ),
1352 ]
1353 );
1354 }
1355
1356 $response = wp_remote_post( 'https://wpcharitable.com/connection-test.php' );
1357
1358 if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
1359 wp_send_json_success(
1360 [
1361 'msg' => esc_html__( 'Success! Your server can make SSL connections.', 'charitable' ),
1362 ]
1363 );
1364 }
1365
1366 wp_send_json_error(
1367 [
1368 'msg' => esc_html__( 'There was an error and the connection failed. Please contact your web host with the technical details below.', 'charitable' ),
1369 'debug' => '<pre>' . print_r( map_deep( $response, 'wp_strip_all_tags' ), true ) . '</pre>', // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1370 ]
1371 );
1372 }
1373 // add_action( 'wp_ajax_charitable_verify_ssl', 'charitable_verify_ssl_ajax' ); // Disabled - conflicts with handler in charitable-core-admin-functions.php
1374
1375
1376 /**
1377 * Update form template.
1378 *
1379 * @since 1.8.0
1380 */
1381 function charitable_builder_ajax_update_campaign_creator() {
1382
1383 // Run a security check.
1384 check_ajax_referer( 'charitable-builder', 'nonce' );
1385
1386 $campaign_id = ! empty( $_POST['campaign_id'] ) ? absint( $_POST['campaign_id'] ) : false;
1387 $new_creator_id = ! empty( $_POST['creator_id'] ) ? absint( $_POST['creator_id'] ) : false;
1388
1389 if ( false === $new_creator_id ) {
1390 wp_send_json_error(
1391 [
1392 'error_type' => 'invalid_campaign_id',
1393 'message' => esc_html__( 'No creator ID provided.', 'charitable' ),
1394 ]
1395 );
1396 }
1397
1398 // check to see if the user exists.
1399 $user = get_userdata( $new_creator_id );
1400 if ( $user === false ) {
1401 wp_send_json_error(
1402 [
1403 'error_type' => 'invalid_campaign_id',
1404 'message' => esc_html__( 'Invalid creator ID provided.', 'charitable' ),
1405 ]
1406 );
1407 }
1408
1409 // todo: check creds?
1410
1411 if ( $user ) {
1412
1413 wp_send_json_success(
1414 [
1415 'id' => $campaign_id,
1416 'new_creator_id' => $new_creator_id,
1417 'avatar_url' => esc_url( get_avatar_url( $new_creator_id ) ),
1418 'creator_name' => $user->display_name . ' (USER ID ' . $new_creator_id . ')',
1419 'public_profile_link' => get_author_posts_url( $new_creator_id ),
1420 'edit_profile_link' => admin_url( 'user-edit.php?user_id=' . $new_creator_id ),
1421 ]
1422 );
1423 }
1424
1425 wp_send_json_error(
1426 [
1427 'error_type' => 'cant_update',
1428 'message' => esc_html__( 'Error updating campaign creator.', 'charitable' ),
1429 ]
1430 );
1431 }
1432 add_action( 'wp_ajax_charitable_update_campaign_creator', 'charitable_builder_ajax_update_campaign_creator' );
1433
1434 /**
1435 * Update form template.
1436 *
1437 * @since 1.8.0
1438 */
1439 function charitable_builder_ajax_update_campaign_status_link() {
1440
1441 // Run a security check.
1442 check_ajax_referer( 'charitable-builder', 'nonce' );
1443
1444 // Check for campaign ID.
1445 if ( empty( $_POST['campaign_id'] ) || 0 === intval( $_POST['campaign_id'] ) ) {
1446 wp_send_json_error(
1447 [
1448 'error_type' => 'invalid_campaign_id',
1449 'message' => esc_html__( 'No campaign ID provided.', 'charitable' ),
1450 ]
1451 );
1452 }
1453
1454 $campaign_id = absint( $_POST['campaign_id'] );
1455
1456 // get status.
1457 $campaign = $campaign_id ? charitable_get_campaign( $campaign_id ) : false;
1458 $status = $campaign ? $campaign->get_status() : false;
1459 if ( 'finished' === $status && $campaign->has_goal() ) {
1460 $status = $campaign->has_achieved_goal() ? 'successful' : 'unsuccessful';
1461 }
1462
1463 // campaign status.
1464 $html = '<span class="charitable-view-campaign-external-link"><a href="' . get_permalink( $campaign_id ) . '" target="_blank" title="' . esc_html__( 'View Campaign Page', 'charitable' ) . '"><!--<i class="fa fa-eye"></i>--></a></span>';
1465
1466 wp_send_json_success(
1467 [
1468 'id' => $campaign_id,
1469 'html' => $html,
1470 ]
1471 );
1472 }
1473 add_action( 'wp_ajax_charitable_update_campaign_status_link', 'charitable_builder_ajax_update_campaign_status_link' );
1474
1475 /**
1476 * Receive and process feedback form content.
1477 *
1478 * @since 1.8.0
1479 */
1480 function charitable_campaign_builder_send_feedback_ajax() {
1481 // phpcs:disable WordPress.Security.NonceVerification.Missing
1482 /* Data to send in our API request */
1483 $feedback_params = array(
1484 'feedback_action' => 'template_feedback',
1485 'type' => ! empty( $_POST['data']['type'] ) ? sanitize_text_field( wp_unslash( $_POST['data']['type'] ) ) : false, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1486 'name' => ! empty( $_POST['data']['name'] ) ? sanitize_text_field( wp_unslash( $_POST['data']['name'] ) ) : false, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1487 'email' => ! empty( $_POST['data']['email'] ) ? sanitize_email( wp_unslash( $_POST['data']['email'] ) ) : false, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1488 'feedback' => ! empty( $_POST['data']['feedback'] ) ? sanitize_text_field( wp_unslash( $_POST['data']['feedback'] ) ) : false, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1489 );
1490 // phpcs:enable WordPress.Security.NonceVerification.Missing
1491
1492 /* Call the custom API */
1493 $response = wp_remote_post(
1494 'https://wpcharitable.com',
1495 array(
1496 'timeout' => 15,
1497 'sslverify' => false,
1498 'body' => $feedback_params,
1499 )
1500 );
1501
1502 if ( is_wp_error( $response ) ) {
1503 wp_send_json_error(
1504 );
1505 } else {
1506 wp_send_json_success(
1507 [
1508 'sent' => true,
1509 ]
1510 );
1511 }
1512 }
1513 add_action( 'wp_ajax_charitable_campaign_builder_send_feedback_ajax', 'charitable_campaign_builder_send_feedback_ajax' );
1514
1515