PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 3.0.2
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v3.0.2
3.4.1 3.4.0 3.3.9 3.3.8 3.3.7 3.3.6 3.3.5 3.3.4 3.3.3 3.3.2 3.3.1 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 2.3.0 2.3.1 2.3.2 2.3.3 All 194 releases
convertkit / includes / class-convertkit-resource-forms.php

class-convertkit-resource-forms.php in Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages 3.0.2, at includes/class-convertkit-resource-forms.php

632 lines 18.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ConvertKit Forms Resource class.
4 *
5 * @package ConvertKit
6 * @author ConvertKit
7 */
8
9 /**
10 * Reads ConvertKit Forms from the options table, and refreshes
11 * ConvertKit Forms data stored locally from the API.
12 *
13 * @since 1.9.6
14 */
15 class ConvertKit_Resource_Forms extends ConvertKit_Resource_V4 {
16
17 /**
18 * Holds the Settings Key that stores site wide ConvertKit settings
19 *
20 * @var string
21 */
22 public $settings_name = 'convertkit_forms';
23
24 /**
25 * The type of resource
26 *
27 * @var string
28 */
29 public $type = 'forms';
30
31 /**
32 * Constructor.
33 *
34 * @since 1.9.8.4
35 *
36 * @param bool|string $context Context.
37 */
38 public function __construct( $context = false ) {
39
40 // Initialize the API if the Access Token has been defined in the Plugin Settings.
41 $settings = new ConvertKit_Settings();
42 if ( $settings->has_access_and_refresh_token() ) {
43 $this->api = new ConvertKit_API_V4(
44 CONVERTKIT_OAUTH_CLIENT_ID,
45 CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI,
46 $settings->get_access_token(),
47 $settings->get_refresh_token(),
48 $settings->debug_enabled(),
49 $context
50 );
51 }
52
53 // Call parent initialization function.
54 parent::init();
55
56 }
57
58 /**
59 * Returns all inline forms based on the sort order.
60 *
61 * @since 2.7.3
62 *
63 * @return bool|array
64 */
65 public function get_inline() {
66
67 // If the ConvertKit WordPress Libraries are < 1.3.6 (e.g. loaded by an outdated
68 // addon), or a WordPress site updates this Plugin before other ConvertKit Plugins,
69 // get_by() won't be available and will cause an E_ERROR, crashing the site.
70 // @see https://wordpress.org/support/topic/error-1795/.
71 if ( ! method_exists( $this, 'get_by' ) ) { // @phpstan-ignore-line Older WordPress Libraries won't have this function.
72 return false;
73 }
74
75 return $this->get_by( 'format', array( 'inline' ) );
76
77 }
78
79 /**
80 * Returns whether any inline forms exist in the options table.
81 *
82 * @since 2.7.3
83 *
84 * @return bool
85 */
86 public function inline_exist() {
87
88 return (bool) $this->get_inline();
89
90 }
91
92 /**
93 * Returns all non-inline forms based on the sort order.
94 *
95 * @since 2.2.4
96 *
97 * @return bool|array
98 */
99 public function get_non_inline() {
100
101 // If the ConvertKit WordPress Libraries are < 1.3.6 (e.g. loaded by an outdated
102 // addon), or a WordPress site updates this Plugin before other ConvertKit Plugins,
103 // get_by() won't be available and will cause an E_ERROR, crashing the site.
104 // @see https://wordpress.org/support/topic/error-1795/.
105 if ( ! method_exists( $this, 'get_by' ) ) { // @phpstan-ignore-line Older WordPress Libraries won't have this function.
106 return false;
107 }
108
109 return $this->get_by( 'format', array( 'modal', 'slide in', 'sticky bar' ) );
110
111 }
112
113 /**
114 * Returns whether any non-inline forms exist in the options table.
115 *
116 * @since 2.2.4
117 *
118 * @return bool
119 */
120 public function non_inline_exist() {
121
122 if ( ! $this->get_non_inline() ) {
123 return false;
124 }
125
126 return true;
127
128 }
129
130 /**
131 * Determines if the given Form ID is a legacy Form or Landing Page.
132 *
133 * @since 2.5.0
134 *
135 * @param int $id Form or Landing Page ID.
136 */
137 public function is_legacy( $id ) {
138
139 // Get Form.
140 $form = $this->get_by_id( (int) $id );
141
142 // Return false if no Form exists.
143 if ( ! $form ) {
144 return false;
145 }
146
147 // If the `format` key exists, this is not a legacy Form.
148 if ( array_key_exists( 'format', $form ) ) {
149 return false;
150 }
151
152 return true;
153
154 }
155
156 /**
157 * Returns a <select> field populated with all forms, based on the given parameters.
158 *
159 * @since 2.3.9
160 *
161 * @param string $name Name.
162 * @param string $id ID.
163 * @param bool|array $css_classes <select> CSS class(es).
164 * @param string $selected_option <option> value to mark as selected.
165 * @param bool|array $prepend_options <option> elements to prepend before resources.
166 * @param bool|array $attributes <select> attributes.
167 * @param bool|string|array $description Description.
168 * @return string HTML Select Field
169 */
170 public function get_select_field_all( $name, $id, $css_classes, $selected_option, $prepend_options = false, $attributes = false, $description = false ) {
171
172 return $this->get_select_field(
173 $this->get(),
174 $name,
175 $id,
176 $css_classes,
177 $selected_option,
178 $prepend_options,
179 $attributes,
180 $description
181 );
182
183 }
184
185 /**
186 * Outputs a <select> field populated with all forms, based on the given parameters.
187 *
188 * @since 2.8.5
189 *
190 * @param string $name Name.
191 * @param string $id ID.
192 * @param bool|array $css_classes <select> CSS class(es).
193 * @param string $selected_option <option> value to mark as selected.
194 * @param bool|array $prepend_options <option> elements to prepend before resources.
195 * @param bool|array $attributes <select> attributes.
196 * @param bool|string|array $description Description.
197 */
198 public function output_select_field_all( $name, $id, $css_classes, $selected_option, $prepend_options = false, $attributes = false, $description = false ) {
199
200 $this->output_select_field(
201 $this->get(),
202 $name,
203 $id,
204 $css_classes,
205 $selected_option,
206 $prepend_options,
207 $attributes,
208 $description
209 );
210
211 }
212
213 /**
214 * Returns a <select> field populated with all non-inline forms, based on the given parameters.
215 *
216 * @since 2.3.9
217 *
218 * @param string $name Name.
219 * @param string $id ID.
220 * @param bool|array $css_classes <select> CSS class(es).
221 * @param array $selected_options <option> values to mark as selected.
222 * @param bool|array $prepend_options <option> elements to prepend before resources.
223 * @param bool|array $attributes <select> attributes.
224 * @param bool|string|array $description Description.
225 * @return string HTML Select Field
226 */
227 public function get_select_field_non_inline( $name, $id, $css_classes, $selected_options, $prepend_options = false, $attributes = false, $description = false ) {
228
229 return $this->get_multi_select_field(
230 $this->get_non_inline(),
231 $name,
232 $id,
233 $css_classes,
234 $selected_options,
235 $prepend_options,
236 $attributes,
237 $description
238 );
239
240 }
241
242 /**
243 * Outputs a <select> field populated with all non-inline forms, based on the given parameters.
244 *
245 * @since 2.3.9
246 *
247 * @param string $name Name.
248 * @param string $id ID.
249 * @param bool|array $css_classes <select> CSS class(es).
250 * @param array $selected_options <option> values to mark as selected.
251 * @param bool|array $prepend_options <option> elements to prepend before resources.
252 * @param bool|array $attributes <select> attributes.
253 * @param bool|string|array $description Description.
254 */
255 public function output_select_field_non_inline( $name, $id, $css_classes, $selected_options, $prepend_options = false, $attributes = false, $description = false ) {
256
257 echo wp_kses(
258 $this->get_select_field_non_inline(
259 $name,
260 $id,
261 $css_classes,
262 $selected_options,
263 $prepend_options,
264 $attributes,
265 $description
266 ),
267 convertkit_kses_allowed_html()
268 );
269
270 }
271
272 /**
273 * Returns a <select> field populated with the resources, based on the given parameters.
274 *
275 * @since 2.3.9
276 *
277 * @param array $forms Forms.
278 * @param string $name Name.
279 * @param string $id ID.
280 * @param bool|array $css_classes <select> CSS class(es).
281 * @param string $selected_option <option> value to mark as selected.
282 * @param bool|array $prepend_options <option> elements to prepend before resources.
283 * @param bool|array $attributes <select> attributes.
284 * @param bool|string|array $description Description.
285 * @return string HTML Select Field
286 */
287 private function get_select_field( $forms, $name, $id, $css_classes, $selected_option, $prepend_options = false, $attributes = false, $description = false ) {
288
289 $html = sprintf(
290 '<select name="%s" id="%s" class="%s"',
291 esc_attr( $name ),
292 esc_attr( $id ),
293 esc_attr( ( is_array( $css_classes ) ? implode( ' ', $css_classes ) : '' ) )
294 );
295
296 // Append any attributes.
297 if ( $attributes ) {
298 foreach ( $attributes as $key => $value ) {
299 $html .= sprintf(
300 ' %s="%s"',
301 esc_attr( $key ),
302 esc_attr( $value )
303 );
304 }
305 }
306
307 // Close select tag.
308 $html .= '>';
309
310 // If any prepended options exist, add them now.
311 if ( $prepend_options ) {
312 foreach ( $prepend_options as $value => $label ) {
313 $html .= sprintf(
314 '<option value="%s" data-preserve-on-refresh="1"%s>%s</option>',
315 esc_attr( $value ),
316 selected( $selected_option, $value, false ),
317 esc_attr( $label )
318 );
319 }
320 }
321
322 // Iterate through resources, if they exist, building <option> elements.
323 if ( $forms ) {
324 foreach ( $forms as $form ) {
325 // Legacy forms don't include a `format` key, so define them as inline.
326 $html .= sprintf(
327 '<option value="%s"%s>%s [%s]</option>',
328 esc_attr( $form['id'] ),
329 selected( $selected_option, $form['id'], false ),
330 esc_attr( $form['name'] ),
331 ( ! empty( $form['format'] ) ? esc_attr( $form['format'] ) : 'inline' )
332 );
333 }
334 }
335
336 // Close select.
337 $html .= '</select>';
338
339 // If no description is provided, return the select field now.
340 if ( ! $description ) {
341 return $html;
342 }
343
344 // Append description before returning field.
345 if ( ! is_array( $description ) ) {
346 return $html . '<p class="description">' . $description . '</p>';
347 }
348
349 // Return description lines in a paragraph, using breaklines for each description entry in the array.
350 return $html . '<p class="description">' . implode( '<br />', $description ) . '</p>';
351
352 }
353
354 /**
355 * Outputs a <select> field populated with the resources, based on the given parameters.
356 *
357 * @since 2.8.5
358 *
359 * @param array $forms Forms.
360 * @param string $name Name.
361 * @param string $id ID.
362 * @param bool|array $css_classes <select> CSS class(es).
363 * @param string $selected_option <option> value to mark as selected.
364 * @param bool|array $prepend_options <option> elements to prepend before resources.
365 * @param bool|array $attributes <select> attributes.
366 * @param bool|string|array $description Description.
367 */
368 private function output_select_field( $forms, $name, $id, $css_classes, $selected_option, $prepend_options = false, $attributes = false, $description = false ) {
369
370 echo wp_kses(
371 $this->get_select_field(
372 $forms,
373 $name,
374 $id,
375 $css_classes,
376 $selected_option,
377 $prepend_options,
378 $attributes,
379 $description
380 ),
381 convertkit_kses_allowed_html()
382 );
383
384 }
385
386 /**
387 * Returns a <select> field populated with the resources, based on the given parameters,
388 * that supports multiple selection.
389 *
390 * @since 2.6.9
391 *
392 * @param array $forms Forms.
393 * @param string $name Name.
394 * @param string $id ID.
395 * @param bool|array $css_classes <select> CSS class(es).
396 * @param array $selected_options <option> values to mark as selected.
397 * @param bool|array $prepend_options <option> elements to prepend before resources.
398 * @param bool|array $attributes <select> attributes.
399 * @param bool|string|array $description Description.
400 * @return string HTML Select Field
401 */
402 private function get_multi_select_field( $forms, $name, $id, $css_classes, $selected_options = array(), $prepend_options = false, $attributes = false, $description = false ) {
403
404 $html = sprintf(
405 '<select name="%s[]" id="%s" class="%s" multiple',
406 esc_attr( $name ),
407 esc_attr( $id ),
408 esc_attr( ( is_array( $css_classes ) ? implode( ' ', $css_classes ) : '' ) )
409 );
410
411 // Append any attributes.
412 if ( $attributes ) {
413 foreach ( $attributes as $key => $value ) {
414 $html .= sprintf(
415 ' %s="%s"',
416 esc_attr( $key ),
417 esc_attr( $value )
418 );
419 }
420 }
421
422 // Close select tag.
423 $html .= '>';
424
425 // If any prepended options exist, add them now.
426 if ( $prepend_options ) {
427 foreach ( $prepend_options as $value => $label ) {
428 $html .= sprintf(
429 '<option value="%s" data-preserve-on-refresh="1"%s>%s</option>',
430 esc_attr( $value ),
431 ( in_array( $value, $selected_options, true ) ? ' selected' : '' ),
432 esc_attr( $label )
433 );
434 }
435 }
436
437 // Iterate through resources, if they exist, building <option> elements.
438 if ( $forms ) {
439 foreach ( $forms as $form ) {
440 // Legacy forms don't include a `format` key, so define them as inline.
441 $html .= sprintf(
442 '<option value="%s"%s>%s [%s]</option>',
443 esc_attr( $form['id'] ),
444 ( in_array( $form['id'], $selected_options, true ) ? ' selected' : '' ),
445 esc_attr( $form['name'] ),
446 ( ! empty( $form['format'] ) ? esc_attr( $form['format'] ) : 'inline' )
447 );
448 }
449 }
450
451 // Close select.
452 $html .= '</select>';
453
454 // If no description is provided, return the select field now.
455 if ( ! $description ) {
456 return $html;
457 }
458
459 // Append description before returning field.
460 if ( ! is_array( $description ) ) {
461 return $html . '<p class="description">' . $description . '</p>';
462 }
463
464 // Return description lines in a paragraph, using breaklines for each description entry in the array.
465 return $html . '<p class="description">' . implode( '<br />', $description ) . '</p>';
466
467 }
468
469 /**
470 * Returns the HTML/JS markup for the given Form ID.
471 *
472 * Legacy Forms will return HTML.
473 * Current Forms will return a <script> embed string.
474 *
475 * @since 1.9.6
476 *
477 * @param int $id Form ID.
478 * @param int $post_id Post ID that requested the Form.
479 * @return WP_Error|string
480 */
481 public function get_html( $id, $post_id = 0 ) {
482
483 // Cast ID to integer.
484 $id = absint( $id );
485
486 // Bail if the resources are a WP_Error.
487 if ( is_wp_error( $this->resources ) ) {
488 return $this->resources;
489 }
490
491 // Bail if the resource doesn't exist.
492 if ( ! isset( $this->resources[ $id ] ) ) {
493 return new WP_Error(
494 'convertkit_resource_forms_get_html',
495 sprintf(
496 /* translators: ConvertKit Form ID */
497 __( 'Kit Form ID %s does not exist on Kit.', 'convertkit' ),
498 $id
499 )
500 );
501 }
502
503 // Initialize Settings.
504 $settings = new ConvertKit_Settings();
505
506 // If no uid is present in the Form API data, this is a legacy form that's served by directly fetching the HTML
507 // from forms.kit.com.
508 if ( ! isset( $this->resources[ $id ]['uid'] ) ) {
509 // Bail if no Access Token is specified in the Plugin Settings.
510 if ( ! $settings->has_access_token() ) {
511 return new WP_Error(
512 'convertkit_resource_forms_get_html',
513 __( 'Kit Legacy Form could not be fetched as no Access Token specified in Plugin Settings', 'convertkit' )
514 );
515 }
516
517 // Initialize the API.
518 $api = new ConvertKit_API_V4(
519 CONVERTKIT_OAUTH_CLIENT_ID,
520 CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI,
521 $settings->get_access_token(),
522 $settings->get_refresh_token(),
523 $settings->debug_enabled(),
524 'output_form'
525 );
526
527 // Return Legacy Form HTML.
528 // We now call get_html() with the `embed_url` property, instead of get_form_html() with the `id` property,
529 // because `embed_url` includes the API Key.
530 return $api->get_html( $this->resources[ $id ]['embed_url'] );
531 }
532
533 // If the form's format is not an inline form, add the inline script before the closing </body> tag.
534 // This prevents a modal form's overlay being constrained by the WordPress Theme's styles,
535 // and accidentally embedding the same non-inline form twice, which would result in e.g. the same modal form
536 // displaying twice.
537 if ( $this->resources[ $id ]['format'] !== 'inline' ) {
538 add_filter(
539 'convertkit_output_scripts_footer',
540 function ( $scripts ) use ( $id, $post_id, $settings ) {
541
542 // Build script.
543 $script = array(
544 'async' => true,
545 'data-uid' => $this->resources[ $id ]['uid'],
546 'src' => $this->resources[ $id ]['embed_js'],
547 'data-kit-limit-per-session' => $settings->non_inline_form_limit_per_session(),
548 );
549
550 // If debugging is enabled, add the post ID to the script.
551 if ( $settings->debug_enabled() ) {
552 $script['data-kit-source-post-id'] = $post_id;
553 }
554
555 // Add the script to the scripts array.
556 $scripts[] = $script;
557
558 return $scripts;
559
560 }
561 );
562
563 // Sanity check we're not in the WordPress Admin interface.
564 // Some third party REST API Plugins seem to load frontend Posts, which would result in a wp_die() as
565 // the output Plugin class (rightly) isn't initialized in the backend.
566 if ( is_admin() ) {
567 return '';
568 }
569
570 // Don't output the global non-inline form, if defined, because
571 // a non-inline form was specified at either Post/Page default level, Post/Page level
572 // or Post Category level.
573 // This prevents multiple non-inline forms loading.
574 remove_action( 'wp_footer', array( WP_ConvertKit()->get_class( 'output' ), 'output_global_non_inline_form' ), 1 );
575
576 // Don't return a script for output, as it'll be output in the site's footer.
577 return '';
578 }
579
580 // If here, return Form <script> embed now, as we want the inline form to display at this specific point of the content.
581
582 // Define script key-value pairs.
583 $script = array(
584 'async' => true,
585 'data-uid' => $this->resources[ $id ]['uid'],
586 'src' => $this->resources[ $id ]['embed_js'],
587 );
588
589 // If debugging is enabled, add the post ID to the script.
590 if ( $settings->debug_enabled() ) {
591 $script['data-kit-source-post-id'] = $post_id;
592 }
593
594 /**
595 * Filter the form <script> key/value pairs immediately before the script is output.
596 *
597 * @since 2.4.5
598 *
599 * @param array $script Form script key/value pairs to output as <script> tag.
600 */
601 $script = apply_filters( 'convertkit_resource_forms_output_script', $script );
602
603 // Build script output.
604 $output = '<script';
605 foreach ( $script as $attribute => $value ) {
606 // If the value is true, just output the attribute.
607 if ( $value === true ) {
608 $output .= ' ' . esc_attr( $attribute );
609 continue;
610 }
611
612 // Sanitize attribute and value.
613 $attribute = esc_attr( $attribute );
614 $value = ( $attribute === 'src' ? esc_url( $value ) : esc_attr( $value ) );
615
616 // Output the attribute and value.
617 $output .= ' ' . $attribute;
618
619 // Output the value, if it's not a blank string.
620 if ( strlen( $value ) > 0 ) {
621 $output .= '="' . $value . '"';
622 }
623 }
624 $output .= '></script>';
625
626 // Return script output.
627 return $output;
628
629 }
630
631 }
632