@automattic/newspack-blocks 4.30.0-alpha.1 → 4.30.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.
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +5 -12
- package/dist/modal.asset.php +1 -1
- package/dist/modal.js +1 -1
- package/includes/class-modal-checkout.php +349 -3
- package/newspack-blocks.php +2 -2
- package/package.json +1 -1
- package/src/blocks/author-list/class-wp-rest-newspack-author-list-controller.php +12 -1
- package/src/blocks/author-list/view.php +1 -1
- package/src/blocks/author-profile/class-wp-rest-newspack-authors-controller.php +44 -0
- package/src/blocks/checkout-button/view.php +4 -0
- package/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-base.php +10 -0
- package/src/blocks/iframe/class-wp-rest-newspack-iframe-controller.php +138 -7
- package/src/modal-checkout/checkout-button-trigger.js +1 -0
- package/src/modal-checkout/modal.js +2 -0
- package/src/modal-checkout/templates/thankyou.php +17 -1
- package/vendor/composer/installed.php +2 -2
package/AGENTS.md
CHANGED
|
@@ -207,7 +207,7 @@ REST controllers are instantiated via wrapper functions hooked to `rest_api_init
|
|
|
207
207
|
- **Functions**: `newspack_blocks_*` prefix (e.g., `newspack_blocks_render_block_homepage_articles`)
|
|
208
208
|
- **Classes**: `Newspack_Blocks*` or `WP_REST_Newspack_*` for REST controllers
|
|
209
209
|
- **Constants**: `NEWSPACK_BLOCKS__*` (double underscore)
|
|
210
|
-
- **Hooks**: `newspack_blocks_*` prefix; modal-specific use `
|
|
210
|
+
- **Hooks**: `newspack_blocks_*` prefix; modal-specific use `newspack_blocks_modal_checkout_*`
|
|
211
211
|
- **Text domain**: `newspack-blocks`
|
|
212
212
|
- **Block names**: Short slugs in `block.json`, namespaced as `newspack-blocks/<slug>` in PHP/JS
|
|
213
213
|
|
|
@@ -268,7 +268,7 @@ Partial adoption. Some blocks use `.ts`/`.tsx` (homepage-articles `edit.tsx`, do
|
|
|
268
268
|
The plugin exposes many hooks. Rather than listing them all here (they change over time), use `grep -r 'apply_filters\|do_action' src/ includes/` to find current hooks. The key hook prefixes are:
|
|
269
269
|
|
|
270
270
|
- **`newspack_blocks_*`**: General block hooks (query building, deduplication, registration, author data)
|
|
271
|
-
- **`
|
|
271
|
+
- **`newspack_blocks_modal_checkout_*`**: Modal checkout specific hooks (labels, gateways, billing fields, HTML output)
|
|
272
272
|
|
|
273
273
|
## CI/CD
|
|
274
274
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,23 +1,16 @@
|
|
|
1
|
-
# @automattic/newspack-blocks [4.30.0-alpha.
|
|
1
|
+
# @automattic/newspack-blocks [4.30.0-alpha.2](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.30.0-alpha.1...newspack-blocks@4.30.0-alpha.2) (2026-08-07)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
* **blocks:**
|
|
7
|
-
* **newspack-blocks:** skip article-block image crops on wpcom uploads ([#576](https://github.com/Automattic/newspack-workspace/issues/576)) ([451272c](https://github.com/Automattic/newspack-workspace/commit/451272c3989cb3ed5f25daaa1e647e1b5dcbf453))
|
|
6
|
+
* **blocks:** input validation hardening for author, iframe and checkout surfaces ([#782](https://github.com/Automattic/newspack-workspace/issues/782)) ([20230c1](https://github.com/Automattic/newspack-workspace/commit/20230c12471e327e8cbdf6d3ffe7d9823a28b00b))
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
### Features
|
|
11
|
-
|
|
12
|
-
* **performance:** defer reader-facing JS assets (NPPM-3037) ([#722](https://github.com/Automattic/newspack-workspace/issues/722)) ([2855574](https://github.com/Automattic/newspack-workspace/commit/2855574099c4f416a984cda8565b269e5a9f0fb0))
|
|
8
|
+
## @automattic/newspack-blocks [4.29.2](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.29.1...newspack-blocks@4.29.2) (2026-08-07)
|
|
13
9
|
|
|
14
10
|
|
|
15
|
-
###
|
|
11
|
+
### Bug Fixes
|
|
16
12
|
|
|
17
|
-
* **
|
|
18
|
-
* **newspack-icons:** upgraded to 1.1.1-alpha.1
|
|
19
|
-
* **newspack-components:** upgraded to 4.6.1-alpha.1
|
|
20
|
-
* **newspack-scripts:** upgraded to 5.11.0-alpha.1
|
|
13
|
+
* **blocks:** input validation hardening for author, iframe and checkout surfaces ([#782](https://github.com/Automattic/newspack-workspace/issues/782)) ([20230c1](https://github.com/Automattic/newspack-workspace/commit/20230c12471e327e8cbdf6d3ffe7d9823a28b00b))
|
|
21
14
|
|
|
22
15
|
## @automattic/newspack-blocks [4.29.1](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.29.0...newspack-blocks@4.29.1) (2026-08-04)
|
|
23
16
|
|
package/dist/modal.asset.php
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<?php return array('dependencies' => array(), 'version' => '
|
|
1
|
+
<?php return array('dependencies' => array(), 'version' => '1c1712bf74d293308889');
|
package/dist/modal.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{"use strict";function e(e,t=!1){const n=e.querySelectorAll('button, [href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])');if(0===n.length)return!1;const o=n?.[0];let c;o.focus(),t?(document.addEventListener("keydown",function(e){if(("Tab"===e.key||9===e.keyCode)&&e.shiftKey&&document.activeElement===o){const n=t.contentWindow.document,o=n.getElementById("checkout-after-success");c=null!==o?o:n.getElementById("checkout_cancel"),c.focus(),e.preventDefault()}}),document.getElementById("newspack-a11y-last-element").addEventListener("focus",()=>{o.focus()})):(c=n[n.length-1],document.addEventListener("keydown",function(e){("Tab"===e.key||9===e.keyCode)&&(e.shiftKey?document.activeElement===o&&(c.focus(),e.preventDefault()):document.activeElement===c&&(o.focus(),e.preventDefault()))}))}const t=(e,t={})=>({...t,action:e}),n=["action","action_type","amount","currency","product_id","product_type","variation_id","variation_ids","is_variable","is_grouped","child_ids","price_summary","newspack_popup_id","prompt_title","gate_post_id","recurrence","referrer"],o=(e,t="np_modal_checkout_interaction")=>{if("function"==typeof window.gtag&&e){const o={};for(const t of n)e[t]&&("boolean"==typeof e[t]?e[t]=e[t]?"yes":"no":"true"===e[t]?e[t]="yes":"false"===e[t]&&(e[t]="no"),o[t]=e[t].toString());window.gtag("event",t,o)}},c=e=>{if("function"!=typeof window.gtag)return;let n="opened";const{amount:c="",is_variable:a="",is_grouped:r="",price:i="",variation_id:s=""}=e,l={...e};(c||i)&&(l.amount=c||i),a&&!s&&(n="opened_variations"),r&&(n="opened_grouped");const u=t(n,l);o(u)};function a(e,t=null){const n=document.createElement("input");return n.type="hidden",n.name=e,t&&(n.value=t),n}function r(e){e.requestSubmit(e.querySelector('button[type="submit"]'))}function i(e){const t="string"==typeof e?document.getElementById(e):e;if(!t)return console.warn("No container found for checkout data"),{};let n={};if("FORM"===t.tagName){const e=new FormData(t);n=Object.fromEntries(e)}const o=t.dataset.checkout;if(!o)return console.warn("No checkout data found"),n;try{n={...n,...JSON.parse(o)}}catch(e){console.warn("Error parsing checkout data")}const c=new URL(t.ownerDocument.defaultView.location.href);return c.searchParams.get("action_type")&&(n.action_type=c.searchParams.get("action_type")),n}const s=e=>{if("function"!=typeof window.gtag)return;e=e||i("newspack_modal_checkout");const n=t("dismissed",e);o(n)};function l(e){const t=e&&e.dataset?e.dataset.checkout:null;if(!t)return null;try{return JSON.parse(t)}catch(e){return null}}function u(e,t,n=null){const o=e.querySelectorAll(".wp-block-newspack-blocks-checkout-button"),c=null!=n&&""!==String(n);let a=null;return o.forEach(e=>{if(a)return;const o=e.querySelector("form"),r=l(o);r&&String(r.product_id)===String(t)&&(c&&String(r.variation_id)!==String(n)||(a=o))}),a}const d=["after_success_behavior","after_success_url","after_success_button_label","gate_post_id","newspack_popup_id","prompt_title","coupon"];const p={gate:"gate_post_id",prompt:"newspack_popup_id"};const m=newspackBlocksModal.newspack_class_prefix,h="newspack_modal_checkout_iframe",_="newspack_modal_checkout_container",f=`${m}__modal`,w="newspack-blocks__modal-variation",y=`${m}__processing-payment-text`,k=newspackBlocksModal.processing_payment_messages;let v=!1,g=newspackBlocksModal.labels.checkout_modal_title,b=null;const S=e=>{e.overlayId&&window.newspackReaderActivation?.overlays&&window.newspackReaderActivation?.overlays.remove(e.overlayId),e.setAttribute("data-state","closed"),document.body.style.overflow="auto"};window.onpageshow=e=>{e.persisted&&(document.querySelectorAll(".modal-processing").forEach(e=>e.classList.remove("modal-processing")),document.querySelectorAll(".non-modal-checkout-loading").forEach(e=>e.classList.remove("non-modal-checkout-loading")),document.querySelectorAll(`.${f}-container`).forEach(e=>S(e)))};const E=new CustomEvent("checkout-closed");var q;window.newspackRAS=window.newspackRAS||[],q=()=>{const t=document.querySelector("#newspack_modal_checkout");if(!t)return;const n=t.querySelector(`.${f}__content`),o=a("modal_checkout","1"),q=n.querySelector(`.${m}__spinner`);let A=document.querySelector(".newspack-reader__account-link")?.[0];const L="600px",$=document.createElement("iframe");$.name=h,$.style.height=L,$.style.visibility="hidden";const R=e=>{P.observe(e),"none"!==q.style.display&&(q.style.display="none"),"visible"!==$.style.visibility&&($.style.visibility="visible"),$._ready=!0};!function(e,t,n){e._ready=!1,e._readyTimer&&clearTimeout(e._readyTimer);let o=!1,c="";function a(){o||(o=!0,clearTimeout(e._readyTimer),t.call(this))}function r(){"complete"===this.readyState&&a.call(this)}function i(){e._ready=!1,n&&n(),e._readyTimer&&clearTimeout(e._readyTimer);const t=e.contentDocument||e.contentWindow?.document;t&&(t.removeEventListener("DOMContentLoaded",a),t.removeEventListener("readystatechange",r))}function s(){if(e._ready)return void i();const t=e.contentDocument||e.contentWindow?.document;t&&0!==t.URL.indexOf("about:")?"complete"===t?.readyState?a.call(t):(t.addEventListener("DOMContentLoaded",a),t.addEventListener("readystatechange",r)):e._readyTimer=setTimeout(s,10)}e._observer||(e._observer=new MutationObserver(e=>{e.forEach(e=>{"attributes"===e.type&&"src"===e.attributeName&&(o=!1,i(),s())})}),e._observer.observe(e,{attributes:!0})),e._locationObserver||(e._locationObserver=setInterval(function(){const t=e.contentDocument||e.contentWindow?.document;t&&t.URL!==c&&(c=t.URL,o=!1,i(),s())},50)),s()}($,function(){const n=$.contentWindow?.location;if(window.newspackReaderActivation&&n?.href?.includes("order-received")){const e=window.newspackReaderActivation,t=new Proxy(new URLSearchParams(n.search),{get:(e,t)=>e.get(t)});t.email&&(e.setReaderEmail(t.email),e.setAuthenticated(!0))}const o=$?.contentDocument?.querySelector(`#${_}`);if(!o)return;const c=i(o.querySelector("#modal-checkout-product-details")),a=document.createElement("p");a.classList.add(y);let r=[];const s=()=>{r.forEach(e=>clearTimeout(e)),r=[]},l=()=>{q.style.display="none",s(),q.querySelectorAll(`.${y}`).forEach(e=>e.remove())};!function(e,t){e.checkoutCancel?t():e.addEventListener("checkout-cancel",t)}(o,()=>{x()}),function(e,t){e.checkoutPlaceOrderStart?t():e.addEventListener("checkout-place-order-start",t)}(o,e=>{q.querySelectorAll(`.${y}`).forEach(e=>e.remove()),q.style.display="flex",s(),a.textContent=k[0]?.text??"",k.slice(1).forEach(({text:t,delay:n})=>{const o=setTimeout(()=>{e.target.dispatchEvent(new CustomEvent("checkout-place-order-processing")),a.textContent=t},n);r.push(o)}),q.appendChild(a)}),function(e,t){e.checkoutPlaceOrderError?t():e.addEventListener("checkout-place-order-error",t)}(o,l),function(e,t){e.checkoutReady?t():e.addEventListener("checkout-ready",t)}(o,()=>{if(t.initialErrors){const e=document.createElement("div");e.classList.add("woocommerce-error"),e.textContent=t.initialErrors,o.prepend(e),delete t.initialErrors}I(),N(g),$.contentWindow?.newspackBlocksModalCheckout?.checkout_nonce&&(t.checkout_nonce=$.contentWindow.newspackBlocksModalCheckout.checkout_nonce),R(o)}),function(e,t){e.checkoutComplete?t():e.addEventListener("checkout-complete",t)}(o,()=>{window.newspackRAS.push(["checkout_completed",c]),window?.newspackReaderActivation?.refreshNewslettersSignupModal&&window?.newspackReaderActivation?.getReader()?.email&&window.newspackReaderActivation.refreshNewslettersSignupModal(window.newspackReaderActivation.getReader().email),I("small"),N(newspackBlocksModal.labels.thankyou_modal_title),R(o),e(t.querySelector(`.${f}`)),l()}),function(e,t){e.checkoutPlaceOrderCriticalError?t():e.addEventListener("checkout-place-order-critical-error",t)}(o,()=>{var e;(e=b)&&(x(),q.style.display="none",t.initialErrors=newspackBlocksModal.labels.critical_error,e.requestSubmit(e.querySelector('button[type="submit"]')),l())})},()=>{q.style.display="flex"});const M=e=>new Promise((t,n)=>{const o=new URLSearchParams(e);o.append("action","modal_checkout_request"),fetch(newspackBlocksModal.ajax_url+"?"+o.toString()).then(e=>{e.ok||n(e),e.json().then(e=>{t(e.url)}).catch(n)}).catch(n)}),C=()=>"undefined"!=typeof newspack_ras_config&&!newspack_ras_config?.is_logged_in&&!window?.newspackReaderActivation?.getReader?.()?.authenticated&&newspackBlocksModal?.is_registration_required&&window?.newspackReaderActivation?.openAuthModal,B=t=>{const n=!newspackBlocksModal.has_unsupported_payment_gateway;n||t.preventDefault();const o=t.target;o.classList.add("modal-processing"),function(e){if(!e||"function"!=typeof e.querySelector)return!1;if(e.closest(".newspack-content-gate__gate"))return!1;const t=function(){let e,t;try{e=window.sessionStorage.getItem("newspack_cta_attribution")}catch(e){return null}if(!e)return null;try{t=JSON.parse(e)}catch(e){return null}return t&&1===t.v&&p[t.type]&&t.id?"number"!=typeof t.ts||Date.now()-t.ts>18e5?null:t:null}();if(!t)return!1;const n=p[t.type];if(e.querySelector(`input[name="${n}"]`))return!1;const o=document.createElement("input");o.type="hidden",o.name=n,o.value=t.id,e.prepend(o)}(o);const u=i(o);if(u.newspack_donate){const e=u.donation_frequency,t=[...o.querySelectorAll(`.donation-tier__${e}, .donation-frequency__${e}`)],n=u.donation_tier_index;let c,a;n?(c=t[n],a=u[`donation_value_${e}`]):(c=t[0],u[`donation_value_${e}_untiered`]?a=u[`donation_value_${e}_untiered`]:(a=u[`donation_value_${e}`],"other"===a&&(a=u[`donation_value_${e}_other`])));const r=i(c);for(const e in r)u[e]=r[e];u.amount=a,u.price_summary=u.summary_template.replace("{{PRICE}}",function(e,t="USD"){return parseFloat(e).toLocaleString(document.documentElement.lang,{style:"currency",currency:t,currencyDisplay:"narrowSymbol"})}(u.amount,u.currency))}u&&Object.keys(u).forEach(e=>{0===o.querySelectorAll('input[name="'+e+'"]').length&&o.prepend(a(e,u[e]))}),u.variation_id||(A=t.submitter);const m=document.querySelectorAll(`.${w}`);if(m.forEach(e=>{(C()||n)&&S(e)}),u.is_grouped||u.is_variable&&!u.variation_id){const n=[...m].find(e=>e.dataset.productId===u.product_id);if(n)return n.querySelectorAll(`form[target="${h}"]`).forEach(e=>{!function(e,t,n=d){if(!e||!t)return;const o=e.ownerDocument;n.forEach(n=>{e.querySelectorAll(`input[name="${n}"]`).forEach(e=>e.remove());const c=t[n],a=null==c?"":String(c);if(!a)return;const r=o.createElement("input");r.type="hidden",r.name=n,r.value=a,e.prepend(r)})}(e,u);const t=l(e);t&&Object.keys(t).forEach(n=>{0===e.querySelectorAll('input[name="'+n+'"]').length&&e.prepend(a(n,t[n]))})}),t.preventDefault(),o.classList.remove("modal-processing"),T(n),e(n,!1),v||c(u),void document.getElementById("newspack_modal_checkout").setAttribute("data-checkout",JSON.stringify(u))}if(n||C()){if(o.classList.remove("modal-processing"),v||c(u),v=!0,C()){t.preventDefault();const e=M(u);e.then(e=>{window.newspackReaderActivation?.setPendingCheckout?.(e)}),window.newspackReaderActivation.openAuthModal({title:newspackBlocksModal.labels.auth_modal_title,onSuccess:(t,o)=>{e.then(e=>{o?.registered&&n&&(e+=`&${newspackBlocksModal.checkout_registration_flag}=1`),n?r(D(e)):M(u).then(window.location.href=e)}).catch(e=>{console.warn("Unable to generate cart:",e),x()})},onError:()=>{x()},onDismiss:()=>{s(u),v=!1,document.getElementById("newspack_modal_checkout").removeAttribute("data-checkout")},skipSuccess:!0,skipNewslettersSignup:!0,labels:{signin:{title:newspackBlocksModal.labels.signin_modal_title},register:{title:newspackBlocksModal.labels.register_modal_title}},trigger:t.submitter,closeOnSuccess:n})}else O(),document.getElementById("newspack_modal_checkout").setAttribute("data-checkout",JSON.stringify(u));b=o}else M(u).then(e=>{window.location.href=e}),(!u.is_variable||u.variation_id)&&o.querySelectorAll("button[type=submit]:focus").forEach(e=>{e.classList.add("non-modal-checkout-loading");const t=e.innerHTML;e.innerHTML="<span>"+t+"</span>"})},D=e=>{const t=document.createElement("form");t.method="POST",t.action=e,t.target=h,t.style.display="none";const n=document.createElement("button");return n.setAttribute("type","submit"),t.appendChild(n),document.body.appendChild(t),t.addEventListener("submit",B),t},P=new ResizeObserver(e=>{if(!e||!e.length)return;if(!$.contentDocument)return;const o=e[0].contentRect;if(o){const e=.01*Math.max(document.documentElement.clientHeight,window.innerHeight||0)*90-(t.querySelector(`.${f}__header`)?.offsetHeight||0),c=o.top+o.bottom,a=Math.min(c,e);if(0===a)return void($.style.visibility="hidden");n.style.height=a+"px",$.style.height=a+"px"}}),x=()=>{const e=$?.contentDocument?.querySelector(`#${_}`),o=e?.querySelector('input[name="after_success_url"]'),c=e?.querySelector('input[name="after_success_behavior"]'),a=document?.querySelector(".newspack-newsletters-signup-modal"),r=i(e?.querySelector("#modal-checkout-product-details"));e?.checkoutComplete||(async()=>{const e=new FormData;newspackBlocksModal.has_unsupported_payment_gateway||e.append("modal_checkout","1"),e.append("action","abandon_modal_checkout"),e.append("_wpnonce",t.checkout_nonce),t.checkout_nonce=null;try{await fetch(newspackBlocksModal.ajax_url,{method:"POST",body:e})}catch(e){console.warn("Unable to empty cart:",e)}})();const l=!($.contentDocument&&o&&c&&e?.checkoutComplete);if((l||a)&&(q.style.display="flex",$&&n.contains($)&&($._ready=!1,$.src="about:blank",$.style.height=L,$.style.visibility="hidden",n.style.height=L,n.removeChild($)),P&&P.disconnect(),document.querySelectorAll(`.${f}-container`).forEach(e=>S(e)),A&&A.focus(),document.dispatchEvent(E)),e?.checkoutComplete){const e=()=>{if(o&&c){const e=o.getAttribute("value"),t=c.getAttribute("value");if("custom"===t)window.location.href=e;else if("referrer"===t){const e=document.referrer;let t=!1;if(e)try{new URL(e).origin===window.location.origin&&(window.location.href=e,t=!0)}catch(e){}t||window.history.back()}}window?.newspackReaderActivation?.setPendingCheckout?.(),v=!1};"subscription_switch"!==r.action_type&&window?.newspackReaderActivation?.openNewslettersSignupModal?window.newspackReaderActivation.openNewslettersSignupModal({onSuccess:e,onError:e,closeOnSuccess:l}):e(),l&&(g=newspackBlocksModal.labels.checkout_modal_title,I(),N(g))}else window?.newspackReaderActivation?.setPendingCheckout?.(),s(),v=!1,document.getElementById("newspack_modal_checkout").removeAttribute("data-checkout");document.removeEventListener("keydown",U)},O=o=>{o&&($.src=o),q.style.display="flex",T(t),n.appendChild($),t.addEventListener("click",e=>{e.target===t&&x()}),e(t,$),document.addEventListener("keydown",U)},T=e=>{window.newspackReaderActivation?.overlays&&(e.overlayId=window.newspackReaderActivation?.overlays.add()),e.setAttribute("data-state","open"),document.body.style.overflow="hidden"},N=e=>{const n=t.querySelector(`.${f}__header h2`);n&&(n.innerText=e)},I=(e="default")=>{const n=t.querySelector(`.${f}`);n&&("small"===e?n.classList.add(`${f}--small`):n.classList.remove(`${f}--small`))};t.querySelectorAll(`.${f}__close`).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault(),x()})}),document.querySelectorAll(".newspack-blocks__modal-variation").forEach(e=>{e.addEventListener("click",t=>{t.target===e&&x()}),e.querySelectorAll(`.${f}__close`).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault(),x()})})});const U=e=>{"Escape"===e.key&&x()};document.querySelectorAll(".wpbnbd.wpbnbd--platform-wc, .wp-block-newspack-blocks-checkout-button, .newspack-blocks__modal-variation").forEach(e=>{e.querySelectorAll("form").forEach(e=>{newspackBlocksModal.has_unsupported_payment_gateway||e.prepend(o.cloneNode()),e.target=h,e.addEventListener("submit",B)})}),(()=>{const e=new URLSearchParams(window.location.search);if(!e.has("checkout"))return;let t=!0;const n=e.get("type");if("donate"===n){const t=e.get("layout"),n=e.get("frequency"),o=e.get("amount"),c=e.get("other");t&&n&&o&&((e,t,n,o=null)=>{let c;document.querySelectorAll(".wpbnbd.wpbnbd--platform-wc form").forEach(a=>{const r=a.querySelector(`input[name="donation_frequency"][value="${t}"]`);if(r)if("tiered"===e){const e=document.querySelector(`button[data-frequency-slug="${t}"]`);if(!e)return;e.click();const o=a.querySelector(`button[type="submit"][name="donation_value_${t}"][value="${n}"]`);if(!o)return;o.click()}else{const i="untiered"===e?a.querySelector(`input[name="donation_value_${t}_untiered"]`):a.querySelector(`input[name="donation_value_${t}"][value="${n}"]`);if(r&&i){if(r.checked=!0,"untiered"===e)i.value=n;else if("other"===n){i.click();const e=a.querySelector(`input[name="donation_value_${t}_other"]`);e&&o&&(e.value=o)}else i.checked=!0;c=a}}}),c&&r(c)})(t,n,o,c)}else if("checkout_button"===n){const n=e.get("product_id"),o=e.get("variation_id");n?t=((e,t=null)=>{const n=function(e,t,n,o={}){const{allowProductOnlyFallback:c=!1}=o;if(null==n||""===String(n))return u(e,t,null);const a=u(e,t,n);if(a)return a;const r=function(e,t,n,o={}){const{variationModalClassPrefix:c,iframeName:a}=o,r=[...e.querySelectorAll(`.${c}`)].find(e=>String(e.dataset.productId)===String(t));if(!r)return null;const i=r.querySelectorAll("form"),s=a?[...i].find(e=>e.getAttribute("target")===a):i[0];if(!s)return null;const l=[...s.querySelectorAll('input[type="radio"][name="product_id"]')].find(e=>String(e.value)===String(n));return l?(l.checked=!0,s):null}(e,t,n,o);return r?(function(e,t,n=d){if(!e||!t)return;const o=t.ownerDocument,c=new FormData(e);n.forEach(e=>{if(t.querySelector(`input[name="${e}"]`))return;const n=c.getAll(e).filter(e=>"string"==typeof e&&e);if(!n.length)return;const a=o.createElement("input");a.type="hidden",a.name=e,a.value=n[n.length-1],t.prepend(a)})}(function(e,t){const n=e.querySelectorAll(".wp-block-newspack-blocks-checkout-button");let o=null,c=null;return n.forEach(e=>{if(c)return;const n=e.querySelector("form"),a=l(n);a&&String(a.product_id)===String(t)&&(a.variation_id?o=o||n:c=n)}),c||o}(e,t),r),r):c?u(e,t,null):null}(document,e,t,{variationModalClassPrefix:w,iframeName:h});if(n)return r(n),!0;const o=`Newspack modal checkout: no checkout form found for product_id "${e}"`+(t?` and variation_id "${t}"`:"")+". The checkout was not triggered.";return console.warn(o),!1})(n,o):(t=!1,console.warn("Newspack modal checkout: checkout_button trigger is missing product_id. The checkout was not triggered."))}else{const e=window.newspackReaderActivation?.getPendingCheckout?.();e&&r(D(e))}t&&window.history.replaceState(null,null,window.location.pathname)})(),window.newspackOpenModalCheckout=({url:e=null,title:t=null,actionType:n=null,afterSuccess:o={},onCheckoutComplete:c=null,onClose:a=null})=>{if(g=t||newspackBlocksModal.labels.checkout_modal_title,N(g),(e=new URL(e||newspackBlocksModal.checkout_url)).searchParams.has("my_account_checkout")&&e.searchParams.delete("my_account_checkout"),e.searchParams.has("modal_checkout")||e.searchParams.set("modal_checkout","1"),n&&e.searchParams.set("action_type",n),o?.url&&e.searchParams.set("after_success_url",o.url),(o?.behavior||o?.url)&&e.searchParams.set("after_success_behavior",o.behavior||"custom"),o?.buttonLabel&&e.searchParams.set("after_success_button_label",o.buttonLabel),c){const e=({detail:{action:e,data:t}})=>{"checkout_completed"===e&&c(t)};window.newspackRAS.push(t=>{t.on("activity",e);const n=()=>{t.off("activity",e),document.removeEventListener("checkout-closed",n)};document.addEventListener("checkout-closed",n)})}if(a){const e=()=>{a(),document.removeEventListener("checkout-closed",e)};document.addEventListener("checkout-closed",e)}O(e.toString())},window.newspackCloseModalCheckout=x},"undefined"!=typeof document&&("complete"!==document.readyState&&"interactive"!==document.readyState?document.addEventListener("DOMContentLoaded",q):q())})();
|
|
1
|
+
(()=>{"use strict";function e(e,t=!1){const n=e.querySelectorAll('button, [href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])');if(0===n.length)return!1;const o=n?.[0];let c;o.focus(),t?(document.addEventListener("keydown",function(e){if(("Tab"===e.key||9===e.keyCode)&&e.shiftKey&&document.activeElement===o){const n=t.contentWindow.document,o=n.getElementById("checkout-after-success");c=null!==o?o:n.getElementById("checkout_cancel"),c.focus(),e.preventDefault()}}),document.getElementById("newspack-a11y-last-element").addEventListener("focus",()=>{o.focus()})):(c=n[n.length-1],document.addEventListener("keydown",function(e){("Tab"===e.key||9===e.keyCode)&&(e.shiftKey?document.activeElement===o&&(c.focus(),e.preventDefault()):document.activeElement===c&&(o.focus(),e.preventDefault()))}))}const t=(e,t={})=>({...t,action:e}),n=["action","action_type","amount","currency","product_id","product_type","variation_id","variation_ids","is_variable","is_grouped","child_ids","price_summary","newspack_popup_id","prompt_title","gate_post_id","recurrence","referrer"],o=(e,t="np_modal_checkout_interaction")=>{if("function"==typeof window.gtag&&e){const o={};for(const t of n)e[t]&&("boolean"==typeof e[t]?e[t]=e[t]?"yes":"no":"true"===e[t]?e[t]="yes":"false"===e[t]&&(e[t]="no"),o[t]=e[t].toString());window.gtag("event",t,o)}},c=e=>{if("function"!=typeof window.gtag)return;let n="opened";const{amount:c="",is_variable:a="",is_grouped:r="",price:i="",variation_id:s=""}=e,l={...e};(c||i)&&(l.amount=c||i),a&&!s&&(n="opened_variations"),r&&(n="opened_grouped");const u=t(n,l);o(u)};function a(e,t=null){const n=document.createElement("input");return n.type="hidden",n.name=e,t&&(n.value=t),n}function r(e){e.requestSubmit(e.querySelector('button[type="submit"]'))}function i(e){const t="string"==typeof e?document.getElementById(e):e;if(!t)return console.warn("No container found for checkout data"),{};let n={};if("FORM"===t.tagName){const e=new FormData(t);n=Object.fromEntries(e)}const o=t.dataset.checkout;if(!o)return console.warn("No checkout data found"),n;try{n={...n,...JSON.parse(o)}}catch(e){console.warn("Error parsing checkout data")}const c=new URL(t.ownerDocument.defaultView.location.href);return c.searchParams.get("action_type")&&(n.action_type=c.searchParams.get("action_type")),n}const s=e=>{if("function"!=typeof window.gtag)return;e=e||i("newspack_modal_checkout");const n=t("dismissed",e);o(n)};function l(e){const t=e&&e.dataset?e.dataset.checkout:null;if(!t)return null;try{return JSON.parse(t)}catch(e){return null}}function u(e,t,n=null){const o=e.querySelectorAll(".wp-block-newspack-blocks-checkout-button"),c=null!=n&&""!==String(n);let a=null;return o.forEach(e=>{if(a)return;const o=e.querySelector("form"),r=l(o);r&&String(r.product_id)===String(t)&&(c&&String(r.variation_id)!==String(n)||(a=o))}),a}const d=["after_success_behavior","after_success_url","after_success_button_label","after_success_token","gate_post_id","newspack_popup_id","prompt_title","coupon"];const p={gate:"gate_post_id",prompt:"newspack_popup_id"};const m=newspackBlocksModal.newspack_class_prefix,h="newspack_modal_checkout_iframe",_="newspack_modal_checkout_container",f=`${m}__modal`,w="newspack-blocks__modal-variation",y=`${m}__processing-payment-text`,k=newspackBlocksModal.processing_payment_messages;let v=!1,g=newspackBlocksModal.labels.checkout_modal_title,b=null;const S=e=>{e.overlayId&&window.newspackReaderActivation?.overlays&&window.newspackReaderActivation?.overlays.remove(e.overlayId),e.setAttribute("data-state","closed"),document.body.style.overflow="auto"};window.onpageshow=e=>{e.persisted&&(document.querySelectorAll(".modal-processing").forEach(e=>e.classList.remove("modal-processing")),document.querySelectorAll(".non-modal-checkout-loading").forEach(e=>e.classList.remove("non-modal-checkout-loading")),document.querySelectorAll(`.${f}-container`).forEach(e=>S(e)))};const E=new CustomEvent("checkout-closed");var q;window.newspackRAS=window.newspackRAS||[],q=()=>{const t=document.querySelector("#newspack_modal_checkout");if(!t)return;const n=t.querySelector(`.${f}__content`),o=a("modal_checkout","1"),q=n.querySelector(`.${m}__spinner`);let A=document.querySelector(".newspack-reader__account-link")?.[0];const L="600px",$=document.createElement("iframe");$.name=h,$.style.height=L,$.style.visibility="hidden";const R=e=>{P.observe(e),"none"!==q.style.display&&(q.style.display="none"),"visible"!==$.style.visibility&&($.style.visibility="visible"),$._ready=!0};!function(e,t,n){e._ready=!1,e._readyTimer&&clearTimeout(e._readyTimer);let o=!1,c="";function a(){o||(o=!0,clearTimeout(e._readyTimer),t.call(this))}function r(){"complete"===this.readyState&&a.call(this)}function i(){e._ready=!1,n&&n(),e._readyTimer&&clearTimeout(e._readyTimer);const t=e.contentDocument||e.contentWindow?.document;t&&(t.removeEventListener("DOMContentLoaded",a),t.removeEventListener("readystatechange",r))}function s(){if(e._ready)return void i();const t=e.contentDocument||e.contentWindow?.document;t&&0!==t.URL.indexOf("about:")?"complete"===t?.readyState?a.call(t):(t.addEventListener("DOMContentLoaded",a),t.addEventListener("readystatechange",r)):e._readyTimer=setTimeout(s,10)}e._observer||(e._observer=new MutationObserver(e=>{e.forEach(e=>{"attributes"===e.type&&"src"===e.attributeName&&(o=!1,i(),s())})}),e._observer.observe(e,{attributes:!0})),e._locationObserver||(e._locationObserver=setInterval(function(){const t=e.contentDocument||e.contentWindow?.document;t&&t.URL!==c&&(c=t.URL,o=!1,i(),s())},50)),s()}($,function(){const n=$.contentWindow?.location;if(window.newspackReaderActivation&&n?.href?.includes("order-received")){const e=window.newspackReaderActivation,t=new Proxy(new URLSearchParams(n.search),{get:(e,t)=>e.get(t)});t.email&&(e.setReaderEmail(t.email),e.setAuthenticated(!0))}const o=$?.contentDocument?.querySelector(`#${_}`);if(!o)return;const c=i(o.querySelector("#modal-checkout-product-details")),a=document.createElement("p");a.classList.add(y);let r=[];const s=()=>{r.forEach(e=>clearTimeout(e)),r=[]},l=()=>{q.style.display="none",s(),q.querySelectorAll(`.${y}`).forEach(e=>e.remove())};!function(e,t){e.checkoutCancel?t():e.addEventListener("checkout-cancel",t)}(o,()=>{x()}),function(e,t){e.checkoutPlaceOrderStart?t():e.addEventListener("checkout-place-order-start",t)}(o,e=>{q.querySelectorAll(`.${y}`).forEach(e=>e.remove()),q.style.display="flex",s(),a.textContent=k[0]?.text??"",k.slice(1).forEach(({text:t,delay:n})=>{const o=setTimeout(()=>{e.target.dispatchEvent(new CustomEvent("checkout-place-order-processing")),a.textContent=t},n);r.push(o)}),q.appendChild(a)}),function(e,t){e.checkoutPlaceOrderError?t():e.addEventListener("checkout-place-order-error",t)}(o,l),function(e,t){e.checkoutReady?t():e.addEventListener("checkout-ready",t)}(o,()=>{if(t.initialErrors){const e=document.createElement("div");e.classList.add("woocommerce-error"),e.textContent=t.initialErrors,o.prepend(e),delete t.initialErrors}I(),N(g),$.contentWindow?.newspackBlocksModalCheckout?.checkout_nonce&&(t.checkout_nonce=$.contentWindow.newspackBlocksModalCheckout.checkout_nonce),R(o)}),function(e,t){e.checkoutComplete?t():e.addEventListener("checkout-complete",t)}(o,()=>{window.newspackRAS.push(["checkout_completed",c]),window?.newspackReaderActivation?.refreshNewslettersSignupModal&&window?.newspackReaderActivation?.getReader()?.email&&window.newspackReaderActivation.refreshNewslettersSignupModal(window.newspackReaderActivation.getReader().email),I("small"),N(newspackBlocksModal.labels.thankyou_modal_title),R(o),e(t.querySelector(`.${f}`)),l()}),function(e,t){e.checkoutPlaceOrderCriticalError?t():e.addEventListener("checkout-place-order-critical-error",t)}(o,()=>{var e;(e=b)&&(x(),q.style.display="none",t.initialErrors=newspackBlocksModal.labels.critical_error,e.requestSubmit(e.querySelector('button[type="submit"]')),l())})},()=>{q.style.display="flex"});const M=e=>new Promise((t,n)=>{const o=new URLSearchParams(e);o.append("action","modal_checkout_request"),fetch(newspackBlocksModal.ajax_url+"?"+o.toString()).then(e=>{e.ok||n(e),e.json().then(e=>{t(e.url)}).catch(n)}).catch(n)}),C=()=>"undefined"!=typeof newspack_ras_config&&!newspack_ras_config?.is_logged_in&&!window?.newspackReaderActivation?.getReader?.()?.authenticated&&newspackBlocksModal?.is_registration_required&&window?.newspackReaderActivation?.openAuthModal,B=t=>{const n=!newspackBlocksModal.has_unsupported_payment_gateway;n||t.preventDefault();const o=t.target;o.classList.add("modal-processing"),function(e){if(!e||"function"!=typeof e.querySelector)return!1;if(e.closest(".newspack-content-gate__gate"))return!1;const t=function(){let e,t;try{e=window.sessionStorage.getItem("newspack_cta_attribution")}catch(e){return null}if(!e)return null;try{t=JSON.parse(e)}catch(e){return null}return t&&1===t.v&&p[t.type]&&t.id?"number"!=typeof t.ts||Date.now()-t.ts>18e5?null:t:null}();if(!t)return!1;const n=p[t.type];if(e.querySelector(`input[name="${n}"]`))return!1;const o=document.createElement("input");o.type="hidden",o.name=n,o.value=t.id,e.prepend(o)}(o);const u=i(o);if(u.newspack_donate){const e=u.donation_frequency,t=[...o.querySelectorAll(`.donation-tier__${e}, .donation-frequency__${e}`)],n=u.donation_tier_index;let c,a;n?(c=t[n],a=u[`donation_value_${e}`]):(c=t[0],u[`donation_value_${e}_untiered`]?a=u[`donation_value_${e}_untiered`]:(a=u[`donation_value_${e}`],"other"===a&&(a=u[`donation_value_${e}_other`])));const r=i(c);for(const e in r)u[e]=r[e];u.amount=a,u.price_summary=u.summary_template.replace("{{PRICE}}",function(e,t="USD"){return parseFloat(e).toLocaleString(document.documentElement.lang,{style:"currency",currency:t,currencyDisplay:"narrowSymbol"})}(u.amount,u.currency))}u&&Object.keys(u).forEach(e=>{0===o.querySelectorAll('input[name="'+e+'"]').length&&o.prepend(a(e,u[e]))}),u.variation_id||(A=t.submitter);const m=document.querySelectorAll(`.${w}`);if(m.forEach(e=>{(C()||n)&&S(e)}),u.is_grouped||u.is_variable&&!u.variation_id){const n=[...m].find(e=>e.dataset.productId===u.product_id);if(n)return n.querySelectorAll(`form[target="${h}"]`).forEach(e=>{!function(e,t,n=d){if(!e||!t)return;const o=e.ownerDocument;n.forEach(n=>{e.querySelectorAll(`input[name="${n}"]`).forEach(e=>e.remove());const c=t[n],a=null==c?"":String(c);if(!a)return;const r=o.createElement("input");r.type="hidden",r.name=n,r.value=a,e.prepend(r)})}(e,u);const t=l(e);t&&Object.keys(t).forEach(n=>{0===e.querySelectorAll('input[name="'+n+'"]').length&&e.prepend(a(n,t[n]))})}),t.preventDefault(),o.classList.remove("modal-processing"),T(n),e(n,!1),v||c(u),void document.getElementById("newspack_modal_checkout").setAttribute("data-checkout",JSON.stringify(u))}if(n||C()){if(o.classList.remove("modal-processing"),v||c(u),v=!0,C()){t.preventDefault();const e=M(u);e.then(e=>{window.newspackReaderActivation?.setPendingCheckout?.(e)}),window.newspackReaderActivation.openAuthModal({title:newspackBlocksModal.labels.auth_modal_title,onSuccess:(t,o)=>{e.then(e=>{o?.registered&&n&&(e+=`&${newspackBlocksModal.checkout_registration_flag}=1`),n?r(D(e)):M(u).then(window.location.href=e)}).catch(e=>{console.warn("Unable to generate cart:",e),x()})},onError:()=>{x()},onDismiss:()=>{s(u),v=!1,document.getElementById("newspack_modal_checkout").removeAttribute("data-checkout")},skipSuccess:!0,skipNewslettersSignup:!0,labels:{signin:{title:newspackBlocksModal.labels.signin_modal_title},register:{title:newspackBlocksModal.labels.register_modal_title}},trigger:t.submitter,closeOnSuccess:n})}else O(),document.getElementById("newspack_modal_checkout").setAttribute("data-checkout",JSON.stringify(u));b=o}else M(u).then(e=>{window.location.href=e}),(!u.is_variable||u.variation_id)&&o.querySelectorAll("button[type=submit]:focus").forEach(e=>{e.classList.add("non-modal-checkout-loading");const t=e.innerHTML;e.innerHTML="<span>"+t+"</span>"})},D=e=>{const t=document.createElement("form");t.method="POST",t.action=e,t.target=h,t.style.display="none";const n=document.createElement("button");return n.setAttribute("type","submit"),t.appendChild(n),document.body.appendChild(t),t.addEventListener("submit",B),t},P=new ResizeObserver(e=>{if(!e||!e.length)return;if(!$.contentDocument)return;const o=e[0].contentRect;if(o){const e=.01*Math.max(document.documentElement.clientHeight,window.innerHeight||0)*90-(t.querySelector(`.${f}__header`)?.offsetHeight||0),c=o.top+o.bottom,a=Math.min(c,e);if(0===a)return void($.style.visibility="hidden");n.style.height=a+"px",$.style.height=a+"px"}}),x=()=>{const e=$?.contentDocument?.querySelector(`#${_}`),o=e?.querySelector('input[name="after_success_url"]'),c=e?.querySelector('input[name="after_success_behavior"]'),a=document?.querySelector(".newspack-newsletters-signup-modal"),r=i(e?.querySelector("#modal-checkout-product-details"));e?.checkoutComplete||(async()=>{const e=new FormData;newspackBlocksModal.has_unsupported_payment_gateway||e.append("modal_checkout","1"),e.append("action","abandon_modal_checkout"),e.append("_wpnonce",t.checkout_nonce),t.checkout_nonce=null;try{await fetch(newspackBlocksModal.ajax_url,{method:"POST",body:e})}catch(e){console.warn("Unable to empty cart:",e)}})();const l=!($.contentDocument&&o&&c&&e?.checkoutComplete);if((l||a)&&(q.style.display="flex",$&&n.contains($)&&($._ready=!1,$.src="about:blank",$.style.height=L,$.style.visibility="hidden",n.style.height=L,n.removeChild($)),P&&P.disconnect(),document.querySelectorAll(`.${f}-container`).forEach(e=>S(e)),A&&A.focus(),document.dispatchEvent(E)),e?.checkoutComplete){const e=()=>{if(o&&c){const e=o.getAttribute("value"),t=c.getAttribute("value");if("custom"===t)window.location.href=e;else if("referrer"===t){const e=document.referrer;let t=!1;if(e)try{new URL(e).origin===window.location.origin&&(window.location.href=e,t=!0)}catch(e){}t||window.history.back()}}window?.newspackReaderActivation?.setPendingCheckout?.(),v=!1};"subscription_switch"!==r.action_type&&window?.newspackReaderActivation?.openNewslettersSignupModal?window.newspackReaderActivation.openNewslettersSignupModal({onSuccess:e,onError:e,closeOnSuccess:l}):e(),l&&(g=newspackBlocksModal.labels.checkout_modal_title,I(),N(g))}else window?.newspackReaderActivation?.setPendingCheckout?.(),s(),v=!1,document.getElementById("newspack_modal_checkout").removeAttribute("data-checkout");document.removeEventListener("keydown",U)},O=o=>{o&&($.src=o),q.style.display="flex",T(t),n.appendChild($),t.addEventListener("click",e=>{e.target===t&&x()}),e(t,$),document.addEventListener("keydown",U)},T=e=>{window.newspackReaderActivation?.overlays&&(e.overlayId=window.newspackReaderActivation?.overlays.add()),e.setAttribute("data-state","open"),document.body.style.overflow="hidden"},N=e=>{const n=t.querySelector(`.${f}__header h2`);n&&(n.innerText=e)},I=(e="default")=>{const n=t.querySelector(`.${f}`);n&&("small"===e?n.classList.add(`${f}--small`):n.classList.remove(`${f}--small`))};t.querySelectorAll(`.${f}__close`).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault(),x()})}),document.querySelectorAll(".newspack-blocks__modal-variation").forEach(e=>{e.addEventListener("click",t=>{t.target===e&&x()}),e.querySelectorAll(`.${f}__close`).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault(),x()})})});const U=e=>{"Escape"===e.key&&x()};document.querySelectorAll(".wpbnbd.wpbnbd--platform-wc, .wp-block-newspack-blocks-checkout-button, .newspack-blocks__modal-variation").forEach(e=>{e.querySelectorAll("form").forEach(e=>{newspackBlocksModal.has_unsupported_payment_gateway||e.prepend(o.cloneNode()),e.target=h,e.addEventListener("submit",B)})}),(()=>{const e=new URLSearchParams(window.location.search);if(!e.has("checkout"))return;let t=!0;const n=e.get("type");if("donate"===n){const t=e.get("layout"),n=e.get("frequency"),o=e.get("amount"),c=e.get("other");t&&n&&o&&((e,t,n,o=null)=>{let c;document.querySelectorAll(".wpbnbd.wpbnbd--platform-wc form").forEach(a=>{const r=a.querySelector(`input[name="donation_frequency"][value="${t}"]`);if(r)if("tiered"===e){const e=document.querySelector(`button[data-frequency-slug="${t}"]`);if(!e)return;e.click();const o=a.querySelector(`button[type="submit"][name="donation_value_${t}"][value="${n}"]`);if(!o)return;o.click()}else{const i="untiered"===e?a.querySelector(`input[name="donation_value_${t}_untiered"]`):a.querySelector(`input[name="donation_value_${t}"][value="${n}"]`);if(r&&i){if(r.checked=!0,"untiered"===e)i.value=n;else if("other"===n){i.click();const e=a.querySelector(`input[name="donation_value_${t}_other"]`);e&&o&&(e.value=o)}else i.checked=!0;c=a}}}),c&&r(c)})(t,n,o,c)}else if("checkout_button"===n){const n=e.get("product_id"),o=e.get("variation_id");n?t=((e,t=null)=>{const n=function(e,t,n,o={}){const{allowProductOnlyFallback:c=!1}=o;if(null==n||""===String(n))return u(e,t,null);const a=u(e,t,n);if(a)return a;const r=function(e,t,n,o={}){const{variationModalClassPrefix:c,iframeName:a}=o,r=[...e.querySelectorAll(`.${c}`)].find(e=>String(e.dataset.productId)===String(t));if(!r)return null;const i=r.querySelectorAll("form"),s=a?[...i].find(e=>e.getAttribute("target")===a):i[0];if(!s)return null;const l=[...s.querySelectorAll('input[type="radio"][name="product_id"]')].find(e=>String(e.value)===String(n));return l?(l.checked=!0,s):null}(e,t,n,o);return r?(function(e,t,n=d){if(!e||!t)return;const o=t.ownerDocument,c=new FormData(e);n.forEach(e=>{if(t.querySelector(`input[name="${e}"]`))return;const n=c.getAll(e).filter(e=>"string"==typeof e&&e);if(!n.length)return;const a=o.createElement("input");a.type="hidden",a.name=e,a.value=n[n.length-1],t.prepend(a)})}(function(e,t){const n=e.querySelectorAll(".wp-block-newspack-blocks-checkout-button");let o=null,c=null;return n.forEach(e=>{if(c)return;const n=e.querySelector("form"),a=l(n);a&&String(a.product_id)===String(t)&&(a.variation_id?o=o||n:c=n)}),c||o}(e,t),r),r):c?u(e,t,null):null}(document,e,t,{variationModalClassPrefix:w,iframeName:h});if(n)return r(n),!0;const o=`Newspack modal checkout: no checkout form found for product_id "${e}"`+(t?` and variation_id "${t}"`:"")+". The checkout was not triggered.";return console.warn(o),!1})(n,o):(t=!1,console.warn("Newspack modal checkout: checkout_button trigger is missing product_id. The checkout was not triggered."))}else{const e=window.newspackReaderActivation?.getPendingCheckout?.();e&&r(D(e))}t&&window.history.replaceState(null,null,window.location.pathname)})(),window.newspackOpenModalCheckout=({url:e=null,title:t=null,actionType:n=null,afterSuccess:o={},onCheckoutComplete:c=null,onClose:a=null})=>{if(g=t||newspackBlocksModal.labels.checkout_modal_title,N(g),(e=new URL(e||newspackBlocksModal.checkout_url)).searchParams.has("my_account_checkout")&&e.searchParams.delete("my_account_checkout"),e.searchParams.has("modal_checkout")||e.searchParams.set("modal_checkout","1"),n&&e.searchParams.set("action_type",n),o?.url&&e.searchParams.set("after_success_url",o.url),(o?.behavior||o?.url)&&e.searchParams.set("after_success_behavior",o.behavior||"custom"),o?.buttonLabel&&e.searchParams.set("after_success_button_label",o.buttonLabel),c){const e=({detail:{action:e,data:t}})=>{"checkout_completed"===e&&c(t)};window.newspackRAS.push(t=>{t.on("activity",e);const n=()=>{t.off("activity",e),document.removeEventListener("checkout-closed",n)};document.addEventListener("checkout-closed",n)})}if(a){const e=()=>{a(),document.removeEventListener("checkout-closed",e)};document.addEventListener("checkout-closed",e)}O(e.toString())},window.newspackCloseModalCheckout=x},"undefined"!=typeof document&&("complete"!==document.readyState&&"interactive"!==document.readyState?document.addEventListener("DOMContentLoaded",q):q())})();
|
|
@@ -383,6 +383,7 @@ final class Modal_Checkout {
|
|
|
383
383
|
$after_success_behavior = filter_input( INPUT_GET, 'after_success_behavior', FILTER_SANITIZE_SPECIAL_CHARS );
|
|
384
384
|
$after_success_url = filter_input( INPUT_GET, 'after_success_url', FILTER_SANITIZE_URL );
|
|
385
385
|
$after_success_button_label = filter_input( INPUT_GET, 'after_success_button_label', FILTER_SANITIZE_SPECIAL_CHARS );
|
|
386
|
+
$after_success_token = filter_input( INPUT_GET, 'after_success_token', FILTER_SANITIZE_SPECIAL_CHARS );
|
|
386
387
|
|
|
387
388
|
if ( $variation_id ) {
|
|
388
389
|
$product_id = $variation_id;
|
|
@@ -401,7 +402,7 @@ final class Modal_Checkout {
|
|
|
401
402
|
wp_parse_str( $parsed_url['query'], $params );
|
|
402
403
|
}
|
|
403
404
|
|
|
404
|
-
$params = array_merge( $params, compact( 'after_success_behavior', 'after_success_url', 'after_success_button_label' ) );
|
|
405
|
+
$params = array_merge( $params, compact( 'after_success_behavior', 'after_success_url', 'after_success_button_label', 'after_success_token' ) );
|
|
405
406
|
|
|
406
407
|
if ( function_exists( 'wpcom_vip_url_to_postid' ) ) {
|
|
407
408
|
$referer_post_id = wpcom_vip_url_to_postid( $referer );
|
|
@@ -1238,6 +1239,346 @@ final class Modal_Checkout {
|
|
|
1238
1239
|
ob_end_flush();
|
|
1239
1240
|
}
|
|
1240
1241
|
|
|
1242
|
+
/**
|
|
1243
|
+
* Behaviors that have somewhere to go once checkout finishes.
|
|
1244
|
+
*
|
|
1245
|
+
* Anything else leaves the reader in a modal that won't move and won't close, so it is
|
|
1246
|
+
* treated as "close the modal" instead.
|
|
1247
|
+
*
|
|
1248
|
+
* @var string[]
|
|
1249
|
+
*/
|
|
1250
|
+
const AFTER_SUCCESS_BEHAVIORS = [ 'custom', 'referrer' ];
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Reduce a post-checkout destination to a single comparable form.
|
|
1254
|
+
*
|
|
1255
|
+
* Signing and verifying have to agree on the exact string, and the value passes through
|
|
1256
|
+
* sanitising on its way between them, so both sides normalise here rather than each
|
|
1257
|
+
* doing their own.
|
|
1258
|
+
*
|
|
1259
|
+
* @param string $url The requested destination.
|
|
1260
|
+
*
|
|
1261
|
+
* @return string The normalised destination, or an empty string.
|
|
1262
|
+
*/
|
|
1263
|
+
public static function normalize_after_success_url( $url ) {
|
|
1264
|
+
$url = sanitize_url( (string) $url );
|
|
1265
|
+
|
|
1266
|
+
if ( empty( $url ) ) {
|
|
1267
|
+
return '';
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Core compares hosts case-sensitively; host names aren't. Rebuild from the parsed
|
|
1271
|
+
// parts rather than replacing the host in the string: the host can appear again
|
|
1272
|
+
// inside a query parameter, and a `user@host` authority never matches `://host` at
|
|
1273
|
+
// all, so a string replacement either rewrites too much or nothing.
|
|
1274
|
+
$parts = wp_parse_url( $url );
|
|
1275
|
+
if ( ! empty( $parts['host'] ) && strtolower( $parts['host'] ) !== $parts['host'] ) {
|
|
1276
|
+
$parts['host'] = strtolower( $parts['host'] );
|
|
1277
|
+
$url = self::build_url( $parts );
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
return $url;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* Reassemble a URL from `wp_parse_url()` parts.
|
|
1285
|
+
*
|
|
1286
|
+
* @param array $parts Parsed URL parts.
|
|
1287
|
+
*
|
|
1288
|
+
* @return string
|
|
1289
|
+
*/
|
|
1290
|
+
private static function build_url( $parts ) {
|
|
1291
|
+
$user = $parts['user'] ?? '';
|
|
1292
|
+
$pass = isset( $parts['pass'] ) ? ':' . $parts['pass'] : '';
|
|
1293
|
+
$auth = $user ? $user . $pass . '@' : '';
|
|
1294
|
+
|
|
1295
|
+
// A destination with a host but no scheme is protocol-relative. Losing the `//` here
|
|
1296
|
+
// would turn it into a relative path, which changes where the reader lands and makes
|
|
1297
|
+
// this function non-idempotent — and the token signs a normalised value at mint and
|
|
1298
|
+
// normalises again at read, so idempotence is what makes it verify.
|
|
1299
|
+
$prefix = isset( $parts['scheme'] )
|
|
1300
|
+
? $parts['scheme'] . '://'
|
|
1301
|
+
: ( isset( $parts['host'] ) ? '//' : '' );
|
|
1302
|
+
|
|
1303
|
+
return $prefix
|
|
1304
|
+
. $auth
|
|
1305
|
+
. ( $parts['host'] ?? '' )
|
|
1306
|
+
. ( isset( $parts['port'] ) ? ':' . $parts['port'] : '' )
|
|
1307
|
+
. ( $parts['path'] ?? '' )
|
|
1308
|
+
. ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' )
|
|
1309
|
+
. ( isset( $parts['fragment'] ) ? '#' . $parts['fragment'] : '' );
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* Whether a post is published, read from the stored status.
|
|
1314
|
+
*
|
|
1315
|
+
* `get_post_status()` reports an attachment as `publish` whatever its stored status, so a
|
|
1316
|
+
* token could be minted from one and could not be revoked by unpublishing. Reading the
|
|
1317
|
+
* field directly returns the real status, and also steps around the `get_post_status`
|
|
1318
|
+
* filter, which would otherwise let any plugin move this gate.
|
|
1319
|
+
*
|
|
1320
|
+
* @param int $post_id The post to check.
|
|
1321
|
+
*
|
|
1322
|
+
* @return boolean
|
|
1323
|
+
*/
|
|
1324
|
+
private static function is_published_post( $post_id ) {
|
|
1325
|
+
return 'publish' === get_post_field( 'post_status', (int) $post_id );
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/**
|
|
1329
|
+
* The post a block is being rendered for.
|
|
1330
|
+
*
|
|
1331
|
+
* `get_the_ID()` is empty outside the loop, which is where a block in a widget or a footer
|
|
1332
|
+
* template part renders. The queried object covers that, but only when it is a post: on an
|
|
1333
|
+
* archive it is a term, whose ID would otherwise be read as an unrelated post's.
|
|
1334
|
+
*
|
|
1335
|
+
* @return int Post ID, or 0.
|
|
1336
|
+
*/
|
|
1337
|
+
private static function get_after_success_post_id() {
|
|
1338
|
+
$post_id = (int) get_the_ID();
|
|
1339
|
+
|
|
1340
|
+
if ( $post_id ) {
|
|
1341
|
+
return $post_id;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
$queried = get_queried_object();
|
|
1345
|
+
|
|
1346
|
+
return $queried instanceof WP_Post ? (int) $queried->ID : 0;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
/**
|
|
1350
|
+
* Sign a value for the post-checkout destination it belongs to.
|
|
1351
|
+
*
|
|
1352
|
+
* Namespaced so this never produces the same digest as another use of the same key —
|
|
1353
|
+
* `wp_hash()` with the auth salt also backs the magic-link and email-change secrets, and
|
|
1354
|
+
* these signatures are published in page HTML.
|
|
1355
|
+
*
|
|
1356
|
+
* @param string $payload The value to sign.
|
|
1357
|
+
*
|
|
1358
|
+
* @return string
|
|
1359
|
+
*/
|
|
1360
|
+
private static function sign_after_success_payload( $payload ) {
|
|
1361
|
+
// The namespace prefix is what separates this from every other use of the same key and
|
|
1362
|
+
// algorithm. `Reader_Registration::get_frontend_registration_key()` also produces
|
|
1363
|
+
// `hash_hmac( 'sha256', …, wp_salt( 'auth' ) )` and publishes the result to the front
|
|
1364
|
+
// end, so nothing but this prefix distinguishes the two. Never register an integration
|
|
1365
|
+
// ID that begins with it.
|
|
1366
|
+
//
|
|
1367
|
+
// The blog ID is included because `wp_salt()` is network-scoped: without it, a token
|
|
1368
|
+
// minted on one site of a network verifies on every other site in that network.
|
|
1369
|
+
$namespace = 'newspack_blocks_after_success|' . get_current_blog_id() . '|';
|
|
1370
|
+
|
|
1371
|
+
return hash_hmac( 'sha256', $namespace . $payload, wp_salt( 'auth' ) );
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* Mint a token vouching for a post-checkout destination a block was configured with.
|
|
1376
|
+
*
|
|
1377
|
+
* Minted where the block renders, which is the last point this site knows the destination
|
|
1378
|
+
* came from its own content rather than from the request. Two properties matter:
|
|
1379
|
+
*
|
|
1380
|
+
* The destination travels *inside* the token rather than beside it. The token's alphabet
|
|
1381
|
+
* is URL-safe base64 plus a dot, so the sanitising the value meets in transit — which
|
|
1382
|
+
* variously deletes percent-encoded octets, drops non-ASCII bytes and HTML-encodes `&`
|
|
1383
|
+
* and `'` — cannot alter it. Signing the bare URL and hoping every transit point leaves
|
|
1384
|
+
* it alone is what this avoids.
|
|
1385
|
+
*
|
|
1386
|
+
* The token is bound to the post being rendered, and only published posts are signed, so
|
|
1387
|
+
* a token cannot be minted through the block-renderer REST route or a draft preview by
|
|
1388
|
+
* someone who can edit posts but not publish them.
|
|
1389
|
+
*
|
|
1390
|
+
* `hash_hmac` rather than a nonce: the token has to survive page caching and a reader who
|
|
1391
|
+
* signs in partway through checkout, and a nonce is bound to a user and a time window.
|
|
1392
|
+
*
|
|
1393
|
+
* @param string $url The destination to vouch for.
|
|
1394
|
+
* @param int $post_id The post being rendered. Defaults to the current post.
|
|
1395
|
+
*
|
|
1396
|
+
* @return string The token, or an empty string if there is nothing to vouch for.
|
|
1397
|
+
*/
|
|
1398
|
+
public static function get_after_success_token( $url, $post_id = 0 ) {
|
|
1399
|
+
$url = self::normalize_after_success_url( $url );
|
|
1400
|
+
|
|
1401
|
+
if ( '' === $url ) {
|
|
1402
|
+
return '';
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
$post_id = $post_id ? (int) $post_id : self::get_after_success_post_id();
|
|
1406
|
+
|
|
1407
|
+
if ( ! $post_id || ! self::is_published_post( $post_id ) ) {
|
|
1408
|
+
return '';
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
$payload = self::encode_after_success_payload(
|
|
1412
|
+
[
|
|
1413
|
+
'u' => $url,
|
|
1414
|
+
'p' => $post_id,
|
|
1415
|
+
]
|
|
1416
|
+
);
|
|
1417
|
+
|
|
1418
|
+
return $payload . '.' . self::sign_after_success_payload( $payload );
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/**
|
|
1422
|
+
* Read a destination back out of a token, if this site vouched for it.
|
|
1423
|
+
*
|
|
1424
|
+
* @param string $token The token offered with the request.
|
|
1425
|
+
*
|
|
1426
|
+
* @return string The destination, or an empty string if the token is not this site's.
|
|
1427
|
+
*/
|
|
1428
|
+
public static function parse_after_success_token( $token ) {
|
|
1429
|
+
$token = (string) $token;
|
|
1430
|
+
|
|
1431
|
+
if ( '' === $token || false === strpos( $token, '.' ) ) {
|
|
1432
|
+
return '';
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
list( $payload, $signature ) = explode( '.', $token, 2 );
|
|
1436
|
+
|
|
1437
|
+
if ( '' === $payload || '' === $signature ) {
|
|
1438
|
+
return '';
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
if ( ! hash_equals( self::sign_after_success_payload( $payload ), $signature ) ) {
|
|
1442
|
+
return '';
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
$claims = self::decode_after_success_payload( $payload );
|
|
1446
|
+
|
|
1447
|
+
if ( empty( $claims['u'] ) || empty( $claims['p'] ) ) {
|
|
1448
|
+
return '';
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// Checked at read time as well as at mint time: a post that has since been
|
|
1452
|
+
// unpublished should stop vouching for its destination.
|
|
1453
|
+
if ( ! self::is_published_post( (int) $claims['p'] ) ) {
|
|
1454
|
+
return '';
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
return self::normalize_after_success_url( $claims['u'] );
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Encode token claims into a form transit cannot alter.
|
|
1462
|
+
*
|
|
1463
|
+
* @param array $claims The claims to encode.
|
|
1464
|
+
*
|
|
1465
|
+
* @return string URL-safe base64.
|
|
1466
|
+
*/
|
|
1467
|
+
private static function encode_after_success_payload( $claims ) {
|
|
1468
|
+
return rtrim( strtr( base64_encode( (string) wp_json_encode( $claims ) ), '+/', '-_' ), '=' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/**
|
|
1472
|
+
* Decode token claims.
|
|
1473
|
+
*
|
|
1474
|
+
* @param string $payload URL-safe base64.
|
|
1475
|
+
*
|
|
1476
|
+
* @return array The claims, or an empty array if the payload is unreadable.
|
|
1477
|
+
*/
|
|
1478
|
+
private static function decode_after_success_payload( $payload ) {
|
|
1479
|
+
$json = base64_decode( strtr( $payload, '-_', '+/' ), true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
|
1480
|
+
|
|
1481
|
+
if ( false === $json ) {
|
|
1482
|
+
return [];
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
$claims = json_decode( $json, true );
|
|
1486
|
+
|
|
1487
|
+
return is_array( $claims ) ? $claims : [];
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* Reduce a post-checkout destination to one this site is willing to send readers to.
|
|
1492
|
+
*
|
|
1493
|
+
* The destination reaches the thank-you page through the request, whether it came from
|
|
1494
|
+
* the block's own settings or from the link the reader followed, and by that point the
|
|
1495
|
+
* two look alike. A signature tells them apart: one is minted when a block renders a
|
|
1496
|
+
* destination a publisher configured, so a destination carrying a valid one is this
|
|
1497
|
+
* site's own and is honoured wherever it points.
|
|
1498
|
+
*
|
|
1499
|
+
* Everything else falls to core's redirect validation, which accepts what
|
|
1500
|
+
* `allowed_redirect_hosts` reports — this site's own host, plus whatever a publisher
|
|
1501
|
+
* adds through that filter, plus anything another plugin has added (`Newspack\NRH`
|
|
1502
|
+
* registers one). A destination arriving in a link with no signature has to clear that
|
|
1503
|
+
* bar, which is what keeps a crafted link from choosing where a reader lands.
|
|
1504
|
+
*
|
|
1505
|
+
* @param string $url The requested destination.
|
|
1506
|
+
* @param string $token Token offered with the request, if any.
|
|
1507
|
+
*
|
|
1508
|
+
* @return string The destination, or an empty string if it is not allowed.
|
|
1509
|
+
*/
|
|
1510
|
+
public static function sanitize_after_success_url( $url, $token = '' ) {
|
|
1511
|
+
// A token carries its own destination, so a mutated `after_success_url` alongside it
|
|
1512
|
+
// is ignored rather than compared against.
|
|
1513
|
+
$vouched = self::parse_after_success_token( $token );
|
|
1514
|
+
|
|
1515
|
+
if ( '' !== $vouched ) {
|
|
1516
|
+
// Returned as verified. `wp_sanitize_redirect()` is deliberately not applied to
|
|
1517
|
+
// this branch: it strips `'` and percent-encodes non-ASCII, so it would rewrite a
|
|
1518
|
+
// destination this site has already established is the publisher's own — the same
|
|
1519
|
+
// drift the token exists to prevent. The untrusted branch below still gets it, via
|
|
1520
|
+
// `wp_validate_redirect()`. Both have been through `sanitize_url()` in normalising.
|
|
1521
|
+
return $vouched;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
$url = self::normalize_after_success_url( $url );
|
|
1525
|
+
|
|
1526
|
+
if ( empty( $url ) ) {
|
|
1527
|
+
return '';
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
$validated = (string) wp_validate_redirect( $url, '' );
|
|
1531
|
+
|
|
1532
|
+
if ( '' === $validated ) {
|
|
1533
|
+
/**
|
|
1534
|
+
* Fires when a post-checkout destination is refused.
|
|
1535
|
+
*
|
|
1536
|
+
* The reader is sent nowhere and the modal simply closes, which looks the same
|
|
1537
|
+
* as a publisher not configuring a destination at all. This is the hook to
|
|
1538
|
+
* watch if you need to tell those two apart.
|
|
1539
|
+
*
|
|
1540
|
+
* @param string $url The refused destination.
|
|
1541
|
+
*/
|
|
1542
|
+
do_action( 'newspack_blocks_modal_checkout_after_success_url_rejected', $url );
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
return $validated;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* Drop an after-success behavior the reader can't act on.
|
|
1550
|
+
*
|
|
1551
|
+
* A behavior with nowhere to go renders a modal that neither navigates nor closes, so
|
|
1552
|
+
* the destination, the behavior and its button label all fall away together and the
|
|
1553
|
+
* reader gets the ordinary "close" button instead of one labelled for a page they
|
|
1554
|
+
* won't be taken to.
|
|
1555
|
+
*
|
|
1556
|
+
* @param array $params After-success params.
|
|
1557
|
+
*
|
|
1558
|
+
* @return array The params, less any the reader can't act on.
|
|
1559
|
+
*/
|
|
1560
|
+
public static function filter_after_success_params( $params ) {
|
|
1561
|
+
$behavior = $params['after_success_behavior'] ?? '';
|
|
1562
|
+
|
|
1563
|
+
if ( '' === $behavior ) {
|
|
1564
|
+
return $params;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
$is_unknown = ! in_array( $behavior, self::AFTER_SUCCESS_BEHAVIORS, true );
|
|
1568
|
+
$has_nowhere = 'custom' === $behavior && empty( $params['after_success_url'] );
|
|
1569
|
+
|
|
1570
|
+
if ( $is_unknown || $has_nowhere ) {
|
|
1571
|
+
unset(
|
|
1572
|
+
$params['after_success_behavior'],
|
|
1573
|
+
$params['after_success_url'],
|
|
1574
|
+
$params['after_success_button_label'],
|
|
1575
|
+
$params['after_success_token']
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
return $params;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1241
1582
|
/**
|
|
1242
1583
|
* Get after success button params.
|
|
1243
1584
|
*/
|
|
@@ -1251,14 +1592,19 @@ final class Modal_Checkout {
|
|
|
1251
1592
|
\wp_parse_str( $referrer_query, $request_params );
|
|
1252
1593
|
}
|
|
1253
1594
|
}
|
|
1254
|
-
|
|
1595
|
+
$token = isset( $request_params['after_success_token'] ) ? sanitize_text_field( wp_unslash( $request_params['after_success_token'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
1596
|
+
|
|
1597
|
+
$params = array_filter(
|
|
1255
1598
|
[
|
|
1256
1599
|
'after_success_behavior' => isset( $request_params['after_success_behavior'] ) ? sanitize_text_field( wp_unslash( $request_params['after_success_behavior'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
1257
|
-
'after_success_url' => isset( $request_params['after_success_url'] ) ?
|
|
1600
|
+
'after_success_url' => isset( $request_params['after_success_url'] ) ? self::sanitize_after_success_url( wp_unslash( $request_params['after_success_url'] ), $token ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
1258
1601
|
'after_success_button_label' => isset( $request_params['after_success_button_label'] ) ? sanitize_text_field( wp_unslash( $request_params['after_success_button_label'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
1602
|
+
'after_success_token' => $token,
|
|
1259
1603
|
'action_type' => isset( $request_params['action_type'] ) ? sanitize_text_field( wp_unslash( $request_params['action_type'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
1260
1604
|
]
|
|
1261
1605
|
);
|
|
1606
|
+
|
|
1607
|
+
return self::filter_after_success_params( $params );
|
|
1262
1608
|
}
|
|
1263
1609
|
|
|
1264
1610
|
/**
|
package/newspack-blocks.php
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Author URI: https://newspack.com/
|
|
8
8
|
* Text Domain: newspack-blocks
|
|
9
9
|
* Domain Path: /languages
|
|
10
|
-
* Version: 4.30.0-alpha.
|
|
10
|
+
* Version: 4.30.0-alpha.2
|
|
11
11
|
*
|
|
12
12
|
* @package Newspack_Blocks
|
|
13
13
|
*/
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
define( 'NEWSPACK_BLOCKS__PLUGIN_FILE', __FILE__ );
|
|
16
16
|
define( 'NEWSPACK_BLOCKS__BLOCKS_DIRECTORY', 'dist/' );
|
|
17
17
|
define( 'NEWSPACK_BLOCKS__PLUGIN_DIR', plugin_dir_path( NEWSPACK_BLOCKS__PLUGIN_FILE ) );
|
|
18
|
-
define( 'NEWSPACK_BLOCKS__VERSION', '4.30.0-alpha.
|
|
18
|
+
define( 'NEWSPACK_BLOCKS__VERSION', '4.30.0-alpha.2' );
|
|
19
19
|
|
|
20
20
|
require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks.php';
|
|
21
21
|
require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks-api.php';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/newspack-blocks",
|
|
3
|
-
"version": "4.30.0-alpha.
|
|
3
|
+
"version": "4.30.0-alpha.2",
|
|
4
4
|
"author": "Automattic",
|
|
5
5
|
"description": "=== Newspack Blocks === Contributors: (this should be a list of wordpress.org userid's) Donate link: https://example.com/ Tags: comments, spam Requires at least: 4.5 Tested up to: 5.1.1 Stable tag: 0.1.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html",
|
|
6
6
|
"repository": {
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
class WP_REST_Newspack_Author_List_Controller extends WP_REST_Newspack_Authors_Controller {
|
|
13
13
|
// phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Fields returned when a caller doesn't ask for a specific set.
|
|
17
|
+
*
|
|
18
|
+
* @var string[]
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_FIELDS = [ 'id', 'name', 'bio', 'email', 'social', 'avatar', 'url' ];
|
|
21
|
+
|
|
15
22
|
/**
|
|
16
23
|
* Constructs the controller.
|
|
17
24
|
*
|
|
@@ -136,6 +143,10 @@ class WP_REST_Newspack_Author_List_Controller extends WP_REST_Newspack_Authors_C
|
|
|
136
143
|
$options['fields'] = explode( ',', $request->get_param( 'fields' ) );
|
|
137
144
|
}
|
|
138
145
|
|
|
146
|
+
// Restricted here rather than in get_all_authors(), which also renders the block on
|
|
147
|
+
// the front end for visitors and must keep returning whatever a publisher publishes.
|
|
148
|
+
$options['fields'] = self::restrict_fields( $options['fields'] ?? self::DEFAULT_FIELDS );
|
|
149
|
+
|
|
139
150
|
$combined_authors = $this->get_all_authors( $options );
|
|
140
151
|
$response = new \WP_REST_Response( $combined_authors );
|
|
141
152
|
$response->header( 'x-wp-total', count( $combined_authors ) );
|
|
@@ -157,7 +168,7 @@ class WP_REST_Newspack_Author_List_Controller extends WP_REST_Newspack_Authors_C
|
|
|
157
168
|
'avatar_hide_default' => false,
|
|
158
169
|
'exclude' => [], // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
|
|
159
170
|
'exclude_empty' => false,
|
|
160
|
-
'fields' =>
|
|
171
|
+
'fields' => self::DEFAULT_FIELDS,
|
|
161
172
|
'per_page' => 10,
|
|
162
173
|
];
|
|
163
174
|
$options = wp_parse_args( $options, $default_options );
|
|
@@ -57,7 +57,7 @@ function newspack_blocks_render_block_author_list( $attributes ) {
|
|
|
57
57
|
'author_type' => $author_type,
|
|
58
58
|
'author_roles' => $author_roles,
|
|
59
59
|
'exclude' => $attributes['exclude'], // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
|
|
60
|
-
'fields' =>
|
|
60
|
+
'fields' => WP_REST_Newspack_Author_List_Controller::DEFAULT_FIELDS,
|
|
61
61
|
];
|
|
62
62
|
|
|
63
63
|
if ( $exclude_empty ) {
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
13
13
|
// phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Fields carrying contact details that WordPress reserves for people who manage users.
|
|
17
|
+
*
|
|
18
|
+
* These endpoints are open to anyone who can edit posts, which is a much wider audience
|
|
19
|
+
* than core allows to read an address.
|
|
20
|
+
*/
|
|
21
|
+
const USER_MANAGEMENT_FIELDS = [ 'email' ];
|
|
22
|
+
|
|
15
23
|
/**
|
|
16
24
|
* Constructs the controller.
|
|
17
25
|
*
|
|
@@ -22,6 +30,41 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
22
30
|
$this->rest_base = 'authors';
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Whether the current user may read author contact details.
|
|
35
|
+
*
|
|
36
|
+
* Matches the capabilities core requires before exposing a user's address.
|
|
37
|
+
*
|
|
38
|
+
* @return boolean
|
|
39
|
+
*/
|
|
40
|
+
public static function can_read_user_management_fields() {
|
|
41
|
+
// Keep `list_users` first: on multisite, core maps `edit_users` through
|
|
42
|
+
// `manage_network_users`, so a site administrator passes only on `list_users`.
|
|
43
|
+
return current_user_can( 'list_users' ) || current_user_can( 'edit_users' );
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Drop any requested fields the current user is not allowed to read.
|
|
48
|
+
*
|
|
49
|
+
* Applied per request rather than inside the formatting helpers, which also render the
|
|
50
|
+
* front-end author blocks for visitors and must keep returning whatever a publisher has
|
|
51
|
+
* chosen to publish there.
|
|
52
|
+
*
|
|
53
|
+
* `false` is the helpers' "every field" convention, used by the front-end blocks; it is
|
|
54
|
+
* passed through untouched so those renders are unaffected.
|
|
55
|
+
*
|
|
56
|
+
* @param array|false $fields Requested fields, or false for every field.
|
|
57
|
+
*
|
|
58
|
+
* @return array|false Fields the current user may receive.
|
|
59
|
+
*/
|
|
60
|
+
public static function restrict_fields( $fields ) {
|
|
61
|
+
if ( ! is_array( $fields ) || self::can_read_user_management_fields() ) {
|
|
62
|
+
return $fields;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return array_values( array_diff( $fields, self::USER_MANAGEMENT_FIELDS ) );
|
|
66
|
+
}
|
|
67
|
+
|
|
25
68
|
/**
|
|
26
69
|
* Registers the necessary REST API routes.
|
|
27
70
|
*
|
|
@@ -84,6 +127,7 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
84
127
|
$per_page = ! empty( $request->get_param( 'perPage' ) ) ? $request->get_param( 'perPage' ) : 10; // Number of results to return per page. This is applied to each query, so the actual number of results returned may be up to 2x this number.
|
|
85
128
|
$avatar_hide_default = ! empty( $request->get_param( 'avatar_hide_default' ) ) ? true : false; // Hide the default avatar if the user has no custom avatar.
|
|
86
129
|
$fields = ! empty( $request->get_param( 'fields' ) ) ? explode( ',', $request->get_param( 'fields' ) ) : [ 'id' ]; // Fields to get. Will return at least id.
|
|
130
|
+
$fields = self::restrict_fields( $fields );
|
|
87
131
|
$include = ! empty( $request->get_param( 'include' ) ) ? explode( ',', $request->get_param( 'include' ) ) : null; // Fetch authors by multiple IDs.
|
|
88
132
|
$post_id = ! empty( $request->get_param( 'post_id' ) ) ? $request->get_param( 'post_id' ) : 0; // Fetch authors for a specific post (contextual mode).
|
|
89
133
|
|
|
@@ -146,6 +146,10 @@ function render_callback( $attributes ) {
|
|
|
146
146
|
$hidden_fields .= $after_success_behavior ? '<input type="hidden" name="after_success_behavior" value="' . esc_attr( $after_success_behavior ) . '" />' : '';
|
|
147
147
|
$hidden_fields .= $after_success_button_label ? '<input type="hidden" name="after_success_button_label" value="' . esc_attr( $after_success_button_label ) . '" />' : '';
|
|
148
148
|
$hidden_fields .= $after_success_url ? '<input type="hidden" name="after_success_url" value="' . esc_attr( $after_success_url ) . '" />' : '';
|
|
149
|
+
// Vouched for here because this is the last point the destination is known to come
|
|
150
|
+
// from the block's own settings rather than from the request.
|
|
151
|
+
$after_success_token = $after_success_url ? Modal_Checkout::get_after_success_token( $after_success_url ) : '';
|
|
152
|
+
$hidden_fields .= $after_success_token ? '<input type="hidden" name="after_success_token" value="' . esc_attr( $after_success_token ) . '" />' : '';
|
|
149
153
|
}
|
|
150
154
|
// Always emit the coupon field (not gated on the gateway check): it is
|
|
151
155
|
// applied server-side for both the modal and the redirect checkout flows.
|
|
@@ -212,6 +212,16 @@ abstract class Newspack_Blocks_Donate_Renderer_Base {
|
|
|
212
212
|
<input type='hidden' name='<?php echo esc_attr( $param_name ); ?>' value='<?php echo esc_attr( $value ); ?>' />
|
|
213
213
|
<?php
|
|
214
214
|
}
|
|
215
|
+
|
|
216
|
+
// Vouched for here because this is the last point the destination is known to come
|
|
217
|
+
// from the block's own settings rather than from the request.
|
|
218
|
+
$after_success_url = isset( $attributes['afterSuccessURL'] ) ? $attributes['afterSuccessURL'] : '';
|
|
219
|
+
$after_success_token = $after_success_url ? \Newspack_Blocks\Modal_Checkout::get_after_success_token( $after_success_url ) : '';
|
|
220
|
+
if ( $after_success_token ) {
|
|
221
|
+
?>
|
|
222
|
+
<input type='hidden' name='after_success_token' value='<?php echo esc_attr( $after_success_token ); ?>' />
|
|
223
|
+
<?php
|
|
224
|
+
}
|
|
215
225
|
}
|
|
216
226
|
return ob_get_clean();
|
|
217
227
|
}
|
|
@@ -246,6 +246,100 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
|
|
|
246
246
|
return in_array( $mime_type, array_keys( self::iframe_archive_accepted_file_mimes() ), true );
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Reduce a caller-supplied name to a single safe path segment.
|
|
251
|
+
*
|
|
252
|
+
* Used for names this plugin generates but builds out of caller-supplied parts, such as
|
|
253
|
+
* the destination folder derived from an attachment title.
|
|
254
|
+
*
|
|
255
|
+
* @param string $segment The name to reduce.
|
|
256
|
+
*
|
|
257
|
+
* @return string|false The safe segment, or false if nothing usable is left.
|
|
258
|
+
*/
|
|
259
|
+
private function sanitize_path_segment( $segment ) {
|
|
260
|
+
$segment = sanitize_file_name( basename( str_replace( '\\', '/', (string) $segment ) ) );
|
|
261
|
+
|
|
262
|
+
if ( '' === $segment || '.' === $segment || '..' === $segment ) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return $segment;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Reduce a caller-supplied relative path to safe segments, keeping subdirectories.
|
|
271
|
+
*
|
|
272
|
+
* Archive entries may legitimately sit in subdirectories and are referenced by name from
|
|
273
|
+
* the archive's own markup, so the segments are checked rather than rewritten: renaming
|
|
274
|
+
* them would break those references. Anything that could point outside the destination —
|
|
275
|
+
* an upward segment, an absolute path, a drive letter or a null byte — is refused
|
|
276
|
+
* outright rather than stripped, since stripping is what makes layered payloads such as
|
|
277
|
+
* `....//` work.
|
|
278
|
+
*
|
|
279
|
+
* @param string $path The relative path to reduce.
|
|
280
|
+
*
|
|
281
|
+
* @return string|false The safe relative path, or false if it is not usable.
|
|
282
|
+
*/
|
|
283
|
+
private function sanitize_relative_path( $path ) {
|
|
284
|
+
$path = str_replace( '\\', '/', (string) $path );
|
|
285
|
+
|
|
286
|
+
if ( '' === $path || false !== strpos( $path, "\0" ) ) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Absolute paths, including Windows drive letters.
|
|
291
|
+
if ( '/' === $path[0] || preg_match( '#^[a-zA-Z]:#', $path ) ) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
$segments = [];
|
|
296
|
+
foreach ( explode( '/', $path ) as $segment ) {
|
|
297
|
+
if ( '' === $segment || '.' === $segment ) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if ( '..' === $segment ) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
$segments[] = $segment;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return empty( $segments ) ? false : implode( '/', $segments );
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Confirm that a destination resolves inside a directory.
|
|
311
|
+
*
|
|
312
|
+
* A last check on the resolved paths, so that a symlink or a gap in the checks above
|
|
313
|
+
* still cannot place a file outside the directory. The destination itself need not
|
|
314
|
+
* exist yet: the deepest part of it that does exist is what gets resolved, which is
|
|
315
|
+
* what lets this run before anything is created.
|
|
316
|
+
*
|
|
317
|
+
* @param string $directory Absolute path to the directory the write must stay inside.
|
|
318
|
+
* @param string $destination Absolute path to the intended destination.
|
|
319
|
+
*
|
|
320
|
+
* @return boolean
|
|
321
|
+
*/
|
|
322
|
+
private function is_inside_directory( $directory, $destination ) {
|
|
323
|
+
$directory = realpath( $directory );
|
|
324
|
+
|
|
325
|
+
if ( ! $directory ) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
$existing = dirname( $destination );
|
|
330
|
+
while ( ! file_exists( $existing ) && dirname( $existing ) !== $existing ) {
|
|
331
|
+
$existing = dirname( $existing );
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
$resolved = realpath( $existing );
|
|
335
|
+
|
|
336
|
+
if ( ! $resolved ) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return 0 === strpos( trailingslashit( $resolved ), trailingslashit( $directory ) );
|
|
341
|
+
}
|
|
342
|
+
|
|
249
343
|
/**
|
|
250
344
|
* Receive the iframe content, handle it, and returns the URL and the folder name.
|
|
251
345
|
*
|
|
@@ -363,7 +457,11 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
|
|
|
363
457
|
* @return WP_REST_Response
|
|
364
458
|
*/
|
|
365
459
|
private function process_iframe_document( $request, $document_filename, $media_source_path ) {
|
|
366
|
-
|
|
460
|
+
// The filename is built from the source attachment's title, so it is only a name once
|
|
461
|
+
// it has been reduced to a single segment.
|
|
462
|
+
$document_filename = $this->sanitize_path_segment( $document_filename );
|
|
463
|
+
|
|
464
|
+
if ( ! $document_filename || ! $this->validate_file_extension( $document_filename, false ) ) {
|
|
367
465
|
return new WP_Error(
|
|
368
466
|
'newspack_blocks',
|
|
369
467
|
sprintf(
|
|
@@ -393,6 +491,14 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
|
|
|
393
491
|
}
|
|
394
492
|
}
|
|
395
493
|
|
|
494
|
+
if ( ! $this->is_inside_directory( $iframe_upload_dir, $iframe_path ) ) {
|
|
495
|
+
return new WP_Error(
|
|
496
|
+
'newspack_blocks',
|
|
497
|
+
__( 'Could not upload your document.', 'newspack-blocks' ),
|
|
498
|
+
[ 'status' => '400' ]
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
396
502
|
// Copy uploaded document file to destination.
|
|
397
503
|
if ( copy( $media_source_path, $iframe_path ) ) {
|
|
398
504
|
return [
|
|
@@ -434,14 +540,25 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
|
|
|
434
540
|
}
|
|
435
541
|
global $wp_filesystem;
|
|
436
542
|
|
|
543
|
+
// The folder is built from the source attachment's title, so it is only a name once it
|
|
544
|
+
// has been reduced to a single segment.
|
|
545
|
+
$iframe_folder = $this->sanitize_path_segment( $iframe_folder );
|
|
546
|
+
if ( ! $iframe_folder ) {
|
|
547
|
+
return $invalid_file_error;
|
|
548
|
+
}
|
|
549
|
+
|
|
437
550
|
$wp_upload_dir = wp_upload_dir();
|
|
438
551
|
$iframe_upload_dir = $wp_upload_dir['path'] . self::IFRAME_UPLOAD_DIR;
|
|
439
552
|
$iframe_path = trailingslashit( $iframe_upload_dir . $iframe_folder );
|
|
440
553
|
$data = $request->get_body_params();
|
|
441
554
|
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
555
|
+
// Create this archive's own directory up front, so each entry's destination can be
|
|
556
|
+
// resolved against it before anything is written.
|
|
557
|
+
if ( ! file_exists( $iframe_path ) ) {
|
|
558
|
+
wp_mkdir_p( $iframe_path );
|
|
559
|
+
}
|
|
560
|
+
if ( ! $this->is_inside_directory( $iframe_upload_dir, $iframe_path ) ) {
|
|
561
|
+
return $invalid_file_error;
|
|
445
562
|
}
|
|
446
563
|
|
|
447
564
|
// Extract files from archive.
|
|
@@ -464,10 +581,24 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
|
|
|
464
581
|
}
|
|
465
582
|
$stat = $zip->statIndex( $file_index );
|
|
466
583
|
if ( $stat && isset( $stat['name'] ) ) {
|
|
467
|
-
|
|
468
|
-
|
|
584
|
+
// An entry names its own destination, so it decides where the write lands.
|
|
585
|
+
$entry_relative_path = $this->sanitize_relative_path( $stat['name'] );
|
|
586
|
+
if ( ! $entry_relative_path ) {
|
|
587
|
+
return $invalid_file_error;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
$destination = $iframe_path . $entry_relative_path;
|
|
591
|
+
|
|
592
|
+
// Checked before anything is created, so a destination outside this archive's
|
|
593
|
+
// own folder never gets a directory made for it.
|
|
594
|
+
if ( ! $this->is_inside_directory( $iframe_path, $destination ) ) {
|
|
595
|
+
return $invalid_file_error;
|
|
469
596
|
}
|
|
470
|
-
|
|
597
|
+
if ( ! file_exists( dirname( $destination ) ) ) {
|
|
598
|
+
wp_mkdir_p( dirname( $destination ) );
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
$put = $wp_filesystem->put_contents( $destination, $contents );
|
|
471
602
|
if ( ! $put ) {
|
|
472
603
|
return $invalid_file_error;
|
|
473
604
|
}
|
|
@@ -366,6 +366,8 @@ domReady( () => {
|
|
|
366
366
|
// Fill in the hidden params in the variation modal. The picker is
|
|
367
367
|
// shared by every button for this product and is never reset, so
|
|
368
368
|
// this overwrites the previous open's context rather than adding to it.
|
|
369
|
+
// The shared PICKER_CONTEXT_FIELDS list carries `after_success_token`, so
|
|
370
|
+
// the signed destination propagates through the picker with everything else.
|
|
369
371
|
applyContextFields( singleVariationForm, checkoutData );
|
|
370
372
|
|
|
371
373
|
// Append the product data hidden inputs.
|
|
@@ -47,9 +47,25 @@ if ( ! $is_valid ) {
|
|
|
47
47
|
|
|
48
48
|
$is_success = ! $order->has_status( 'failed' );
|
|
49
49
|
$after_success_behavior = isset( $_GET['after_success_behavior'] ) ? \sanitize_text_field( \wp_unslash( $_GET['after_success_behavior'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
50
|
-
$
|
|
50
|
+
$after_success_token = isset( $_GET['after_success_token'] ) ? \sanitize_text_field( \wp_unslash( $_GET['after_success_token'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
51
|
+
// Not escaped here: the value is escaped where it is output, and escaping it into the
|
|
52
|
+
// variable would leave it holding something other than the destination that was verified.
|
|
53
|
+
$after_success_url = isset( $_GET['after_success_url'] ) ? \Newspack_Blocks\Modal_Checkout::sanitize_after_success_url( \sanitize_url( \wp_unslash( $_GET['after_success_url'] ) ), $after_success_token ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
51
54
|
$after_success_label = isset( $_GET['after_success_button_label'] ) ? \sanitize_text_field( \wp_unslash( $_GET['after_success_button_label'] ) ) : \Newspack_Blocks\Modal_Checkout::get_modal_checkout_labels( 'after_success' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
52
55
|
$checkout_data = Checkout_Data::get_checkout_data( $order );
|
|
56
|
+
|
|
57
|
+
// A behavior the reader can't act on falls back to closing the modal, so they get a
|
|
58
|
+
// finished checkout rather than a button that goes nowhere.
|
|
59
|
+
$after_success_params = \Newspack_Blocks\Modal_Checkout::filter_after_success_params(
|
|
60
|
+
[
|
|
61
|
+
'after_success_behavior' => $after_success_behavior,
|
|
62
|
+
'after_success_url' => $after_success_url,
|
|
63
|
+
'after_success_button_label' => $after_success_label,
|
|
64
|
+
]
|
|
65
|
+
);
|
|
66
|
+
$after_success_behavior = $after_success_params['after_success_behavior'] ?? '';
|
|
67
|
+
$after_success_url = $after_success_params['after_success_url'] ?? '';
|
|
68
|
+
$after_success_label = $after_success_params['after_success_button_label'] ?? \Newspack_Blocks\Modal_Checkout::get_modal_checkout_labels( 'after_success' );
|
|
53
69
|
?>
|
|
54
70
|
<div class="woocommerce-order">
|
|
55
71
|
<?php if ( $is_success ) : ?>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
'name' => 'automattic/newspack-blocks',
|
|
4
4
|
'pretty_version' => 'dev-main',
|
|
5
5
|
'version' => 'dev-main',
|
|
6
|
-
'reference' => '
|
|
6
|
+
'reference' => '676b21e16de65c4c31fba7e48dcf23dd93dfcd1c',
|
|
7
7
|
'type' => 'wordpress-plugin',
|
|
8
8
|
'install_path' => __DIR__ . '/../../',
|
|
9
9
|
'aliases' => array(),
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
'automattic/newspack-blocks' => array(
|
|
14
14
|
'pretty_version' => 'dev-main',
|
|
15
15
|
'version' => 'dev-main',
|
|
16
|
-
'reference' => '
|
|
16
|
+
'reference' => '676b21e16de65c4c31fba7e48dcf23dd93dfcd1c',
|
|
17
17
|
'type' => 'wordpress-plugin',
|
|
18
18
|
'install_path' => __DIR__ . '/../../',
|
|
19
19
|
'aliases' => array(),
|