PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.12.3
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.12.3
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.12.3, at includes/admin/campaign-builder/ajax-actions.php

1,435 lines 54.5 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 $row_id = 0;
753 $column_id = 0;
754 $section_id = 0;
755
756 foreach ( $template_data['layout'] as $row_id => $row ) :
757
758 $campaign_settings_v2['layout']['rows'][ $row_id ]['type'] = ! empty( $row['type'] ) ? $row['type'] : 'row';
759 $campaign_settings_v2['layout']['rows'][ $row_id ]['css_class'] = ! empty( $row['css_class'] ) ? $row['css_class'] : false;
760 $campaign_settings_v2['layout']['rows'][ $row_id ]['background_image'] = ! empty( $row['background_image'] ) ? esc_url_raw( $row['background_image'] ) : '';
761 $campaign_settings_v2['layout']['rows'][ $row_id ]['locked'] = ! empty( $row['locked'] ) ? 1 : 0;
762
763 foreach ( $row['columns'] as $column_key => $column ) :
764
765 foreach ( $column as $section_key => $section ) :
766
767 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'] = array();
768
769 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['type'] = esc_attr( $section['type'] );
770
771 switch ( $section['type'] ) {
772 case 'fields':
773 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['fields'] = array(); // [0] => 1 /// (array) $section['fields'];
774
775 break;
776 case 'tabs':
777 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ]['tabs'] = array(); // [0] => 1 /// (array) $section['fields'];
778
779 if ( ! empty( $section['tabs'] ) ) {
780 foreach ( $section['tabs'] as $tab_id => $tab_info ) :
781 $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ][ $section['type'] ][ $tab_id ] = array(
782 'title' => ! empty( $campaign_post[ 'tabs__' . $tab_id . '__title' ]['value'] ) ? sanitize_text_field( $campaign_post[ 'tabs__' . $tab_id . '__title' ]['value'] ) : $tab_info['title'],
783 'type' => $tab_info['type'],
784 'slug' => sanitize_title( $tab_info['slug'] ),
785 'fields' => array(),
786 );
787 endforeach;
788 }
789
790 break;
791 default:
792 // $campaign_settings_v2['layout']['rows'][ $row_id ]['columns'][ $column_id ]['sections'][ $section_id ][ $section['type'] ] = $section[ $section['type'] ];
793 break;
794 }
795
796 ++$column_id;
797 ++$section_id;
798
799 endforeach;
800
801 // $column_id++;
802
803 endforeach;
804
805 $campaign_settings_v2['layout']['rows'][ $row_id ]['fields'] = array();
806
807 ++$row_id;
808
809 endforeach;
810
811 return $campaign_settings_v2;
812 }
813
814 /**
815 * Searches for a key in a multidimensional array.
816 *
817 * @param array $array The array to search.
818 * @param mixed $key The key to search for.
819 * @return mixed|null The value of the key if found, null otherwise.
820 */
821 function charitable_search_settings( $array, $key ) {
822 // If the key exists in the current level of the array, return its value.
823 if ( array_key_exists( $key, $array ) ) {
824 return $array[ $key ];
825 }
826
827 // Iterate over each element in the array.
828 foreach ( $array as $value ) {
829 // If the element is an array, recursively search it for the key.
830 if ( is_array( $value ) ) {
831 $result = charitable_search_settings( $value, $key );
832
833 // If the key is found in the nested array, return its value.
834 if ( $result !== null ) {
835 return $result;
836 }
837 }
838 }
839
840 // The key was not found in the array.
841 return null;
842 }
843
844
845 /**
846 * Find the highest field ID field so we can keep the max number accurate.
847 *
848 * @since 1.8.0
849 *
850 * @param array $array Settings data.
851 */
852 function charitable_find_highest_field_key( $array ) {
853 $highest_key = null;
854
855 // Iterate through each element in the array.
856 foreach ( $array as $key => $value ) {
857 // Check if the current element is an array.
858 if ( is_array( $value ) ) {
859 // Recursively call the function to search through nested arrays.
860 $field_key = charitable_find_highest_field_key( $value );
861
862 // Check if the key is numeric and greater than the current highest key.
863 if ( is_numeric( $field_key ) && ( $highest_key === null || $field_key > $highest_key ) ) {
864 $highest_key = $field_key;
865 }
866 } else {
867 // Check if the key is numeric and greater than the current highest key.
868 if ( is_numeric( $key ) && ( $highest_key === null || $key > $highest_key ) ) {
869 $highest_key = $key;
870 }
871 }
872 }
873
874 return $highest_key;
875 }
876
877 /**
878 * A very particular util array for the builder.
879 *
880 * @since 1.8.0
881 *
882 * @param string $string The string from the settings data.
883 * @param string $value Value.
884 * @param string $end_key End key.
885 */
886 function charitable_convert_campaign_string_to_array( $string, $value, $end_key = 'amount' ) {
887
888 $keys = explode( '][', trim( $string, '[]' ) ); // split the string into an array of keys.
889 $array = [];
890 $current = &$array;
891 foreach ( $keys as $key ) {
892 $current[ $key ] = [];
893 if ( $key === $end_key ) {
894 $current[ $key ] = $value; // $value;
895 }
896 $current = &$current[ $key ];
897 }
898
899 return $array;
900 }
901
902 /**
903 * Convert a campaign string to an array.
904 *
905 * @param string $input The input string to convert.
906 * @param mixed $value The default value to use for each key.
907 * @param string $end_key The key to use for the final value.
908 * @return array The resulting array.
909 */
910 function charitable_convert_campaign_string_to_array_new( $input, $value, $end_key = 'amount' ) { // phpcs:ignore
911
912 $output = array();
913 $pointer = &$output;
914
915 while ( ( $index = strpos( $input, '[' ) ) !== false ) {
916 if ( $index != 0 ) {
917 $key = substr( $input, 0, $index );
918 $pointer[ $key ] = array();
919 $pointer = &$pointer[ $key ];
920 $input = substr( $input, $index );
921 continue;
922 }
923 $end_index = strpos( $input, ']' );
924 $array_key = substr( $input, $index + 1, $end_index - 1 );
925 $pointer[ $array_key ] = array();
926 $pointer = &$pointer[ $array_key ];
927 $input = substr( $input, $end_index + 1 );
928 }
929
930 return( $output );
931 }
932
933 /**
934 * Create a new form.
935 *
936 * @since 1.8.0
937 */
938 function charitable_new_campaign() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
939
940 check_ajax_referer( 'charitable-builder', 'nonce' );
941
942 if ( empty( $_POST['title'] ) ) {
943 wp_send_json_error(
944 [
945 'error_type' => 'missing_campaign_title',
946 'message' => esc_html__( 'No form name provided.', 'charitable' ),
947 ]
948 );
949 }
950
951 $campaign_title = sanitize_text_field( wp_unslash( $_POST['title'] ) );
952 $campaign_template = empty( $_POST['template'] ) ? 'blank' : sanitize_text_field( wp_unslash( $_POST['template'] ) );
953
954 if ( ! charitable()->get( 'builder_templates' )->is_valid_template( $campaign_template ) ) {
955 wp_send_json_error(
956 [
957 'error_type' => 'invalid_template',
958 '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' ),
959 ]
960 );
961 }
962
963 /*
964 * Does a campaign with this title already exist? This has to run *before* the new
965 * campaign is created, otherwise it would match the new campaign itself.
966 *
967 * Replaces get_page_by_title(), which was deprecated in WP 6.2 and was being passed
968 * the post type 'charitable' rather than 'campaign' — so it never matched anything and
969 * the disambiguating suffix below never ran.
970 */
971 $charitable_existing_titles = new WP_Query(
972 [
973 'post_type' => Charitable::CAMPAIGN_POST_TYPE,
974 'post_status' => 'any',
975 'title' => $campaign_title,
976 'posts_per_page' => 1,
977 'fields' => 'ids',
978 'no_found_rows' => true,
979 'update_post_meta_cache' => false,
980 'update_post_term_cache' => false,
981 ]
982 );
983
984 $title_exists = ! empty( $charitable_existing_titles->posts );
985 $campaign_id = charitable()->get( 'form' )->add(
986 $campaign_title,
987 [],
988 [
989 'template' => $campaign_template,
990 ]
991 );
992
993 if ( $title_exists ) {
994
995 // Skip creating a revision for this action.
996 remove_action( 'post_updated', 'wp_save_post_revision' );
997
998 wp_update_post(
999 [
1000 'ID' => $campaign_id,
1001 'post_title' => $campaign_title . ' (ID #' . $campaign_id . ')',
1002 ]
1003 );
1004
1005 // Restore the initial revisions state.
1006 add_action( 'post_updated', 'wp_save_post_revision', 10, 1 );
1007 }
1008
1009 if ( ! $campaign_id ) {
1010 wp_send_json_error(
1011 [
1012 'error_type' => 'cant_create_campaign',
1013 'message' => esc_html__( 'Error creating form.', 'charitable' ),
1014 ]
1015 );
1016 }
1017
1018 if ( charitable_current_user_can( 'edit_campaign_single', $campaign_id ) ) {
1019 wp_send_json_success(
1020 [
1021 'id' => $campaign_id,
1022 'redirect' => add_query_arg(
1023 [
1024 'view' => 'fields',
1025 'campaign_id' => $campaign_id,
1026 'newform' => '1',
1027 ],
1028 admin_url( 'admin.php?page=charitable-builder' )
1029 ),
1030 ]
1031 );
1032 }
1033
1034 if ( charitable_current_user_can( 'view_campaigns' ) ) {
1035 wp_send_json_success( [ 'redirect' => admin_url( 'admin.php?page=charitable-overview' ) ] );
1036 }
1037
1038 wp_send_json_success( [ 'redirect' => admin_url() ] );
1039 }
1040
1041 add_action( 'wp_ajax_charitable_new_campaign', 'charitable_new_campaign' );
1042
1043 /**
1044 * Update form template.
1045 *
1046 * @since 1.0.0
1047 */
1048 function charitable_update_campaign_template() {
1049
1050 // Run a security check.
1051 check_ajax_referer( 'charitable-builder', 'nonce' );
1052
1053 // Check for form name.
1054 if ( empty( $_POST['campaign_id'] ) ) {
1055 wp_send_json_error(
1056 [
1057 'error_type' => 'invalid_campaign_id',
1058 'message' => esc_html__( 'No form ID provided.', 'charitable' ),
1059 ]
1060 );
1061 }
1062
1063 $campaign_id = absint( $_POST['campaign_id'] );
1064 $campaign_template = empty( $_POST['template'] ) ? 'blank' : sanitize_text_field( wp_unslash( $_POST['template'] ) );
1065
1066 if ( ! charitable()->get( 'builder_templates' )->is_valid_template( $campaign_template ) ) {
1067 wp_send_json_error(
1068 [
1069 'error_type' => 'invalid_template',
1070 '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' ),
1071 ]
1072 );
1073 }
1074
1075 $data = charitable()->get( 'form' )->get(
1076 $campaign_id,
1077 [
1078 'content_only' => true,
1079 ]
1080 );
1081
1082 if ( ! empty( $_POST['title'] ) ) {
1083 $data['settings']['campaign_title'] = sanitize_text_field( wp_unslash( $_POST['title'] ) );
1084 }
1085
1086 $updated = (bool) charitable()->get( 'form' )->update(
1087 $campaign_id,
1088 $data,
1089 [
1090 'template' => $campaign_template,
1091 ]
1092 );
1093
1094 if ( $updated ) {
1095 wp_send_json_success(
1096 [
1097 'id' => $campaign_id,
1098 'redirect' => add_query_arg(
1099 [
1100 'view' => 'fields',
1101 'campaign_id' => $campaign_id,
1102 ],
1103 admin_url( 'admin.php?page=charitable-builder' )
1104 ),
1105 ]
1106 );
1107 }
1108
1109 wp_send_json_error(
1110 [
1111 'error_type' => 'cant_update',
1112 'message' => esc_html__( 'Error updating form template.', 'charitable' ),
1113 ]
1114 );
1115 }
1116
1117 add_action( 'wp_ajax_charitable_update_campaign_template', 'charitable_update_campaign_template' );
1118
1119 /**
1120 * Campaign Builder update next field ID.
1121 *
1122 * @since 1.8.0
1123 */
1124 function charitable_builder_increase_next_field_id() {
1125
1126 // Run a security check.
1127 check_ajax_referer( 'charitable-builder', 'nonce' );
1128
1129 // Check for permissions.
1130 if ( ! charitable_current_user_can( 'edit_campaigns' ) ) {
1131 wp_send_json_error();
1132 }
1133
1134 // Check for required items.
1135 if ( empty( $_POST['campaign_id'] ) ) {
1136 wp_send_json_error();
1137 }
1138
1139 $args = [];
1140
1141 // In the case of duplicating the Layout field that contains a bunch of fields,
1142 // we need to set the next `field_id` to the desired value which is passed via POST argument.
1143 if ( ! empty( $_POST['field_id'] ) ) {
1144 $args['field_id'] = absint( $_POST['field_id'] );
1145 }
1146
1147 charitable()->get( 'form' )->next_field_id( absint( $_POST['campaign_id'] ), $args );
1148
1149 wp_send_json_success();
1150 }
1151
1152 add_action( 'wp_ajax_charitable_builder_increase_next_field_id', 'charitable_builder_increase_next_field_id' );
1153
1154 /**
1155 * Campaign Builder Dynamic Choices option toggle.
1156 *
1157 * This can be triggered with select/radio/checkbox fields.
1158 *
1159 * @since 1.8.0
1160 */
1161 function charitable_builder_dynamic_choices() {
1162
1163 // Run a security check.
1164 check_ajax_referer( 'charitable-builder', 'nonce' );
1165
1166 // Check for permissions.
1167 if ( ! charitable_current_user_can( 'edit_campaigns' ) ) {
1168 wp_send_json_error();
1169 }
1170
1171 // Check for valid/required items.
1172 if ( ! isset( $_POST['field_id'] ) || empty( $_POST['type'] ) || ! in_array( $_POST['type'], [ 'post_type', 'taxonomy' ], true ) ) {
1173 wp_send_json_error();
1174 }
1175
1176 $type = sanitize_key( $_POST['type'] );
1177 $id = absint( $_POST['field_id'] );
1178
1179 // Fetch the option row HTML to be returned to the builder.
1180 $field = new Charitable_Field_Select( false );
1181 $field_args = [
1182 'id' => $id,
1183 'dynamic_choices' => $type,
1184 ];
1185 $option_row = $field->field_option( 'dynamic_choices_source', $field_args, [], false );
1186
1187 wp_send_json_success(
1188 [
1189 'markup' => $option_row,
1190 ]
1191 );
1192 }
1193
1194 add_action( 'wp_ajax_charitable_builder_dynamic_choices', 'charitable_builder_dynamic_choices' );
1195
1196 /**
1197 * Content preview via ajax.
1198 *
1199 * @since 1.8.0
1200 */
1201 function charitable_builder_tab_content_preview() {
1202 // phpcs:disable WordPress.Security.NonceVerification.Missing
1203 $type = isset( $_POST['type'] ) ? sanitize_key( wp_unslash( $_POST['type'] ) ) : '';
1204 // phpcs:enable WordPress.Security.NonceVerification.Missing
1205 $output = charitable_builder_tab_content_preview_by_type( $type );
1206
1207 wp_send_json_success(
1208 [
1209 'output' => $output,
1210 ]
1211 );
1212 }
1213
1214 add_action( 'wp_ajax_charitable_tab_content_preview', 'charitable_builder_tab_content_preview' );
1215
1216 /**
1217 * Content preview via ajax.
1218 *
1219 * @since 1.8.0
1220 */
1221 function charitable_builder_field_content_preview() {
1222 // phpcs:disable WordPress.Security.NonceVerification.Missing
1223 if ( ! empty( $_POST['field_type'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below
1224
1225 $type = 'Charitable_Field_' . str_replace( ' ', '_', ( ucwords( str_replace( '-', ' ', sanitize_key( wp_unslash( $_POST['field_type'] ) ) ) ) ) );
1226
1227 if ( class_exists( $type ) ) :
1228
1229 $class = new $type();
1230 $field_id = isset( $_POST['field_id'] ) ? intval( wp_unslash( $_POST['field_id'] ) ) : 0; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1231 $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
1232 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Array data, sanitized in field_preview method
1233 $field_data = $_POST;
1234
1235 ob_start();
1236
1237 echo $class->field_preview( $field_data, $campaign_data, $field_id, false, true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1238
1239 $output = ob_get_clean();
1240
1241 wp_send_json_success(
1242 [
1243 'output' => $output,
1244 ]
1245 );
1246
1247 endif;
1248 // phpcs:enable WordPress.Security.NonceVerification.Missing
1249 }
1250
1251 wp_send_json_error();
1252 }
1253
1254 add_action( 'wp_ajax_charitable_builder_field_content_preview', 'charitable_builder_field_content_preview' );
1255
1256 /**
1257 * Perform test connection to verify that the current web host can successfully
1258 * make outbound SSL connections.
1259 *
1260 * @since 1.8.0
1261 */
1262 function charitable_verify_ssl_ajax() {
1263
1264 // Run a security check.
1265 check_ajax_referer( 'charitable-admin', 'nonce' );
1266
1267 // Check for permissions.
1268 if ( ! charitable_current_user_can() ) {
1269 wp_send_json_error(
1270 [
1271 'msg' => esc_html__( 'You do not have permission to perform this operation.', 'charitable' ),
1272 ]
1273 );
1274 }
1275
1276 $response = wp_remote_post( 'https://wpcharitable.com/connection-test.php' );
1277
1278 if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
1279 wp_send_json_success(
1280 [
1281 'msg' => esc_html__( 'Success! Your server can make SSL connections.', 'charitable' ),
1282 ]
1283 );
1284 }
1285
1286 wp_send_json_error(
1287 [
1288 'msg' => esc_html__( 'There was an error and the connection failed. Please contact your web host with the technical details below.', 'charitable' ),
1289 'debug' => '<pre>' . print_r( map_deep( $response, 'wp_strip_all_tags' ), true ) . '</pre>', // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1290 ]
1291 );
1292 }
1293 // add_action( 'wp_ajax_charitable_verify_ssl', 'charitable_verify_ssl_ajax' ); // Disabled - conflicts with handler in charitable-core-admin-functions.php
1294
1295
1296 /**
1297 * Update form template.
1298 *
1299 * @since 1.8.0
1300 */
1301 function charitable_builder_ajax_update_campaign_creator() {
1302
1303 // Run a security check.
1304 check_ajax_referer( 'charitable-builder', 'nonce' );
1305
1306 $campaign_id = ! empty( $_POST['campaign_id'] ) ? absint( $_POST['campaign_id'] ) : false;
1307 $new_creator_id = ! empty( $_POST['creator_id'] ) ? absint( $_POST['creator_id'] ) : false;
1308
1309 if ( false === $new_creator_id ) {
1310 wp_send_json_error(
1311 [
1312 'error_type' => 'invalid_campaign_id',
1313 'message' => esc_html__( 'No creator ID provided.', 'charitable' ),
1314 ]
1315 );
1316 }
1317
1318 // check to see if the user exists.
1319 $user = get_userdata( $new_creator_id );
1320 if ( $user === false ) {
1321 wp_send_json_error(
1322 [
1323 'error_type' => 'invalid_campaign_id',
1324 'message' => esc_html__( 'Invalid creator ID provided.', 'charitable' ),
1325 ]
1326 );
1327 }
1328
1329 // todo: check creds?
1330
1331 if ( $user ) {
1332
1333 wp_send_json_success(
1334 [
1335 'id' => $campaign_id,
1336 'new_creator_id' => $new_creator_id,
1337 'avatar_url' => esc_url( get_avatar_url( $new_creator_id ) ),
1338 'creator_name' => $user->display_name . ' (USER ID ' . $new_creator_id . ')',
1339 'public_profile_link' => get_author_posts_url( $new_creator_id ),
1340 'edit_profile_link' => admin_url( 'user-edit.php?user_id=' . $new_creator_id ),
1341 ]
1342 );
1343 }
1344
1345 wp_send_json_error(
1346 [
1347 'error_type' => 'cant_update',
1348 'message' => esc_html__( 'Error updating campaign creator.', 'charitable' ),
1349 ]
1350 );
1351 }
1352 add_action( 'wp_ajax_charitable_update_campaign_creator', 'charitable_builder_ajax_update_campaign_creator' );
1353
1354 /**
1355 * Update form template.
1356 *
1357 * @since 1.8.0
1358 */
1359 function charitable_builder_ajax_update_campaign_status_link() {
1360
1361 // Run a security check.
1362 check_ajax_referer( 'charitable-builder', 'nonce' );
1363
1364 // Check for campaign ID.
1365 if ( empty( $_POST['campaign_id'] ) || 0 === intval( $_POST['campaign_id'] ) ) {
1366 wp_send_json_error(
1367 [
1368 'error_type' => 'invalid_campaign_id',
1369 'message' => esc_html__( 'No campaign ID provided.', 'charitable' ),
1370 ]
1371 );
1372 }
1373
1374 $campaign_id = absint( $_POST['campaign_id'] );
1375
1376 // get status.
1377 $campaign = $campaign_id ? charitable_get_campaign( $campaign_id ) : false;
1378 $status = $campaign ? $campaign->get_status() : false;
1379 if ( 'finished' === $status && $campaign->has_goal() ) {
1380 $status = $campaign->has_achieved_goal() ? 'successful' : 'unsuccessful';
1381 }
1382
1383 // campaign status.
1384 $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>';
1385
1386 wp_send_json_success(
1387 [
1388 'id' => $campaign_id,
1389 'html' => $html,
1390 ]
1391 );
1392 }
1393 add_action( 'wp_ajax_charitable_update_campaign_status_link', 'charitable_builder_ajax_update_campaign_status_link' );
1394
1395 /**
1396 * Receive and process feedback form content.
1397 *
1398 * @since 1.8.0
1399 */
1400 function charitable_campaign_builder_send_feedback_ajax() {
1401 // phpcs:disable WordPress.Security.NonceVerification.Missing
1402 /* Data to send in our API request */
1403 $feedback_params = array(
1404 'feedback_action' => 'template_feedback',
1405 'type' => ! empty( $_POST['data']['type'] ) ? sanitize_text_field( wp_unslash( $_POST['data']['type'] ) ) : false, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1406 'name' => ! empty( $_POST['data']['name'] ) ? sanitize_text_field( wp_unslash( $_POST['data']['name'] ) ) : false, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1407 'email' => ! empty( $_POST['data']['email'] ) ? sanitize_email( wp_unslash( $_POST['data']['email'] ) ) : false, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1408 'feedback' => ! empty( $_POST['data']['feedback'] ) ? sanitize_text_field( wp_unslash( $_POST['data']['feedback'] ) ) : false, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1409 );
1410 // phpcs:enable WordPress.Security.NonceVerification.Missing
1411
1412 /* Call the custom API */
1413 $response = wp_remote_post(
1414 'https://wpcharitable.com',
1415 array(
1416 'timeout' => 15,
1417 'sslverify' => false,
1418 'body' => $feedback_params,
1419 )
1420 );
1421
1422 if ( is_wp_error( $response ) ) {
1423 wp_send_json_error(
1424 );
1425 } else {
1426 wp_send_json_success(
1427 [
1428 'sent' => true,
1429 ]
1430 );
1431 }
1432 }
1433 add_action( 'wp_ajax_charitable_campaign_builder_send_feedback_ajax', 'charitable_campaign_builder_send_feedback_ajax' );
1434
1435