@automattic/newspack-blocks 1.75.6 → 2.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/.cache/babel/05ef61416b32f57c09787aa5b64d2174.json.gz +0 -0
  2. package/.cache/babel/161346fa97c507c17228f7da8926b1a2.json.gz +0 -0
  3. package/.cache/babel/1f43960564787ebde7c6d44957cddf0e.json.gz +0 -0
  4. package/.cache/babel/2f22325668b5763f8f2c9557833af36f.json.gz +0 -0
  5. package/.cache/babel/3e0ce774e995d778a0c3c43825791fdf.json.gz +0 -0
  6. package/.cache/babel/6d457f8985600b07d97aebc4e1a21855.json.gz +0 -0
  7. package/.cache/babel/98c36ce1c809c31c7b1a84794c9233d3.json.gz +0 -0
  8. package/.cache/babel/a2068f9bffb6026a246057f4a1058bd9.json.gz +0 -0
  9. package/.cache/babel/a3f4c297e3a52f8342c1ffea1b4b1c0c.json.gz +0 -0
  10. package/.cache/babel/e1d8932bb19afa36f476f107dbc6ca39.json.gz +0 -0
  11. package/CHANGELOG.md +8 -0
  12. package/composer.lock +13 -13
  13. package/dist/carousel/view.asset.php +1 -1
  14. package/dist/carousel/view.css +1 -1
  15. package/dist/carousel/view.js +1 -1
  16. package/dist/carousel/view.rtl.css +1 -1
  17. package/dist/donateStreamlined.asset.php +1 -1
  18. package/dist/donateStreamlined.js +1 -1
  19. package/dist/editor.asset.php +1 -1
  20. package/dist/editor.css +1 -1
  21. package/dist/editor.js +3 -3
  22. package/dist/editor.rtl.css +1 -1
  23. package/dist/homepage-articles/view.asset.php +1 -1
  24. package/dist/homepage-articles/view.css +1 -1
  25. package/dist/homepage-articles/view.rtl.css +1 -1
  26. package/dist/modal.asset.php +1 -1
  27. package/dist/modal.js +1 -1
  28. package/includes/class-modal-checkout.php +49 -7
  29. package/includes/class-newspack-blocks.php +52 -59
  30. package/languages/newspack-blocks-de_DE.po +1 -1
  31. package/languages/newspack-blocks-es_ES.po +1 -1
  32. package/languages/newspack-blocks-fr_BE.po +2 -2
  33. package/languages/newspack-blocks-pt_PT.po +1 -1
  34. package/languages/newspack-blocks.pot +1 -1
  35. package/newspack-blocks.php +4 -4
  36. package/package.json +3 -3
  37. package/src/blocks/carousel/edit.js +5 -17
  38. package/src/blocks/carousel/editor.scss +1 -17
  39. package/src/blocks/carousel/view.php +20 -79
  40. package/src/blocks/carousel/view.scss +3 -36
  41. package/src/blocks/checkout-button/block.json +9 -0
  42. package/src/blocks/checkout-button/edit.js +45 -0
  43. package/src/blocks/checkout-button/save.js +23 -0
  44. package/src/blocks/donate/edit/index.tsx +5 -12
  45. package/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-base.php +1 -7
  46. package/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-frequency-based.php +1 -1
  47. package/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer.php +0 -1
  48. package/src/blocks/donate/streamlined/index.test.js +2 -3
  49. package/src/blocks/donate/streamlined/index.ts +0 -8
  50. package/src/blocks/donate/streamlined/utils.ts +0 -1
  51. package/src/blocks/homepage-articles/block.json +4 -0
  52. package/src/blocks/homepage-articles/class-wp-rest-newspack-articles-controller.php +0 -28
  53. package/src/blocks/homepage-articles/edit.js +10 -0
  54. package/src/blocks/homepage-articles/store.js +22 -4
  55. package/src/blocks/homepage-articles/templates/article.php +0 -4
  56. package/src/blocks/homepage-articles/templates/articles-loop.php +1 -2
  57. package/src/blocks/homepage-articles/utils.ts +1 -0
  58. package/src/blocks/homepage-articles/view.php +0 -34
  59. package/src/blocks/homepage-articles/view.scss +0 -23
  60. package/src/blocks/iframe/view.php +11 -13
  61. package/src/modal-checkout/modal.js +14 -0
  62. package/src/modal-checkout/templates/checkout-form.php +8 -0
  63. package/src/types/index.d.ts +1 -2
  64. package/vendor/autoload.php +1 -1
  65. package/vendor/composer/autoload_real.php +4 -4
  66. package/vendor/composer/autoload_static.php +2 -2
  67. package/vendor/composer/installed.php +2 -2
  68. package/.cache/babel/0156e1f0f743cd4a5a4de57f8ccfe0b8.json.gz +0 -0
  69. package/.cache/babel/02e816c46dd4e4d9a24c9a1b7a59540a.json.gz +0 -0
  70. package/.cache/babel/06ef11d145e318fb48d3a115527879ce.json.gz +0 -0
  71. package/.cache/babel/078314f1ffd302d32bbe1124c797c4a8.json.gz +0 -0
  72. package/.cache/babel/08bd669298cd26a7d3a62aadcf637516.json.gz +0 -0
  73. package/.cache/babel/0a3c5c9b515d55e6daf9bb1530a33935.json.gz +0 -0
  74. package/.cache/babel/0ec98f305300249477f0877dcfeab93f.json.gz +0 -0
  75. package/.cache/babel/0f96392a3b96da0b4dce7a77edef76ca.json.gz +0 -0
  76. package/.cache/babel/0fced15ed787cc4239bb53c1d12892e6.json.gz +0 -0
  77. package/.cache/babel/13e57088d8bf09905af8538fcde7339b.json.gz +0 -0
  78. package/.cache/babel/15387cb771ffa80b113069531d7dbf2a.json.gz +0 -0
  79. package/.cache/babel/168ca11449f840b88170d992e1c687f9.json.gz +0 -0
  80. package/.cache/babel/1745993d7e6ba90f0d42d2bd650945a5.json.gz +0 -0
  81. package/.cache/babel/1a54b89358e5145c6ef0ce9b6f297420.json.gz +0 -0
  82. package/.cache/babel/1ab4bf44aa14d2eb7e6911900a3b2c12.json.gz +0 -0
  83. package/.cache/babel/1bc1de530b8e11865e9129484335e271.json.gz +0 -0
  84. package/.cache/babel/1c9fb1cadc04d30853d52c2b78719398.json.gz +0 -0
  85. package/.cache/babel/1e3c4531cef0122939eabd5f484f82bd.json.gz +0 -0
  86. package/.cache/babel/240c25710f01a9ff51938c1864e05565.json.gz +0 -0
  87. package/.cache/babel/243de511af40dccb789c53e2dbbcf8e2.json.gz +0 -0
  88. package/.cache/babel/251320ac52752952f12b0509db4847e9.json.gz +0 -0
  89. package/.cache/babel/264935e7a1393600813a1526a4c51a99.json.gz +0 -0
  90. package/.cache/babel/26ba0bb46844575022e54a8819fbf405.json.gz +0 -0
  91. package/.cache/babel/284040ebee104b9acefa5396c3e0e813.json.gz +0 -0
  92. package/.cache/babel/2dc630cecd79488c7912540ac3a1ae93.json.gz +0 -0
  93. package/.cache/babel/30cc975abcfadd1df5b5ab14bfef277e.json.gz +0 -0
  94. package/.cache/babel/317592e8c6159e343d73886ae88e3549.json.gz +0 -0
  95. package/.cache/babel/31bb32d659e37be6cb488e5db5bdeccb.json.gz +0 -0
  96. package/.cache/babel/3521a72b024f8525cb016ecb583e9982.json.gz +0 -0
  97. package/.cache/babel/41a786b05c7e86aa4ee7b0aca2422737.json.gz +0 -0
  98. package/.cache/babel/43305bd73ad590a21ab00045894c1407.json.gz +0 -0
  99. package/.cache/babel/469f215eb2ddc25aa138667dd41fb568.json.gz +0 -0
  100. package/.cache/babel/480d26447b7ee019a37e4e18422403e9.json.gz +0 -0
  101. package/.cache/babel/48df6fe3b45d872e1ded6f33a70e3176.json.gz +0 -0
  102. package/.cache/babel/49194e4a0a36c7ed4c0bff0be7fb831c.json.gz +0 -0
  103. package/.cache/babel/4b3def64c2dc6c4f40e7d35e18d9cda5.json.gz +0 -0
  104. package/.cache/babel/4b84a19f72461be1f3f8a1363456cb30.json.gz +0 -0
  105. package/.cache/babel/506a7461434184f7d09f833f61d6bfcf.json.gz +0 -0
  106. package/.cache/babel/50aaf34aa54b2cd3058518e42850bb62.json.gz +0 -0
  107. package/.cache/babel/522cbf93af7ec749dde0e2a7dd860c9f.json.gz +0 -0
  108. package/.cache/babel/53d915d1e8c3e78d33ce618b7e71d9c5.json.gz +0 -0
  109. package/.cache/babel/56046f06a99a61b7847b604a58062e17.json.gz +0 -0
  110. package/.cache/babel/5aa5e8d1492938c62999de022c4b220e.json.gz +0 -0
  111. package/.cache/babel/5cdc287b0f89d83292cb39ed8a26e634.json.gz +0 -0
  112. package/.cache/babel/6366f9a19fda42ebc6432b2767e76d7b.json.gz +0 -0
  113. package/.cache/babel/6531721abbc0b3906b28b268ed7914af.json.gz +0 -0
  114. package/.cache/babel/695780d53e2d31de09fc42456ad05cff.json.gz +0 -0
  115. package/.cache/babel/6ad227d2c4160c54ee51fdae5d795d99.json.gz +0 -0
  116. package/.cache/babel/6b835eb676bf657d4405404c13ceec1b.json.gz +0 -0
  117. package/.cache/babel/6bc5913640b460de4b83482460b5a779.json.gz +0 -0
  118. package/.cache/babel/6bc92299b6332d48bcc944e155ce08cd.json.gz +0 -0
  119. package/.cache/babel/6d2559360f2fff9e75ddf057cfe0e2a8.json.gz +0 -0
  120. package/.cache/babel/6edf79f9da4b488bd4355052d2604c1b.json.gz +0 -0
  121. package/.cache/babel/6f2e7ac771a37e6fcde789a298b73e70.json.gz +0 -0
  122. package/.cache/babel/7030a1a5aa29c124ce7c35ac8767ad57.json.gz +0 -0
  123. package/.cache/babel/764469c94d0f935d0d48d3b8ca9964ff.json.gz +0 -0
  124. package/.cache/babel/7b683070fec6ffe276629b2e43f832ad.json.gz +0 -0
  125. package/.cache/babel/82dcac9a920b1401a6e4d9116620ad03.json.gz +0 -0
  126. package/.cache/babel/845433093cc647521f9ed7aada6399c5.json.gz +0 -0
  127. package/.cache/babel/867d3b995951f97af45663f52797290b.json.gz +0 -0
  128. package/.cache/babel/88ec99735ea4f314937736f04360ff8c.json.gz +0 -0
  129. package/.cache/babel/89043a36a31c7e92c973b2e0ae512e39.json.gz +0 -0
  130. package/.cache/babel/8959ab8fcfa783cb56e9418b99c0951c.json.gz +0 -0
  131. package/.cache/babel/8992b31884c946aa1b70f6db4434a9fc.json.gz +0 -0
  132. package/.cache/babel/8ac84b121ed7e10702015d07c86d96c8.json.gz +0 -0
  133. package/.cache/babel/8b9624d721f8706682e33fe1853c8287.json.gz +0 -0
  134. package/.cache/babel/8e6a90b4bc7afb2e6b93ea86efce5a92.json.gz +0 -0
  135. package/.cache/babel/91cad33175abac6d33f0c20421ec316f.json.gz +0 -0
  136. package/.cache/babel/9300ec843499931b4743b6f0862a12ad.json.gz +0 -0
  137. package/.cache/babel/9321b14d86eb18eeecee85b95d36800c.json.gz +0 -0
  138. package/.cache/babel/95452ca3f0fd5a140f6426306fdec2b4.json.gz +0 -0
  139. package/.cache/babel/9dd0c10541b1bd54bcc6f7510f7e1730.json.gz +0 -0
  140. package/.cache/babel/9fa3511f36f06d92dadc26f96928aabb.json.gz +0 -0
  141. package/.cache/babel/a12f0cc885278887a2b0bbb3871824d6.json.gz +0 -0
  142. package/.cache/babel/a398d30cfa757f570719e381323da904.json.gz +0 -0
  143. package/.cache/babel/a656f81700e8f1a93c995190f8138b3b.json.gz +0 -0
  144. package/.cache/babel/ab39cbb804689d063293f37fa4485487.json.gz +0 -0
  145. package/.cache/babel/acff6371a410845f5da464cb4e237aaf.json.gz +0 -0
  146. package/.cache/babel/ad43360146aec3cbcdc4c0df3791d848.json.gz +0 -0
  147. package/.cache/babel/b25fb66cc6e303883b249f044f991b93.json.gz +0 -0
  148. package/.cache/babel/b54e20f4b38273a1ede9af6bb4bf20ac.json.gz +0 -0
  149. package/.cache/babel/bd79e4e680122935b9d5ff39b4b0665b.json.gz +0 -0
  150. package/.cache/babel/c0d9222366e0ca5cb1687b96ba0ff169.json.gz +0 -0
  151. package/.cache/babel/c0e75e9017dcf69507da774f93b23422.json.gz +0 -0
  152. package/.cache/babel/c481eae84d69b65405a44af442dbfea7.json.gz +0 -0
  153. package/.cache/babel/c7b72847e8266932199eab550d74d4ec.json.gz +0 -0
  154. package/.cache/babel/cb562dfa06c52220cf31b2d2f3dfe5ed.json.gz +0 -0
  155. package/.cache/babel/cf4e47aa0d80b97616c031301bcf80dc.json.gz +0 -0
  156. package/.cache/babel/d1cc4e91603f5c597ff63d85bf7f2ff2.json.gz +0 -0
  157. package/.cache/babel/d542afdb91386a0759170a6313be13c8.json.gz +0 -0
  158. package/.cache/babel/d665b07906defb294138edfb6c04e0a1.json.gz +0 -0
  159. package/.cache/babel/db5215643b0737dc5245972a5c25f736.json.gz +0 -0
  160. package/.cache/babel/dc94026c61aa370892be7ffaa3c0bdbd.json.gz +0 -0
  161. package/.cache/babel/df56e5784cf3decfe6c7a459f29e9d5f.json.gz +0 -0
  162. package/.cache/babel/e064590b2370afea20d7e2fbb313b7c5.json.gz +0 -0
  163. package/.cache/babel/e0b5ce53743b7a92333dde848fd81f42.json.gz +0 -0
  164. package/.cache/babel/e1f3cdff6066c3110a7df739f642ddc8.json.gz +0 -0
  165. package/.cache/babel/e32fb0d04614c50ee701cf3af842887f.json.gz +0 -0
  166. package/.cache/babel/e499e66d1cc40fb9b6c4238bf3dd2e6a.json.gz +0 -0
  167. package/.cache/babel/ef23c92e697190265fa2e11c69779b88.json.gz +0 -0
  168. package/.cache/babel/efc47462720c1d97fe7176407b3c5bca.json.gz +0 -0
  169. package/.cache/babel/f13abc23689523a1b6b97b9bfdd1aac5.json.gz +0 -0
  170. package/.cache/babel/f5dc184121cc109a8bc4f20364bd68b5.json.gz +0 -0
  171. package/.cache/babel/f8e24ac6182e943c6a6cd14ed62ee1b4.json.gz +0 -0
  172. package/.cache/babel/fac753cac804bb674519703a43aec6aa.json.gz +0 -0
  173. package/.cache/babel/fe735cbd0f6e131e28d8b6eb1f39141b.json.gz +0 -0
  174. package/.cache/babel/ff0e41a8b69788632a0b147d4169dc94.json.gz +0 -0
  175. package/amp/homepage-articles/view.js +0 -87
@@ -23,7 +23,6 @@ function newspack_blocks_render_block_carousel( $attributes ) {
23
23
  $autoplay = isset( $attributes['autoplay'] ) ? $attributes['autoplay'] : false;
24
24
  $delay = isset( $attributes['delay'] ) ? absint( $attributes['delay'] ) : 3;
25
25
  $authors = isset( $attributes['authors'] ) ? $attributes['authors'] : array();
26
- $is_amp = Newspack_Blocks::is_amp();
27
26
 
28
27
  $other = array();
29
28
  if ( $autoplay ) {
@@ -50,12 +49,8 @@ function newspack_blocks_render_block_carousel( $attributes ) {
50
49
 
51
50
  $article_classes = [
52
51
  'post-has-image',
52
+ 'swiper-slide',
53
53
  ];
54
- if ( $is_amp ) {
55
- $article_classes[] = 'amp-carousel-slide';
56
- } else {
57
- $article_classes[] = 'swiper-slide';
58
- }
59
54
 
60
55
  // Add classes based on the post's assigned categories and tags.
61
56
  $article_classes[] = Newspack_Blocks::get_term_classes( $post_id );
@@ -224,49 +219,26 @@ function newspack_blocks_render_block_carousel( $attributes ) {
224
219
  }
225
220
 
226
221
  $slides_per_view = absint( $attributes['slidesPerView'] ?? 1 );
227
- $slides_to_show = $slides_per_view <= $counter ? $slides_per_view : $counter;
228
222
  $aspect_ratio = floatval( $attributes['aspectRatio'] ?? 0.75 );
229
223
 
230
- if ( $is_amp ) {
231
- $selector = sprintf(
232
- '<amp-selector id="wp-block-newspack-carousel__amp-pagination__%1$d" class="swiper-pagination-bullets amp-pagination" on="select:wp-block-newspack-carousel__amp-carousel__%1$d.goToSlide(index=event.targetOption)" layout="container" %2$s>%3$s</amp-selector>',
233
- absint( $newspack_blocks_carousel_id ),
234
- $attributes['hideControls'] ? 'aria-hidden="true"' : '',
235
- implode( '', $buttons )
236
- );
224
+ $selector = sprintf(
225
+ '<div class="swiper-pagination-bullets" %1$s>%2$s</div>',
226
+ $attributes['hideControls'] ? 'aria-hidden="true"' : '',
227
+ implode( '', $buttons )
228
+ );
229
+ $navigation = 1 === $counter ? '' : sprintf(
230
+ '<button class="swiper-button swiper-button-prev" aria-label="%1$s" %3$s></button><button class="swiper-button swiper-button-next" aria-label="%2$s" %3$s></button>',
231
+ esc_attr__( 'Previous Slide', 'newspack-blocks' ),
232
+ esc_attr__( 'Next Slide', 'newspack-blocks' ),
233
+ $attributes['hideControls'] ? 'aria-hidden="true"' : ''
234
+ );
235
+ $carousel = sprintf(
236
+ '<div class="swiper"><div class="swiper-wrapper">%s</div>%s</div>',
237
+ $slides,
238
+ $navigation
239
+ );
240
+ $autoplay_ui = $autoplay ? newspack_blocks_carousel_block_autoplay_ui( $newspack_blocks_carousel_id ) : '';
237
241
 
238
- $carousel = sprintf(
239
- '<amp-base-carousel class="wp-block-newspack-carousel__amp-carousel" width="%1$s" height="%2$s" heights="%3$s" layout="responsive" snap="true" data-next-button-aria-label="%4$s" data-prev-button-aria-label="%5$s" controls="auto" loop="true" %6$s id="wp-block-newspack-carousel__amp-carousel__%7$s" on="slideChange:wp-block-newspack-carousel__amp-pagination__%7$s.toggle(index=event.index, value=true)" advance-count="1" visible-count="%8$s">%9$s</amp-base-carousel>',
240
- esc_attr( $slides_per_view * 1 ),
241
- esc_attr( $aspect_ratio ),
242
- esc_attr( '(min-width: 1168px) ' . ( $aspect_ratio / $slides_to_show * 100 ) . '% !important, (min-width: 782px) ' . ( $slides_to_show > 1 ? ( $aspect_ratio / 2 * 100 ) . '% !important' : ( $aspect_ratio * 100 ) . '% !important' ) . ', ' . ( $aspect_ratio * 100 ) . '% !important' ),
243
- esc_attr__( 'Next Slide', 'newspack-blocks' ),
244
- esc_attr__( 'Previous Slide', 'newspack-blocks' ),
245
- $autoplay ? 'auto-advance="true" auto-advance-interval=' . esc_attr( $delay * 1000 ) : '',
246
- absint( $newspack_blocks_carousel_id ),
247
- esc_attr( '(min-width: 1168px) ' . $slides_to_show . ', (min-width: 782px) ' . ( $slides_to_show > 1 ? 2 : 1 ) . ', ' . 1 ),
248
- $slides
249
- );
250
- $autoplay_ui = $autoplay ? newspack_blocks_carousel_block_autoplay_ui_amp( $newspack_blocks_carousel_id ) : '';
251
- } else {
252
- $selector = sprintf(
253
- '<div class="swiper-pagination-bullets amp-pagination" %1$s>%2$s</div>',
254
- $attributes['hideControls'] ? 'aria-hidden="true"' : '',
255
- implode( '', $buttons )
256
- );
257
- $navigation = 1 === $counter ? '' : sprintf(
258
- '<button class="swiper-button swiper-button-prev" aria-label="%1$s" %3$s></button><button class="swiper-button swiper-button-next" aria-label="%2$s" %3$s></button>',
259
- esc_attr__( 'Previous Slide', 'newspack-blocks' ),
260
- esc_attr__( 'Next Slide', 'newspack-blocks' ),
261
- $attributes['hideControls'] ? 'aria-hidden="true"' : ''
262
- );
263
- $carousel = sprintf(
264
- '<div class="swiper"><div class="swiper-wrapper">%s</div>%s</div>',
265
- $slides,
266
- $navigation
267
- );
268
- $autoplay_ui = $autoplay ? newspack_blocks_carousel_block_autoplay_ui( $newspack_blocks_carousel_id ) : '';
269
- }
270
242
  $data_attributes = [
271
243
  'data-current-post-id=' . $post_id,
272
244
  'data-slides-per-view=' . esc_attr( $slides_per_view ),
@@ -274,7 +246,7 @@ function newspack_blocks_render_block_carousel( $attributes ) {
274
246
  'data-aspect-ratio=' . esc_attr( $aspect_ratio ),
275
247
  ];
276
248
 
277
- if ( $autoplay && ! $is_amp ) {
249
+ if ( $autoplay ) {
278
250
  $data_attributes[] = 'data-autoplay=1';
279
251
  $data_attributes[] = sprintf( 'data-autoplay_delay=%s', esc_attr( $delay ) );
280
252
  }
@@ -294,7 +266,7 @@ function newspack_blocks_render_block_carousel( $attributes ) {
294
266
  }
295
267
 
296
268
  /**
297
- * Generate autoplay play/pause UI for non-AMP requests.
269
+ * Generate autoplay play/pause UI.
298
270
  *
299
271
  * @return string Autoplay UI markup.
300
272
  */
@@ -306,37 +278,6 @@ function newspack_blocks_carousel_block_autoplay_ui() {
306
278
  );
307
279
  }
308
280
 
309
- /**
310
- * Generate autoplay play/pause UI for AMP requests.
311
- *
312
- * @param int $block_ordinal The ordinal number of the block, used in unique ID.
313
- *
314
- * @return string Autoplay UI markup.
315
- */
316
- function newspack_blocks_carousel_block_autoplay_ui_amp( $block_ordinal = 0 ) {
317
- $block_id = sprintf(
318
- 'wp-block-newspack-carousel__%d',
319
- absint( $block_ordinal )
320
- );
321
- $amp_carousel_id = sprintf(
322
- 'wp-block-newspack-carousel__amp-carousel__%d',
323
- absint( $block_ordinal )
324
- );
325
- $autoplay_pause = sprintf(
326
- '<button aria-label="%s" class="amp-carousel-button-pause amp-carousel-button" on="tap:%s.toggleAutoplay(toggleOn=false),%s.toggleClass(class=wp-block-newspack-blocks-carousel__autoplay-playing,force=false)"></button>',
327
- esc_attr__( 'Pause Slideshow', 'newspack-blocks' ),
328
- esc_attr( $amp_carousel_id ),
329
- esc_attr( $block_id )
330
- );
331
- $autoplay_play = sprintf(
332
- '<button aria-label="%s" class="amp-carousel-button-play amp-carousel-button" on="tap:%s.toggleAutoplay(toggleOn=true),%s.toggleClass(class=wp-block-newspack-blocks-carousel__autoplay-playing,force=true)"></button>',
333
- esc_attr__( 'Play Slideshow', 'newspack-blocks' ),
334
- esc_attr( $amp_carousel_id ),
335
- esc_attr( $block_id )
336
- );
337
- return $autoplay_pause . $autoplay_play;
338
- }
339
-
340
281
  /**
341
282
  * Registers the `newspack-blocks/carousel` block on server.
342
283
  */
@@ -141,8 +141,7 @@
141
141
  @include mixins.visuallyHidden;
142
142
  }
143
143
  }
144
- .swiper-button,
145
- .amp-carousel-button {
144
+ .swiper-button {
146
145
  background-color: rgba( black, 0.5 );
147
146
  background-position: center;
148
147
  background-repeat: no-repeat;
@@ -165,22 +164,17 @@
165
164
  outline: 0;
166
165
  }
167
166
  }
168
- .amp-carousel-button-next,
169
- .amp-carousel-button-prev,
170
167
  .swiper-button-next,
171
168
  .swiper-button-prev {
172
169
  left: 1.5em;
173
170
  display: none;
174
171
  right: auto;
172
+ margin-top: -24px;
175
173
 
176
174
  @include mixins.media( mobile ) {
177
175
  display: block;
178
176
  }
179
177
  }
180
- .swiper-button-next,
181
- .swiper-button-prev {
182
- margin-top: -24px;
183
- }
184
178
  .swiper-button-prev::after,
185
179
  .swiper-rtl .swiper-button-next::after {
186
180
  content: none;
@@ -189,18 +183,14 @@
189
183
  .swiper-rtl .swiper-button-prev::after {
190
184
  content: none;
191
185
  }
192
- .amp-carousel-button-next,
193
186
  .swiper-button-next {
194
187
  background-image: url( "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M5.88%204.12L13.76%2012l-7.88%207.88L8%2022l10-10L8%202z'%20fill='white'/%3E%3Cpath%20fill='none'%20d='M0 0h24v24H0z'/%3E%3C/svg%3E" );
195
188
  left: auto;
196
189
  right: 1.5em;
197
190
  }
198
- .amp-carousel-button-prev,
199
191
  .swiper-button-prev {
200
192
  background-image: url( "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M18%204.12L10.12%2012%2018%2019.88%2015.88%2022l-10-10%2010-10z'%20fill='white'/%3E%3Cpath%20fill='none'%20d='M0 0h24v24H0z'/%3E%3C/svg%3E" );
201
193
  }
202
- .amp-carousel-button-pause,
203
- .amp-carousel-button-play,
204
194
  .swiper-button-pause,
205
195
  .swiper-button-play {
206
196
  background-image: url( "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M6%2019h4V5H6v14zm8-14v14h4V5h-4z'%20fill='white'/%3E%3Cpath%20d='M0%200h24v24H0z'%20fill='none'/%3E%3C/svg%3E" );
@@ -212,27 +202,10 @@
212
202
  transform: none;
213
203
  z-index: 9;
214
204
  }
215
- .amp-carousel-button-play,
216
205
  .swiper-button-play {
217
206
  background-image: url( "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M8%205v14l11-7z'%20fill='white'/%3E%3Cpath%20d='M0 0h24v24H0z'%20fill='none'/%3E%3C/svg%3E" );
218
207
  }
219
208
 
220
- /* AMP carousel container */
221
- .wp-block-newspack-carousel__amp-carousel {
222
- margin: 0 -8px;
223
- }
224
-
225
- /* AMP carousel slide */
226
- .amp-carousel-slide {
227
- position: relative;
228
-
229
- .entry-wrapper,
230
- .post-thumbnail img {
231
- left: 8px;
232
- width: calc( 100% - 16px );
233
- }
234
- }
235
-
236
209
  /* Swiper Slide */
237
210
  .swiper-slide {
238
211
  height: auto;
@@ -287,15 +260,11 @@
287
260
  margin-right: 0.5em;
288
261
  }
289
262
 
290
- &.wp-block-newspack-blocks-carousel__autoplay-playing .amp-carousel-button-pause,
291
263
  &.wp-block-newspack-blocks-carousel__autoplay-playing .swiper-button-pause,
292
- .amp-carousel-button-play,
293
264
  .swiper-button-play {
294
265
  display: block;
295
266
  }
296
- &.wp-block-newspack-blocks-carousel__autoplay-playing .amp-carousel-button-play,
297
267
  &.wp-block-newspack-blocks-carousel__autoplay-playing .swiper-button-play,
298
- .amp-carousel-button-pause,
299
268
  .swiper-button-pause {
300
269
  display: none;
301
270
  }
@@ -314,7 +283,6 @@
314
283
 
315
284
  // If hideControls is enabled, visually hide the UI elements.
316
285
  &.hide-controls {
317
- .amp-pagination,
318
286
  button {
319
287
  display: none;
320
288
  visibility: hidden;
@@ -374,8 +342,7 @@
374
342
  }
375
343
 
376
344
  /* stylelint-disable selector-type-no-unknown */
377
- .wpnbpc,
378
- amp-script .wpnbpc {
345
+ .wpnbpc {
379
346
  .entry-sponsors {
380
347
  align-items: center;
381
348
  display: inline-flex;
@@ -35,6 +35,15 @@
35
35
  },
36
36
  "gradient": {
37
37
  "type": "string"
38
+ },
39
+ "afterSuccessBehavior": {
40
+ "type": "string"
41
+ },
42
+ "afterSuccessButtonLabel": {
43
+ "type": "string"
44
+ },
45
+ "afterSuccessURL": {
46
+ "type": "string"
38
47
  }
39
48
  },
40
49
  "supports": {
@@ -153,6 +153,48 @@ function ProductControl( props ) {
153
153
  );
154
154
  }
155
155
 
156
+ function RedirectAfterSuccess( props ) {
157
+ const { attributes, setAttributes } = props;
158
+ return (
159
+ <>
160
+ <SelectControl
161
+ label={ __( 'Post-Checkout Button', 'newspack-blocks' ) }
162
+ help={ __(
163
+ 'Select whether the user should be presented with a button to navigate after a successful purchase.',
164
+ 'newspack-blocks'
165
+ ) }
166
+ value={ attributes.afterSuccessBehavior }
167
+ options={ [
168
+ { label: __( 'Do not show a button', 'newspack-blocks' ), value: '' },
169
+ { label: __( 'Go to a custom URL', 'newspack-blocks' ), value: 'custom' },
170
+ { label: __( 'Go to the previous page', 'newspack-blocks' ), value: 'referrer' },
171
+ ] }
172
+ onChange={ value => {
173
+ setAttributes( { afterSuccessBehavior: value.toString() } );
174
+ } }
175
+ />
176
+ { attributes.afterSuccessBehavior !== '' && (
177
+ <>
178
+ <TextControl
179
+ label={ __( 'Button Label', 'newspack-blocks' ) }
180
+ placeholder={ __( 'Continue browsing', 'newspack-blocks' ) }
181
+ value={ attributes.afterSuccessButtonLabel || '' }
182
+ onChange={ value => setAttributes( { afterSuccessButtonLabel: value } ) }
183
+ />
184
+ { attributes.afterSuccessBehavior === 'custom' && (
185
+ <TextControl
186
+ label={ __( 'Custom URL', 'newspack-blocks' ) }
187
+ placeholder={ __( 'https://example.com', 'newspack-blocks' ) }
188
+ value={ attributes.afterSuccessURL || '' }
189
+ onChange={ value => setAttributes( { afterSuccessURL: value } ) }
190
+ />
191
+ ) }
192
+ </>
193
+ ) }
194
+ </>
195
+ );
196
+ }
197
+
156
198
  function CheckoutButtonEdit( props ) {
157
199
  const { attributes, setAttributes, className } = props;
158
200
  const { placeholder, style, text, product, price, variation } = attributes;
@@ -291,6 +333,9 @@ function CheckoutButtonEdit( props ) {
291
333
  ) }
292
334
  </ProductControl>
293
335
  </PanelBody>
336
+ <PanelBody title={ __( 'After purchase', 'newspack-blocks' ) }>
337
+ <RedirectAfterSuccess setAttributes={ setAttributes } attributes={ attributes } />
338
+ </PanelBody>
294
339
  { nyp?.isNYP && (
295
340
  <PanelBody title={ __( 'Name Your Price', 'newspack-blocks' ) }>
296
341
  <p>
@@ -57,9 +57,32 @@ export default function save( { attributes, className } ) {
57
57
  />
58
58
  <input type="hidden" name="product_id" value={ product } />
59
59
  <input type="hidden" name="newspack_checkout" value="1" />
60
+
60
61
  { price && <input type="hidden" name="price" value={ price } /> }
61
62
  { variation && <input type="hidden" name="variation_id" value={ variation } /> }
62
63
  { is_variable && <input type="hidden" name="is_variable" value="1" /> }
64
+
65
+ { attributes.afterSuccessBehavior && (
66
+ <input
67
+ type="hidden"
68
+ name="after_success_behavior"
69
+ value={ attributes.afterSuccessBehavior }
70
+ />
71
+ ) }
72
+ { attributes.afterSuccessButtonLabel && (
73
+ <input
74
+ type="hidden"
75
+ name="after_success_button_label"
76
+ value={ attributes.afterSuccessButtonLabel }
77
+ />
78
+ ) }
79
+ { attributes.afterSuccessBehavior && (
80
+ <input
81
+ type="hidden"
82
+ name="after_success_url"
83
+ value={ attributes.afterSuccessURL || '' }
84
+ />
85
+ ) }
63
86
  </form>
64
87
  </div>
65
88
  );
@@ -142,20 +142,13 @@ const Edit = ( { attributes, setAttributes, className }: EditProps ) => {
142
142
  }
143
143
 
144
144
  const isTiered = attributes.manual ? attributes.tiered : settings.tiered;
145
- const canRenderTiersBasedLayout = Boolean(
146
- window.newspack_blocks_data?.can_render_tiers_based_layout
147
- );
148
- const isTierBasedLayoutEnabled =
149
- canRenderTiersBasedLayout && isTiered && attributes.layoutOption === 'tiers';
145
+ const isTierBasedLayoutEnabled = isTiered && attributes.layoutOption === 'tiers';
150
146
 
151
147
  const amounts = attributes.manual ? attributes.amounts : settings.amounts;
152
148
  const availableFrequencies = FREQUENCY_SLUGS.filter( slug =>
153
149
  attributes.manual
154
150
  ? ! attributes.disabledFrequencies[ slug ]
155
151
  : ! settings.disabledFrequencies[ slug ]
156
- ).filter( slug => ( isTierBasedLayoutEnabled ? slug !== 'once' : true ) );
157
- const displayedFrequencies = FREQUENCY_SLUGS.filter( slug =>
158
- isTierBasedLayoutEnabled ? slug !== 'once' : true
159
152
  );
160
153
 
161
154
  // Editor bug – initially, the default style is selected, but the class not applied.
@@ -233,7 +226,7 @@ const Edit = ( { attributes, setAttributes, className }: EditProps ) => {
233
226
  isPressed={ isSelected }
234
227
  onClick={ () => setAttributes( { layoutOption: key } ) }
235
228
  aria-current={ isSelected }
236
- disabled={ key === 'tiers' && ( ! canRenderTiersBasedLayout || ! isTiered ) }
229
+ disabled={ key === 'tiers' && ! isTiered }
237
230
  >
238
231
  { label }
239
232
  </Button>
@@ -278,13 +271,13 @@ const Edit = ( { attributes, setAttributes, className }: EditProps ) => {
278
271
  { attributes.tiered ? (
279
272
  <>
280
273
  <div className="components-frequency-donations">
281
- { displayedFrequencies.map( ( frequency: DonationFrequencySlug ) => {
274
+ { FREQUENCY_SLUGS.map( ( frequency: DonationFrequencySlug ) => {
282
275
  const isFrequencyDisabled = attributes.disabledFrequencies[ frequency ];
283
276
  const disabledDisplayedFrequencyCount = Object.values(
284
- pick( attributes.disabledFrequencies, displayedFrequencies )
277
+ pick( attributes.disabledFrequencies, FREQUENCY_SLUGS )
285
278
  ).filter( Boolean ).length;
286
279
  const isOnlyOneFrequencyActive =
287
- displayedFrequencies.length - disabledDisplayedFrequencyCount === 1;
280
+ FREQUENCY_SLUGS.length - disabledDisplayedFrequencyCount === 1;
288
281
  return (
289
282
  <Fragment key={ frequency }>
290
283
  <CheckboxControl
@@ -90,10 +90,7 @@ abstract class Newspack_Blocks_Donate_Renderer_Base {
90
90
  }
91
91
  }
92
92
 
93
- $is_tiers_based = $configuration['tiered'] && 'tiers' === $attributes['layoutOption'];
94
- if ( ! Newspack_Blocks::can_render_tiers_based_layout() ) {
95
- $is_tiers_based = false;
96
- }
93
+ $is_tiers_based = $configuration['tiered'] && 'tiers' === $attributes['layoutOption'];
97
94
  $configuration['is_tier_based_layout'] = $is_tiers_based;
98
95
 
99
96
  $frequencies = [
@@ -105,9 +102,6 @@ abstract class Newspack_Blocks_Donate_Renderer_Base {
105
102
  if ( $configuration['disabledFrequencies'][ $frequency_slug ] ) {
106
103
  unset( $frequencies[ $frequency_slug ] );
107
104
  }
108
- if ( $is_tiers_based && 'once' === $frequency_slug ) {
109
- unset( $frequencies[ $frequency_slug ] );
110
- }
111
105
  }
112
106
  $configuration['frequencies'] = $frequencies;
113
107
 
@@ -94,7 +94,7 @@ class Newspack_Blocks_Donate_Renderer_Frequency_Based extends Newspack_Blocks_Do
94
94
  ob_start();
95
95
 
96
96
  /**
97
- * For AMP-compatibility, the donation forms are implemented as pure HTML forms (no JS).
97
+ * For AMP-compatibility, the donation forms were implemented as pure HTML forms (no JS).
98
98
  * Each frequency and tier option is a radio input, styled to look like a button.
99
99
  * As the radio inputs are checked/unchecked, fields are hidden/displayed using only CSS.
100
100
  */
@@ -146,7 +146,6 @@ class Newspack_Blocks_Donate_Renderer {
146
146
 
147
147
  Newspack_Blocks::enqueue_view_assets( 'donate' );
148
148
  wp_script_add_data( 'newspack-blocks-donate', 'async', true );
149
- wp_script_add_data( 'newspack-blocks-donate', 'amp-plus', true );
150
149
 
151
150
  if ( true === $attributes['useModalCheckout'] && ! $configuration['is_rendering_stripe_payment_form'] ) {
152
151
  \Newspack_Blocks\Modal_Checkout::enqueue_modal();
@@ -36,7 +36,7 @@ const createDOM = settings => {
36
36
  <div class="stripe-payment__messages"></div>
37
37
  <button type="submit">Donate</button>
38
38
  </div>
39
- <input name="cid" type="hidden" value="amp-123" />
39
+ <input name="cid" type="hidden" value="cid-123" />
40
40
  </form>
41
41
  `;
42
42
  document.body.appendChild( parentElement );
@@ -122,8 +122,7 @@ describe( 'Streamlined Donate block processing', () => {
122
122
  full_name: 'Bax',
123
123
  frequency: 'month',
124
124
  newsletter_opt_in: false,
125
- clientId: 'amp-123',
126
- origin: null,
125
+ clientId: 'cid-123',
127
126
  additional_fields: [],
128
127
  },
129
128
  },
@@ -116,13 +116,6 @@ export const processStreamlinedElements = ( parentElement = document ) =>
116
116
  }
117
117
  }
118
118
  const formValues = utils.getDonationFormValues( formElement );
119
- const promptOrigin = formElement.closest( 'amp-layout.newspack-popup' );
120
-
121
- // If the donation originated from a Campaigns prompt, append the prompt ID to the event label.
122
- const origin =
123
- promptOrigin && promptOrigin.hasAttribute( 'amp-access' )
124
- ? promptOrigin.getAttribute( 'amp-access' )
125
- : null;
126
119
 
127
120
  const additionalFields: { name: string; value: string }[] =
128
121
  getAdditionalFieldsValues( formElement );
@@ -139,7 +132,6 @@ export const processStreamlinedElements = ( parentElement = document ) =>
139
132
  newspack_popup_id: formValues.newspack_popup_id,
140
133
  _wp_http_referer: formValues._wp_http_referer,
141
134
  clientId: formValues.cid,
142
- origin,
143
135
  additional_fields: additionalFields,
144
136
  ...requestPayloadOverrides,
145
137
  };
@@ -120,7 +120,6 @@ export const getDonationFormValues = ( formElement: HTMLFormElement ): DonationF
120
120
  typeof formValues.cid === 'string' &&
121
121
  formValues.cid.indexOf( 'CLIENT_ID' ) === 0
122
122
  ) {
123
- // In non-AMP environment, the value will not be dynamically substituted by AMP runtime.
124
123
  formValues.cid = getCookies()[ 'newspack-cid' ];
125
124
  }
126
125
  return formValues;
@@ -191,6 +191,10 @@
191
191
  "type": "array",
192
192
  "default": [ "publish" ],
193
193
  "items": { "type": "string" }
194
+ },
195
+ "deduplicate": {
196
+ "type": "boolean",
197
+ "default": true
194
198
  }
195
199
  }
196
200
  }
@@ -161,9 +161,6 @@ class WP_REST_Newspack_Articles_Controller extends WP_REST_Controller {
161
161
  ]
162
162
  );
163
163
 
164
- if ( $request->get_param( 'amp' ) ) {
165
- $html = $this->generate_amp_partial( $html );
166
- }
167
164
  $items[]['html'] = $html;
168
165
  $ids[] = get_the_ID();
169
166
  }
@@ -184,8 +181,6 @@ class WP_REST_Newspack_Articles_Controller extends WP_REST_Controller {
184
181
  [
185
182
  'exclude_ids' => false,
186
183
  'page' => $next_page,
187
- 'amp' => $request->get_param( 'amp' ),
188
-
189
184
  ]
190
185
  ),
191
186
  rest_url( '/newspack-blocks/v1/articles' )
@@ -228,27 +223,4 @@ class WP_REST_Newspack_Articles_Controller extends WP_REST_Controller {
228
223
  }
229
224
  return $this->attribute_schema;
230
225
  }
231
-
232
- /**
233
- * Use AMP Plugin functions to render markup as valid AMP.
234
- *
235
- * @param string $html Markup to convert to AMP.
236
- * @return string
237
- */
238
- public function generate_amp_partial( $html ) {
239
- $dom = AMP_DOM_Utils::get_dom_from_content( $html );
240
-
241
- AMP_Content_Sanitizer::sanitize_document(
242
- $dom,
243
- amp_get_content_sanitizers(),
244
- [
245
- 'use_document_element' => false,
246
- ]
247
- );
248
- $xpath = new DOMXPath( $dom );
249
- foreach ( iterator_to_array( $xpath->query( '//noscript | //comment()' ) ) as $node ) {
250
- $node->parentNode->removeChild( $node ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
251
- }
252
- return AMP_DOM_Utils::get_content_from_dom( $dom );
253
- }
254
226
  }
@@ -434,6 +434,16 @@ class Edit extends Component {
434
434
  />
435
435
  )
436
436
  ) }
437
+ <ToggleControl
438
+ label={ __( 'Use deduplication logic', 'newspack-blocks' ) }
439
+ help={ __(
440
+ 'If unchecked, this block will be excluded from the deduplication logic and may show duplicate posts.',
441
+ 'newspack-blocks'
442
+ ) }
443
+ checked={ attributes.deduplicate }
444
+ onChange={ () => setAttributes( { deduplicate: ! attributes.deduplicate } ) }
445
+ className="newspack-blocks-deduplication-toggle"
446
+ />
437
447
  </PanelBody>
438
448
  <PanelBody title={ __( 'Featured Image Settings', 'newspack-blocks' ) }>
439
449
  <PanelRow>
@@ -110,6 +110,19 @@ function* getPostsForBlock( block ) {
110
110
  return postsIds;
111
111
  }
112
112
 
113
+ /**
114
+ * Whether a block uses deduplication.
115
+ *
116
+ * @param {string} clientId
117
+ *
118
+ * @return {boolean} whether the block uses deduplication
119
+ */
120
+ function shouldDeduplicate( clientId ) {
121
+ const { getBlock } = select( 'core/block-editor' );
122
+ const block = getBlock( clientId );
123
+ return block?.attributes?.deduplicate;
124
+ }
125
+
113
126
  const createFetchPostsSaga = blockNames => {
114
127
  /**
115
128
  * "worker" Saga: will be fired on REFLOW actions
@@ -138,8 +151,8 @@ const createFetchPostsSaga = blockNames => {
138
151
  const blockQueries = getBlockQueries( blocks, blockNames );
139
152
 
140
153
  // Use requested specific posts ids as the starting state of exclusion list.
141
- const specificPostsId = blockQueries.reduce( ( acc, { postsQuery } ) => {
142
- if ( postsQuery.include ) {
154
+ const specificPostsId = blockQueries.reduce( ( acc, { clientId, postsQuery } ) => {
155
+ if ( shouldDeduplicate( clientId ) && postsQuery.include ) {
143
156
  acc = [ ...acc, ...postsQuery.include ];
144
157
  }
145
158
  return acc;
@@ -148,14 +161,19 @@ const createFetchPostsSaga = blockNames => {
148
161
  let exclude = sanitizePostList( [ ...specificPostsId, getCurrentPostId() ] );
149
162
  while ( blockQueries.length ) {
150
163
  const nextBlock = blockQueries.shift();
151
- nextBlock.postsQuery.exclude = exclude;
164
+ const deduplicate = shouldDeduplicate( nextBlock.clientId );
165
+ if ( deduplicate ) {
166
+ nextBlock.postsQuery.exclude = exclude;
167
+ }
152
168
  let fetchedPostIds = [];
153
169
  try {
154
170
  fetchedPostIds = yield call( getPostsForBlock, nextBlock );
155
171
  } catch ( e ) {
156
172
  yield put( { type: 'UPDATE_BLOCK_ERROR', clientId: nextBlock.clientId, error: e.message } );
157
173
  }
158
- exclude = [ ...exclude, ...fetchedPostIds ];
174
+ if ( deduplicate ) {
175
+ exclude = [ ...exclude, ...fetchedPostIds ];
176
+ }
159
177
  }
160
178
 
161
179
  yield put( { type: 'ENABLE_UI' } );
@@ -44,10 +44,6 @@ call_user_func(
44
44
  global $newspack_blocks_hpb_rendering_context;
45
45
  $newspack_blocks_hpb_rendering_context = [ 'attrs' => $attributes ];
46
46
 
47
- // If the image position is behind, pass the object-fit setting to maintain styles with AMP.
48
- if ( 'behind' === $attributes['mediaPosition'] ) {
49
- $thumbnail_args['object-fit'] = 'cover';
50
- }
51
47
  // Disable lazy loading by using an arbitraty `loading` attribute other than `lazy`.
52
48
  // Empty string or `false` would still result in `lazy`.
53
49
  if ( $attributes['disableImageLazyLoad'] ) {
@@ -18,10 +18,9 @@ call_user_func(
18
18
 
19
19
  Newspack_Blocks::filter_excerpt( $attributes );
20
20
 
21
- $enable_post_duplication = apply_filters( 'newspack_blocks_homepage_enable_duplication', false );
22
21
  while ( $article_query->have_posts() ) {
23
22
  $article_query->the_post();
24
- if ( ! $enable_post_duplication ) {
23
+ if ( Newspack_Blocks::should_deduplicate_block( $attributes ) ) {
25
24
  $newspack_blocks_post_id[ get_the_ID() ] = true;
26
25
  }
27
26
  echo Newspack_Blocks::template_inc( __DIR__ . '/article.php', array( 'attributes' => $attributes ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped