PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 3.2.3
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v3.2.3
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.2.3, at includes/class-convertkit-resource-forms.php

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