PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 2.5.9
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v2.5.9
3.4.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 All 195 releases
convertkit / includes / class-convertkit-output.php

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

765 lines 21.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ConvertKit Output class.
4 *
5 * @package ConvertKit
6 * @author ConvertKit
7 */
8
9 /**
10 * Outputs Forms and Landing Pages on the frontend web site, based on
11 * the Post and Plugin's configuration.
12 *
13 * @since 1.9.6
14 */
15 class ConvertKit_Output {
16
17 /**
18 * Holds the ConvertKit Subscriber ID.
19 *
20 * @since 2.0.6
21 *
22 * @var int|string
23 */
24 private $subscriber_id = 0;
25
26 /**
27 * Holds the ConvertKit Plugin Settings class
28 *
29 * @since 1.9.6
30 *
31 * @var bool|ConvertKit_Settings
32 */
33 private $settings = false;
34
35 /**
36 * Holds the ConvertKit Post Settings class
37 *
38 * @since 1.9.6
39 *
40 * @var bool|ConvertKit_Post
41 */
42 private $post_settings = false;
43
44 /**
45 * Holds the available ConvertKit Forms
46 *
47 * @since 1.9.6
48 *
49 * @var bool|ConvertKit_Resource_Forms
50 */
51 private $forms = false;
52
53 /**
54 * Holds the available ConvertKit Landing Pages
55 *
56 * @since 1.9.6
57 *
58 * @var bool|ConvertKit_Resource_Landing_Pages
59 */
60 private $landing_pages = false;
61
62 /**
63 * Constructor. Registers actions and filters to output ConvertKit Forms and Landing Pages
64 * on the frontend web site.
65 *
66 * @since 1.9.6
67 */
68 public function __construct() {
69
70 add_action( 'init', array( $this, 'get_subscriber_id_from_request' ) );
71 add_action( 'wp', array( $this, 'maybe_tag_subscriber' ) );
72 add_action( 'template_redirect', array( $this, 'output_form' ) );
73 add_action( 'template_redirect', array( $this, 'page_takeover' ) );
74 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
75 add_filter( 'the_content', array( $this, 'append_form_to_content' ) );
76 add_filter( 'hooked_block_types', array( $this, 'maybe_register_form_block_on_category_archive' ), 10, 4 );
77 add_filter( 'hooked_block_convertkit/form', array( $this, 'append_form_block_to_category_archive' ), 10, 1 );
78 add_action( 'wp_footer', array( $this, 'output_global_non_inline_form' ), 1 );
79 add_action( 'wp_footer', array( $this, 'output_scripts_footer' ) );
80
81 }
82
83 /**
84 * Tags the subscriber, if:
85 * - a subscriber ID exists in the cookie or URL,
86 * - the WordPress Page has the "Add a Tag" setting specified
87 *
88 * @since 2.4.9.1
89 */
90 public function maybe_tag_subscriber() {
91
92 // Bail if no subscriber ID detected.
93 if ( ! $this->subscriber_id ) {
94 return;
95 }
96
97 // Bail if not a singular Post Type supported by ConvertKit.
98 if ( ! is_singular( convertkit_get_supported_post_types() ) ) {
99 return;
100 }
101
102 // Get Post ID.
103 $post_id = get_the_ID();
104
105 // Bail if a Post ID couldn't be identified.
106 if ( ! $post_id ) {
107 return;
108 }
109
110 // Get Settings, if they have not yet been loaded.
111 if ( ! $this->settings ) {
112 $this->settings = new ConvertKit_Settings();
113 }
114
115 // Bail if the API if an API Key and Secret is not defined.
116 if ( ! $this->settings->has_api_key_and_secret() ) {
117 return;
118 }
119
120 // Get ConvertKit Post's Settings, if they have not yet been loaded.
121 if ( ! $this->post_settings ) {
122 $this->post_settings = new ConvertKit_Post( $post_id );
123 }
124
125 // Bail if no "Add a Tag" setting specified for this Page.
126 if ( ! $this->post_settings->has_tag() ) {
127 return;
128 }
129
130 // Initialize the API.
131 $api = new ConvertKit_API_V4(
132 CONVERTKIT_OAUTH_CLIENT_ID,
133 CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI,
134 $this->settings->get_access_token(),
135 $this->settings->get_refresh_token(),
136 $this->settings->debug_enabled(),
137 'output'
138 );
139
140 // Tag subscriber.
141 $api->tag_subscriber( $this->post_settings->get_tag(), $this->subscriber_id );
142
143 }
144
145 /**
146 * Runs the `convertkit_output_output_form` action for singular Post Types that don't use the_content()
147 * or apply_filters( 'the_content' ) to output a ConvertKit Form.
148 *
149 * @since 1.9.6
150 */
151 public function output_form() {
152
153 /**
154 * Outputs a ConvertKit Form on singular Post Types that don't use the_content()
155 * or apply_filters( 'the_content' ).
156 *
157 * @since 1.9.6
158 *
159 * @return string Post Content with Form Appended, if applicable
160 */
161 do_action( 'convertkit_output_output_form' );
162
163 }
164
165 /**
166 * Outputs a ConvertKit Landing Page if configured, replacing all output for the singular Post Type.
167 *
168 * @since 1.9.6
169 */
170 public function page_takeover() {
171
172 $queried_object = get_queried_object();
173
174 // Bail if the queried object cannot be inspected.
175 if ( ! isset( $queried_object->post_type ) ) {
176 return;
177 }
178
179 // Get Post ID.
180 $post_id = $queried_object->ID;
181
182 // Bail if the queried object isn't a supported Post Type for Landing Pages.
183 if ( $queried_object->post_type !== 'page' ) {
184 return;
185 }
186
187 // Get ConvertKit Post's Settings, if they have not yet been loaded.
188 if ( ! $this->post_settings ) {
189 $this->post_settings = new ConvertKit_Post( $post_id );
190 }
191
192 // Get Landing Page ID.
193 $landing_page_id = $this->post_settings->get_landing_page();
194
195 /**
196 * Define the ConvertKit Landing Page ID to display for the given Post ID,
197 * overriding the Post settings.
198 *
199 * Return false to not display any ConvertKit Landing Page.
200 *
201 * @since 1.9.6
202 *
203 * @param int $landing_page_id Landing Page ID
204 * @param int $post_id Post ID
205 */
206 $landing_page_id = apply_filters( 'convertkit_output_page_takeover_landing_page_id', $landing_page_id, $post_id );
207
208 // Bail if no Landing Page is configured to be output.
209 if ( empty( $landing_page_id ) ) {
210 return;
211 }
212
213 // Get available ConvertKit Landing Pages, if they have not yet been loaded.
214 if ( ! $this->landing_pages ) {
215 $this->landing_pages = new ConvertKit_Resource_Landing_Pages( 'output_landing_page' );
216 }
217
218 // Get Landing Page.
219 $landing_page = $this->landing_pages->get_html( $this->post_settings->get_landing_page() );
220
221 // Bail if an error occured.
222 if ( is_wp_error( $landing_page ) ) {
223 return;
224 }
225
226 // Replace the favicon with the WordPress site's favicon, if specified.
227 $landing_page = $this->landing_pages->replace_favicon( $landing_page );
228
229 /**
230 * Perform any actions immediately prior to outputting the Landing Page.
231 *
232 * Caching and minification Plugins may need to hook here to prevent
233 * CSS / JS minification and lazy loading images, which can interfere
234 * with Landing Pages.
235 *
236 * @since 2.4.4
237 *
238 * @param string $landing_page ConvertKit Landing Page HTML.
239 * @param int $landing_page_id ConvertKit Landing Page ID.
240 * @param int $post_id WordPress Page ID.
241 */
242 do_action( 'convertkit_output_landing_page_before', $landing_page, $landing_page_id, $post_id );
243
244 // Output Landing Page.
245 // Output is supplied from ConvertKit's API, which is already sanitized.
246 echo $landing_page; // phpcs:ignore WordPress.Security.EscapeOutput
247 exit;
248
249 }
250
251 /**
252 * Inserts a form to the singular Page, Post or Custom Post Type's Content.
253 *
254 * @param string $content Post Content.
255 * @return string Post Content with Form Appended, if applicable
256 */
257 public function append_form_to_content( $content ) {
258
259 // Bail if not a singular Post Type supported by ConvertKit.
260 if ( ! is_singular( convertkit_get_supported_post_types() ) ) {
261 return $content;
262 }
263
264 // Get Post ID and ConvertKit Form ID for the Post.
265 $post_id = get_the_ID();
266 $form_id = $this->get_post_form_id( $post_id );
267
268 /**
269 * Define the ConvertKit Form ID to display for the given Post ID,
270 * overriding the Post, Category or Plugin settings.
271 *
272 * Return false to not display any ConvertKit Form.
273 *
274 * @since 1.9.6
275 *
276 * @param bool|int $form_id Form ID
277 * @param int $post_id Post ID
278 */
279 $form_id = apply_filters( 'convertkit_output_append_form_to_content_form_id', $form_id, $post_id );
280
281 // Return the Post Content, unedited, if the Form ID is false or zero.
282 if ( ! $form_id ) {
283 return $content;
284 }
285
286 // Get available ConvertKit Forms, if they have not yet been loaded.
287 if ( ! $this->forms ) {
288 $this->forms = new ConvertKit_Resource_Forms( 'output_form' );
289 }
290
291 // Get Form HTML.
292 $form = $this->forms->get_html( $form_id );
293
294 // If an error occured, it could be because the specified Form ID for the Post either:
295 // - belongs to another ConvertKit account (i.e. API credentials were changed in the Plugin, but this Post's specified Form was not changed), or
296 // - the form was deleted from the ConvertKit account.
297 // Attempt to fallback to the default form for this Post Type.
298 if ( is_wp_error( $form ) ) {
299 if ( $this->settings->debug_enabled() ) {
300 $content .= '<!-- ConvertKit append_form_to_content(): ' . $form->get_error_message() . ' Attempting fallback to Default Form. -->';
301 }
302
303 // Get Default Form ID for this Post's Type.
304 $form_id = $this->settings->get_default_form( get_post_type( $post_id ) );
305
306 // If no Default Form is specified, just return the Post Content, unedited.
307 if ( ! $form_id ) {
308 if ( $this->settings->debug_enabled() ) {
309 $content .= '<!-- ConvertKit append_form_to_content(): No Default Form exists as a fallback. -->';
310 }
311
312 return $content;
313 }
314
315 // Get Form HTML.
316 $form = $this->forms->get_html( $form_id );
317
318 // If an error occured again, the default form doesn't exist in this ConvertKit account.
319 // Just return the Post Content, unedited.
320 if ( is_wp_error( $form ) ) {
321 if ( $this->settings->debug_enabled() ) {
322 $content .= '<!-- ConvertKit append_form_to_content(): Default Form: ' . $form->get_error_message() . ' -->';
323 }
324
325 return $content;
326 }
327 }
328
329 // If here, we have a ConvertKit Form.
330 // Append form to Post's Content, based on the position setting.
331 $form_position = $this->settings->get_default_form_position( get_post_type( $post_id ) );
332 switch ( $form_position ) {
333 case 'before_after_content':
334 $content = $form . $content . $form;
335 break;
336
337 case 'before_content':
338 $content = $form . $content;
339 break;
340
341 case 'after_content':
342 default:
343 // Default behaviour < 2.5.8 was to append the Form after the content.
344 $content .= $form;
345 break;
346 }
347
348 /**
349 * Filter the Post's Content, which includes a ConvertKit Form, immediately before it is output.
350 *
351 * @since 1.9.6
352 *
353 * @param string $content Post Content
354 * @param string $form ConvertKit Form HTML
355 * @param int $post_id Post ID
356 * @param int $form_id ConvertKit Form ID
357 * @param string $form_position Form Position setting for the Post's Type.
358 */
359 $content = apply_filters( 'convertkit_frontend_append_form', $content, $form, $post_id, $form_id, $form_position );
360
361 return $content;
362
363 }
364
365 /**
366 * Registers the ConvertKit Form block to before or after the Query Loop block, when viewing a Category archive.
367 *
368 * See append_form_block_on_category_archive() configures the block to display the applicable category's Form.
369 *
370 * @since 2.4.9.1
371 *
372 * @param array $hooked_blocks The list of hooked block types.
373 * @param string $position The relative position of the hooked blocks.
374 * @param string $anchor_block The anchor block type.
375 * @param WP_Block_Template|WP_Post|array $context The block template, template part, wp_navigation post type, or pattern that the anchor block belongs to.
376 * @return array
377 */
378 public function maybe_register_form_block_on_category_archive( $hooked_blocks, $position, $anchor_block, $context ) {
379
380 // Don't append if we're not viewing a category archive.
381 if ( ! is_category() ) {
382 return $hooked_blocks;
383 }
384
385 if ( $context instanceof WP_Block_Template && $context->slug !== 'archive' ) {
386 return $hooked_blocks;
387 }
388
389 // Don't append if the anchor block isn't the Query Loop block.
390 if ( $anchor_block !== 'core/query' ) {
391 return $hooked_blocks;
392 }
393
394 // Don't append if the Category's form position setting is not defined.
395 $form_position = $this->get_term_form_position();
396 if ( ! $form_position ) {
397 // Unhook this function as we don't need to check again in this request, as we'll
398 // never output a form on the Category archive.
399 remove_filter( 'hooked_block_types', array( $this, 'maybe_register_form_block_on_category_archive' ), 10 );
400
401 return $hooked_blocks;
402 }
403
404 // Don't append if the position doesn't match.
405 if ( $form_position !== $position ) {
406 return $hooked_blocks;
407 }
408
409 // Hook the ConvertKit Form block.
410 $hooked_blocks[] = 'convertkit/form';
411
412 // Unhook this function as we don't need to check again in this request, as
413 // we have now appended the form.
414 remove_filter( 'hooked_block_types', array( $this, 'maybe_register_form_block_on_category_archive' ), 10 );
415
416 return $hooked_blocks;
417
418 }
419
420 /**
421 * Configures the ConvertKit Form block that was hooked below the Query Loop block by maybe_register_form_block_on_category_archive,
422 * defining the Form ID based on the current Category's Form ID.
423 *
424 * @since 2.4.9.1
425 *
426 * @param array $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block.
427 * @return null|array
428 */
429 public function append_form_block_to_category_archive( $parsed_hooked_block ) {
430
431 // Sanity check that we're still viewing a Category archive.
432 if ( ! is_category() ) {
433 // Returning null will unregister the Form block from displaying.
434 return null;
435 }
436
437 // Get Category archive being viewed.
438 $category = get_category( get_query_var( 'cat' ) );
439
440 // Bail if the Category could be found.
441 if ( is_wp_error( $category ) || is_null( $category ) ) {
442 // Returning null will unregister the Form block from displaying.
443 return null;
444 }
445
446 // Load Term Settings.
447 $term_settings = new ConvertKit_Term( $category->term_id );
448
449 // Bail if no Form specified for the Category.
450 if ( ! $term_settings->has_form() ) {
451 // Returning null will unregister the Form block from displaying.
452 return null;
453 }
454
455 // Define the form block attributes to display the given Form ID.
456 $parsed_hooked_block['attrs'] = array(
457 'id' => absint( $term_settings->get_form() ),
458 );
459
460 // Return the Form block with its attributes.
461 return $parsed_hooked_block;
462
463 }
464
465 /**
466 * Returns the Post, Category or Plugin ConvertKit Form ID for the given Post.
467 *
468 * If the Post specifies a form to use, returns that Form ID.
469 * If the Post uses the 'Default' setting, and an assigned Category has a Form ID, uses the Category's Form ID.
470 * Otherwise falls back to the Plugin's Default Form ID (if any).
471 *
472 * @since 1.9.6
473 *
474 * @param int $post_id Post ID.
475 * @return bool|string|int false|'default'|Form ID
476 */
477 private function get_post_form_id( $post_id ) {
478
479 // Get Settings, if they have not yet been loaded.
480 if ( ! $this->settings ) {
481 $this->settings = new ConvertKit_Settings();
482 }
483
484 // Get ConvertKit Post's Settings, if they have not yet been loaded.
485 if ( ! $this->post_settings ) {
486 $this->post_settings = new ConvertKit_Post( $post_id );
487 }
488
489 // If the Post specifies a Form to use, return its ID now.
490 if ( $this->post_settings->has_form() ) {
491 return $this->post_settings->get_form();
492 }
493
494 // If the Post specifies that no Form should be used, return false.
495 if ( $this->post_settings->uses_no_form() ) {
496 return false;
497 }
498
499 // Sanity check that the Post uses the Default Form setting, which should be the case
500 // because the above conditions were not met.
501 if ( ! $this->post_settings->uses_default_form() ) {
502 return false;
503 }
504
505 // Get Post's Categories.
506 $categories = wp_get_post_categories(
507 $post_id,
508 array(
509 'fields' => 'ids',
510 )
511 );
512
513 // If no Categories exist, use the Default Form.
514 if ( ! is_array( $categories ) || ! count( $categories ) ) {
515 // Get Post Type.
516 return $this->settings->get_default_form( get_post_type( $post_id ) );
517 }
518
519 /**
520 * Iterate through Categories in reverse order.
521 * This honors the behaviour < 1.9.6, which states that if multiple Categories each have a Form.
522 * assigned, the last Category with a Form in the wp_get_post_categories() call will be used.
523 */
524 $categories = array_reverse( $categories );
525 foreach ( $categories as $term_id ) {
526 // Load Term Settings.
527 $term_settings = new ConvertKit_Term( $term_id );
528
529 // If a Form ID exists, return it now.
530 if ( $term_settings->has_form() ) {
531 return $term_settings->get_form();
532 }
533 }
534
535 // If here, use the Plugin's Default Form.
536 return $this->settings->get_default_form( get_post_type( $post_id ) );
537
538 }
539
540 /**
541 * Returns the Form Position setting for the currently viewed Category.
542 *
543 * @since 2.4.9.1
544 *
545 * @return bool|string
546 */
547 private function get_term_form_position() {
548
549 // Get Category archive being viewed.
550 $category = get_category( get_query_var( 'cat' ) );
551
552 // Bail if the Category could be found.
553 if ( is_wp_error( $category ) || is_null( $category ) ) {
554 return false;
555 }
556
557 // Load Term Settings.
558 $term_settings = new ConvertKit_Term( $category->term_id );
559
560 // Return false if no form position is defined i.e. we don't want to display
561 // it on the Category archive.
562 if ( ! $term_settings->has_form_position() ) {
563 return false;
564 }
565
566 // Return form position.
567 return $term_settings->get_form_position();
568
569 }
570
571 /**
572 * Enqueue scripts.
573 *
574 * @since 1.9.6
575 */
576 public function enqueue_scripts() {
577
578 // Get Post.
579 $post = get_post();
580
581 // Bail if no Post could be fetched.
582 if ( ! $post ) {
583 return;
584 }
585
586 // Get ConvertKit Settings and Post's Settings.
587 $settings = new ConvertKit_Settings();
588 $convertkit_post = new ConvertKit_Post( $post->ID );
589
590 // Register scripts that we might use.
591 wp_register_script(
592 'convertkit-js',
593 CONVERTKIT_PLUGIN_URL . 'resources/frontend/js/convertkit.js',
594 array(),
595 CONVERTKIT_PLUGIN_VERSION,
596 true
597 );
598 wp_localize_script(
599 'convertkit-js',
600 'convertkit',
601 array(
602 'ajaxurl' => admin_url( 'admin-ajax.php' ),
603 'debug' => $settings->debug_enabled(),
604 'nonce' => wp_create_nonce( 'convertkit' ),
605 'subscriber_id' => $this->subscriber_id,
606 )
607 );
608
609 // Bail if the no scripts setting is enabled.
610 if ( $settings->scripts_disabled() ) {
611 return;
612 }
613
614 // Enqueue.
615 wp_enqueue_script( 'convertkit-js' );
616
617 }
618
619 /**
620 * Gets the subscriber ID from the request (either the cookie or the URL).
621 *
622 * @since 1.9.6
623 */
624 public function get_subscriber_id_from_request() {
625
626 // Use ConvertKit_Subscriber class to fetch and validate the subscriber ID.
627 $subscriber = new ConvertKit_Subscriber();
628 $subscriber_id = $subscriber->get_subscriber_id();
629
630 // If an error occured, the subscriber ID in the request/cookie is not a valid subscriber.
631 if ( is_wp_error( $subscriber_id ) ) {
632 return;
633 }
634
635 $this->subscriber_id = $subscriber_id;
636
637 }
638
639 /**
640 * Outputs a non-inline form if defined in the Plugin's settings >
641 * Default Non-Inline Form (Global) setting.
642 *
643 * @since 2.3.3
644 */
645 public function output_global_non_inline_form() {
646
647 // Get Settings, if they have not yet been loaded.
648 if ( ! $this->settings ) {
649 $this->settings = new ConvertKit_Settings();
650 }
651
652 // Bail if no non-inline form setting is specified.
653 if ( ! $this->settings->has_non_inline_form() ) {
654 return;
655 }
656
657 // Get form.
658 $convertkit_forms = new ConvertKit_Resource_Forms();
659 $form = $convertkit_forms->get_by_id( (int) $this->settings->get_non_inline_form() );
660
661 // Bail if the Form doesn't exist (this shouldn't happen, but you never know).
662 if ( ! $form ) {
663 return;
664 }
665
666 // Add the form to the scripts array so it is included in the output.
667 add_filter(
668 'convertkit_output_scripts_footer',
669 function ( $scripts ) use ( $form ) {
670
671 $scripts[] = array(
672 'async' => true,
673 'data-uid' => $form['uid'],
674 'src' => $form['embed_js'],
675 );
676
677 return $scripts;
678
679 }
680 );
681
682 }
683
684 /**
685 * Outputs any JS <script> tags registered with the convertkit_output_scripts_footer
686 * filter
687 *
688 * @since 2.1.4
689 */
690 public function output_scripts_footer() {
691
692 // Define array of scripts.
693 $scripts = array();
694
695 /**
696 * Define an array of scripts to output in the footer of the WordPress site.
697 *
698 * @since 2.1.4
699 *
700 * @param array $scripts Scripts.
701 */
702 $scripts = apply_filters( 'convertkit_output_scripts_footer', $scripts );
703
704 // Bail if no scripts exist.
705 if ( ! count( $scripts ) ) {
706 return;
707 }
708
709 // Define array to store <script> outputs.
710 $output_scripts = array();
711
712 // Iterate through scripts, building the <script> tag for each.
713 foreach ( $scripts as $script ) {
714 /**
715 * Filter the form <script> key/value pairs immediately before the script is output.
716 *
717 * @since 2.4.5
718 *
719 * @param array $script Form script key/value pairs to output as <script> tag.
720 */
721 $script = apply_filters( 'convertkit_output_script_footer', $script );
722
723 // Build output.
724 $output = '<script';
725 foreach ( $script as $attribute => $value ) {
726 // If the value is true, just output the attribute.
727 if ( $value === true ) {
728 $output .= ' ' . esc_attr( $attribute );
729 continue;
730 }
731
732 // Sanitize attribute and value.
733 $attribute = esc_attr( $attribute );
734 $value = ( $attribute === 'src' ? esc_url( $value ) : esc_attr( $value ) );
735
736 // Output the attribute and value.
737 $output .= ' ' . $attribute;
738
739 // Output the value, if it's not a blank string.
740 if ( strlen( $value ) > 0 ) {
741 $output .= '="' . $value . '"';
742 }
743 }
744
745 $output .= '></script>';
746
747 // Add to array.
748 $output_scripts[] = $output;
749 }
750
751 // Remove duplicate scripts.
752 // This prevents the same non-inline form displaying twice. For example, if a modal form is specified both
753 // in the Page's settings and the Form block, the user would see the same modal form displayed twice
754 // because the script would be output twice.
755 $output_scripts = array_unique( $output_scripts );
756
757 // Output scripts.
758 foreach ( $output_scripts as $output_script ) {
759 echo $output_script . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
760 }
761
762 }
763
764 }
765