Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1 @@
.block-editor__container .googlesitekit-rrm-settings-panel .googlesitekit-rrm-panel__select-control label{text-transform:none;white-space:normal}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.googlesitekit-blocks-reader-revenue-manager-button{align-items:center;background-color:#fff;border:1px solid #dadce0;border-radius:4px;color:#1a73e8;display:flex;font-family:"Google Sans",Roboto-Regular,sans-serif,arial;font-size:14px;font-weight:500;justify-content:center;letter-spacing:.014px;outline:0;padding:12px 34px}.googlesitekit-blocks-reader-revenue-manager-button svg{margin-right:8px}.googlesitekit-blocks-reader-revenue-manager-button.googlesitekit-blocks-reader-revenue-manager-button--disabled{filter:grayscale(100%);opacity:.5}

View File

@@ -0,0 +1,14 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "google-site-kit/rrm-contribute-with-google",
"version": "1.165.0",
"title": "Contribute with Google",
"category": "widgets",
"icon": "google",
"description": "Allow users to make voluntary contributions using Reader Revenue Manager.",
"textdomain": "google-site-kit",
"supports": {
"inserter": true
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "google-site-kit/rrm-subscribe-with-google",
"version": "1.165.0",
"title": "Subscribe with Google",
"category": "widgets",
"icon": "google",
"description": "Allow users to subscribe using Reader Revenue Manager to access content behind a paywall.",
"textdomain": "google-site-kit",
"supports": {
"inserter": true
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,34 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "google-site-kit/sign-in-with-google",
"version": "1.165.0",
"title": "Sign in with Google",
"category": "widgets",
"icon": "google",
"description": "Allow users to sign in to your site using their Google Account.",
"textdomain": "google-site-kit",
"attributes": {
"shape": {
"type": "string",
"enum": [ "", "rectangular", "pill" ]
},
"text": {
"type": "string",
"enum": [
"",
"continue_with",
"signin",
"signin_with",
"signup_with"
]
},
"theme": {
"type": "string",
"enum": [ "", "outline", "filled_blue", "filled_black" ]
},
"buttonClassName": {
"type": "string"
}
}
}

View File

@@ -0,0 +1 @@
.googlesitekit-blocks-sign-in-with-google{max-width:180px;min-width:120px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.authorize-application-php{background-color:#fff}.authorize-application-php .wrap{margin:30px 0 0}.authorize-application-php .wrap h1{font-size:24px;line-height:32px;padding:0}.authorize-application-php .auth-app-card{border:none;margin:37px 0 0;padding:0}.authorize-application-php .auth-app-card p{font-size:14px;font-weight:500;line-height:20px;margin:19px 0 20px}.authorize-application-php .title{font-size:18px;font-weight:500;line-height:24px}.authorize-application-php strong{font-weight:500}.authorize-application-php .form-field{margin:20px 0 40px;max-width:512px}.authorize-application-php .form-field label{color:#1f1f1f;font-size:12px;font-weight:500;line-height:16px}.authorize-application-php .form-field input{border:1px solid #747775;border-radius:8px;color:#1f1f1f;font-size:14px;line-height:20px;margin:8px 0 0;padding:12px 16px;width:100%}.authorize-application-php .description{color:#1f1f1f;font-size:12px;line-height:16px}.authorize-application-php .description strong{display:block;margin:9px 0 0;max-width:512px}.authorize-application-php .description code{background-color:#ededed;border-radius:4px;color:#1f1f1f;display:inline-block;font-family:"Google Sans Text","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;line-height:16px;padding:4px 9px}.authorize-application-php .googlesitekit-authorize-application__footer{display:none}@media(min-width: 783px){.authorize-application-php .googlesitekit-authorize-application__footer{bottom:0;display:block;left:0;line-height:32px;margin-left:36px;padding:0 47px;position:absolute}.authorize-application-php .googlesitekit-authorize-application__footer p{color:#1f1f1f;font-family:"Google Sans Text","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;font-weight:500;line-height:20px;margin:19px 0 20px}}@media(min-width: 961px){.authorize-application-php .googlesitekit-authorize-application__footer{margin-left:160px}}html[dir=rtl] .googlesitekit-authorize-application__footer{left:auto;margin-right:36px;right:0}@media(min-width: 961px){html[dir=rtl] .googlesitekit-authorize-application__footer{margin-right:160px}}#wpbody-content{padding-bottom:100px}@media(max-width: 782px){.auto-fold #wpcontent{padding:0 26px}}#wpcontent,#wpfooter{color:#1f1f1f;font-family:"Google Sans Text","Helvetica Neue",Helvetica,Arial,sans-serif}@media(min-width: 783px){#wpcontent,#wpfooter{padding:0 47px}}#wpbody-content a,#wpfooter a{color:#1a73e8}#wpfooter{bottom:45px}#approve,#reject{border-radius:100px;font-size:14px;font-weight:500;line-height:20px;margin:0 0 15px;padding:10px 24px}#approve{background-color:#0b57d0;border:none;color:#fff}#approve:hover{box-shadow:0 0 0 1000px rgba(255,255,255,.08) inset}#approve:focus{box-shadow:0 0 0 1000px rgba(255,255,255,.12) inset}#reject{background-color:#fff;border:1px solid #747775;color:#0b57d0}#reject:hover{box-shadow:0 0 0 1000px rgba(11,87,208,.08) inset}#reject:focus{border:1px solid #0b57d0;box-shadow:0 0 0 1000px rgba(11,87,208,.12) inset}#description-approve{border-bottom:1px solid #c4c7c5;margin:0 0 33px;padding:0 0 31px}#description-reject{margin:0 0 8px}#footer-thankyou{font-size:12px;line-height:16px}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
"use strict";(globalThis.__googlesitekit_webpackJsonp=globalThis.__googlesitekit_webpackJsonp||[]).push([[146],{6146:(e,t,r)=>{r.r(t),r.d(t,{default:()=>__WEBPACK_DEFAULT_EXPORT__});var l,a,i,n,o,c,s,h=r(63696);function f(){return f=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var l in r)({}).hasOwnProperty.call(r,l)&&(e[l]=r[l])}return e},f.apply(null,arguments)}const __WEBPACK_DEFAULT_EXPORT__=e=>h.createElement("svg",f({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 169 95"},e),l||(l=h.createElement("g",{filter:"url(#ghost-card-red_svg__a)"},h.createElement("rect",{width:165,height:90,fill:"#fff",rx:11}),h.createElement("rect",{width:164,height:89,x:.5,y:.5,stroke:"#EBEEF0",rx:10.5}))),a||(a=h.createElement("rect",{width:25,height:8,x:16,y:16,fill:"#EBEEF0",rx:4})),i||(i=h.createElement("rect",{width:50,height:20,x:16,y:33,fill:"#FFDED3",rx:10})),n||(n=h.createElement("rect",{width:133,height:8,x:16,y:68,fill:"#EBEEF0",rx:4})),o||(o=h.createElement("path",{stroke:"#fff",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"m24.997 40 6.594 6.593m0 0 .073-5.201m-.073 5.201-5.202.074"})),c||(c=h.createElement("rect",{width:20,height:6,x:38,y:40,fill:"#fff",rx:2})),s||(s=h.createElement("defs",null,h.createElement("filter",{id:"ghost-card-red_svg__a",width:169,height:95,x:0,y:0,colorInterpolationFilters:"sRGB",filterUnits:"userSpaceOnUse"},h.createElement("feFlood",{floodOpacity:0,result:"BackgroundImageFix"}),h.createElement("feColorMatrix",{in:"SourceAlpha",result:"hardAlpha",values:"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"}),h.createElement("feOffset",{dx:4,dy:5}),h.createElement("feComposite",{in2:"hardAlpha",operator:"out"}),h.createElement("feColorMatrix",{values:"0 0 0 0 0.921569 0 0 0 0 0.933333 0 0 0 0 0.941176 0 0 0 1 0"}),h.createElement("feBlend",{in2:"BackgroundImageFix",result:"effect1_dropShadow_4824_80809"}),h.createElement("feBlend",{in:"SourceGraphic",in2:"effect1_dropShadow_4824_80809",result:"shape"})))))}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
"use strict";(globalThis.__googlesitekit_webpackJsonp=globalThis.__googlesitekit_webpackJsonp||[]).push([[909],{4290:(e,t,r)=>{r.r(t),r.d(t,{default:()=>__WEBPACK_DEFAULT_EXPORT__});var l,a,n,i,o,c,s,h=r(63696);function f(){return f=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var l in r)({}).hasOwnProperty.call(r,l)&&(e[l]=r[l])}return e},f.apply(null,arguments)}const __WEBPACK_DEFAULT_EXPORT__=e=>h.createElement("svg",f({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 169 95"},e),l||(l=h.createElement("g",{filter:"url(#ghost-card-green_svg__a)"},h.createElement("rect",{width:165,height:90,fill:"#fff",rx:11}),h.createElement("rect",{width:164,height:89,x:.5,y:.5,stroke:"#EBEEF0",rx:10.5}))),a||(a=h.createElement("rect",{width:64,height:8,x:16,y:16,fill:"#EBEEF0",rx:4})),n||(n=h.createElement("path",{fill:"#B8E6CA",d:"M16 43c0-5.523 4.477-10 10-10h30c5.523 0 10 4.477 10 10s-4.477 10-10 10H26c-5.523 0-10-4.477-10-10"})),i||(i=h.createElement("rect",{width:133,height:8,x:16,y:68,fill:"#EBEEF0",rx:4})),o||(o=h.createElement("path",{stroke:"#fff",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"m25 46.667 6.593-6.594m0 0L26.392 40m5.201.073.074 5.202"})),c||(c=h.createElement("rect",{width:20,height:6,x:38,y:40,fill:"#fff",rx:2})),s||(s=h.createElement("defs",null,h.createElement("filter",{id:"ghost-card-green_svg__a",width:169,height:95,x:0,y:0,colorInterpolationFilters:"sRGB",filterUnits:"userSpaceOnUse"},h.createElement("feFlood",{floodOpacity:0,result:"BackgroundImageFix"}),h.createElement("feColorMatrix",{in:"SourceAlpha",result:"hardAlpha",values:"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"}),h.createElement("feOffset",{dx:4,dy:5}),h.createElement("feComposite",{in2:"hardAlpha",operator:"out"}),h.createElement("feColorMatrix",{values:"0 0 0 0 0.921569 0 0 0 0 0.933333 0 0 0 0 0.941176 0 0 0 1 0"}),h.createElement("feBlend",{in2:"BackgroundImageFix",result:"effect1_dropShadow_4824_80823"}),h.createElement("feBlend",{in:"SourceGraphic",in2:"effect1_dropShadow_4824_80823",result:"shape"})))))}}]);

View File

@@ -0,0 +1 @@
(()=>{"use strict";var t={};function e(t,e){const o=t.matches||t.matchesSelector||t.webkitMatchesSelector||t.mozMatchesSelector||t.msMatchesSelector||t.oMatchesSelector||function(t){const e=(this.document||this.ownerDocument).querySelectorAll(t);let o=e.length;for(;--o>=0&&e.item(o)!==this;);return o>-1};return!!o&&o.call(t,e)}t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}();const o=window._googlesitekitAnalyticsTrackingData||[];Array.isArray(o)&&function(o){const n=[];o.forEach(o=>{function c(t){var n,c;("DOMContentLoaded"===o.on||e(t.target,o.selector)||e(t.target,o.selector.concat(" *")))&&(n=o.action,c=o.metadata,window.gtag("event",n,c||void 0))}t.g.document.addEventListener(o.on,c,!0),n.push([o.on,c,!0])})}(o)})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
"use strict";(globalThis.__googlesitekit_webpackJsonp=globalThis.__googlesitekit_webpackJsonp||[]).push([[101],{2522:(t,e,n)=>{n.d(e,{D:()=>o});var r=n(32091),i=n.n(r);function o(t,{dateRangeLength:e}){i()(Array.isArray(t),"report must be an array to partition."),i()(Number.isInteger(e)&&e>0,"dateRangeLength must be a positive integer.");const n=-1*e;return{currentRange:t.slice(n),compareRange:t.slice(2*n,n)}}},8143:(t,e,n)=>{n.d(e,{VZ:()=>o,dc:()=>s,pH:()=>i,r0:()=>a});var r=n(84024);function i(t){try{return new URL(t).pathname}catch{}return null}function o(t,e){try{return new URL(e,t).href}catch{}return("string"==typeof t?t:"")+("string"==typeof e?e:"")}function s(t){return"string"!=typeof t?t:t.replace(/^https?:\/\/(www\.)?/i,"").replace(/\/$/,"")}function a(t,e){if(!(0,r.m)(t))return t;if(t.length<=e)return t;const n=new URL(t),i=t.replace(n.origin,"");if(i.length<e)return i;const o=i.length-Math.floor(e)+1;return"…"+i.substr(o)}},12850:(t,e,n)=>{n.d(e,{tt:()=>S,Jg:()=>D,Gp:()=>_,GH:()=>v,r0:()=>N,Du:()=>I,Zf:()=>V,Cn:()=>T,G7:()=>h,vH:()=>p,N_:()=>R,zh:()=>z,mK:()=>l.mK,Ql:()=>$,vY:()=>K,sq:()=>E,VZ:()=>C.VZ,JK:()=>l.JK,IS:()=>j,pH:()=>C.pH,kf:()=>B,O5:()=>M,Qr:()=>L,x6:()=>U,K5:()=>l.K5,S_:()=>m,dc:()=>C.dc,Eo:()=>l.Eo,jq:()=>l.jq,DK:()=>J.D,N9:()=>H,p9:()=>o.p,XH:()=>O,Zm:()=>c,sx:()=>i.sx,BI:()=>i.BI,CZ:()=>o.C,BG:()=>Z});var r=n(17243),i=n(89318),o=n(82046),s=n(10523),a=n.n(s);function c(t){return a()(JSON.stringify(u(t)))}function u(t){const e={};return Object.keys(t).sort().forEach(n=>{let r=t[n];r&&"object"==typeof r&&!Array.isArray(r)&&(r=u(r)),e[n]=r}),e}var l=n(79829);function g(t){return t.replace(new RegExp("\\[([^\\]]+)\\]\\((https?://[^/]+\\.\\w+/?.*?)\\)","gi"),'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')}function f(t){return`<p>${t.replace(/\n{2,}/g,"</p><p>")}</p>`}function d(t){return t.replace(/\n/gi,"<br>")}function m(t){const e=[g,f,d];let n=t;for(const t of e)n=t(n);return n}function p(t){return t=parseFloat(t),isNaN(t)||0===t?[0,0,0,0]:[Math.floor(t/60/60),Math.floor(t/60%60),Math.floor(t%60),Math.floor(1e3*t)-1e3*Math.floor(t)]}function h(t){const e=t&&!Number.isInteger(t)?new Date(t).getTime():t;return isNaN(e)||!e?0:e}var y=n(32091),b=n.n(y),k=n(82871);const w="Date param must construct to a valid date instance or be a valid date instance itself.",v="Invalid dateString parameter, it must be a string.",_='Invalid date range, it must be a string with the format "last-x-days".',N=60,D=60*N,S=24*D,I=7*S;function $(){function t(t){return(0,k.sprintf)(/* translators: %s: number of days */ /* translators: %s: number of days */
(0,k._n)("Last %s day","Last %s days",t,"google-site-kit"),t)}return{"last-7-days":{slug:"last-7-days",label:t(7),days:7},"last-14-days":{slug:"last-14-days",label:t(14),days:14},"last-28-days":{slug:"last-28-days",label:t(28),days:28},"last-90-days":{slug:"last-90-days",label:t(90),days:90}}}function L(t=""){if(!(0,r.isString)(t))return!1;if(3!==t.split("-").length)return!1;const e=new Date(t);return(0,r.isDate)(e)&&!isNaN(e)}function E(t){b()((0,r.isDate)(t)&&!isNaN(t),w);const e=`${t.getMonth()+1}`,n=`${t.getDate()}`;return[t.getFullYear(),e.length<2?`0${e}`:e,n.length<2?`0${n}`:n].join("-")}function O(t){b()(L(t),v);const[e,n,r]=t.split("-");return new Date(e,n-1,r)}function j(t,e){return E(R(t,e*S))}function M(t){const e=t.split("-");return 3===e.length&&"last"===e[0]&&!Number.isNaN(e[1])&&!Number.isNaN(parseFloat(e[1]))&&"days"===e[2]}function R(t,e){b()(L(t)||(0,r.isDate)(t)&&!isNaN(t),v);const n=L(t)?Date.parse(t):t.getTime();return new Date(n-1e3*e)}var x=n(69743),A=n(94552),F=n(62540);function K(t,e={}){if(Number.isNaN(Number(t)))return"";const{invertColor:n=!1}=e;return(0,x.Ay)((0,F.jsx)(A.A,{direction:t>0?"up":"down",invertColor:n}))}function T(t,e){return t>0&&e>0?t/e-1:t>0?1:e>0?-1:0}var C=n(8143);function U(t){const e=parseFloat(t)||0;return!!Number.isInteger(e)&&e>0}function B(t){if("number"==typeof t)return!0;const e=(t||"").toString();return!!e&&!isNaN(e)}function H(t){return Array.isArray(t)?[...t].sort():t}var J=n(2522);function V(t,e){function n(t){return"0"===t||0===t}if(n(t)&&n(e))return 0;if(n(t)||Number.isNaN(t))return null;const r=(e-t)/t;return Number.isNaN(r)||!Number.isFinite(r)?null:r}function Z(t){try{return JSON.parse(t)&&!!t}catch(t){return!1}}function z(t){if(!t)return"";const e=t.replace(/&#(\d+);/g,(t,e)=>String.fromCharCode(e)).replace(/(\\)/g,"");return(0,r.unescape)(e)}},15210:(t,e,n)=>{n.d(e,{O:()=>i});var r=n(31234);const i=n.n(r)()(n.g)},24558:(t,e,n)=>{n.d(e,{Ax:()=>o,CZ:()=>l,Ej:()=>N,Gw:()=>D,Is:()=>s,KK:()=>f,Nn:()=>I,OT:()=>v,SH:()=>w,Y$:()=>d,ZS:()=>a,bg:()=>h,en:()=>_,ep:()=>p,f7:()=>i,hi:()=>m,jU:()=>r,k$:()=>b,kz:()=>S,ly:()=>c,mo:()=>y,s3:()=>k,uR:()=>u,zx:()=>g});const r="mainDashboard",i="entityDashboard",o="mainDashboardViewOnly",s="entityDashboardViewOnly",a="userInput",c="activation",u="splash",l="adminBar",g="adminBarViewOnly",f="settings",d="adBlockingRecovery",m="wpDashboard",p="wpDashboardViewOnly",h="moduleSetup",y="metricSelection",b="wpBlockEditor",k="keyMetricsSetup",w="key-metrics",v="traffic",_="content",N="speed",D="monetization",S=[r,i,o,s,a,u,f,h,y],I=[o,s,g,p]},50539:t=>{t.exports=googlesitekit.data},65214:(t,e,n)=>{n.d(e,{G:()=>i,t:()=>r});const r=new Set(n.g?._googlesitekitBaseData?.enabledFeatures||[]);function i(t,e=r){return e instanceof Set&&e.has(t)}},71715:(t,e,n)=>{var r=n(12850),i=n(50539),o=n(24558);!function(t){const e=function(t){const e=new Map,n=(0,i.subscribe)(()=>{(0,i.select)("core/block-editor").getInserterItems().filter(({id:e})=>t.includes(e)).forEach(({id:t,title:n})=>e.set(t,n)),e.size===t.length&&n()});return function(t){return e.get(t)}}(t),n=new Set((0,i.select)("core/block-editor").getBlocks().map(t=>t.clientId));(0,i.subscribe)(()=>{(0,i.select)("core/block-editor").getBlocks().forEach(s=>{const{clientId:a,name:c}=s;t.includes(c)&&!n.has(a)&&(0,i.select)("core/block-editor").isBlockSelected(a)&&(0,r.sx)(`${o.k$}_rrm`,"insert_block",e(c)),n.add(a)})})}(["google-site-kit/rrm-subscribe-with-google","google-site-kit/rrm-contribute-with-google"])},79829:(t,e,n)=>{n.d(e,{Eo:()=>g,JK:()=>p,K5:()=>m,jq:()=>d,mK:()=>l});var r=n(17243),i=n(50532),o=n.n(i),s=n(82871);function a(t,e={}){const{formatUnit:n,formatDecimal:r}=function(t,e={}){const{hours:n,minutes:r,seconds:i}=c(t);return{hours:n,minutes:r,seconds:i,formatUnit(){const{unitDisplay:o="short",...a}=e,c={unitDisplay:o,...a,style:"unit"};return 0===t?d(i,{...c,unit:"second"}):(0,s.sprintf)(/* translators: 1: formatted seconds, 2: formatted minutes, 3: formatted hours */ /* translators: 1: formatted seconds, 2: formatted minutes, 3: formatted hours */
(0,s._x)("%3$s %2$s %1$s","duration of time: hh mm ss","google-site-kit"),i?d(i,{...c,unit:"second"}):"",r?d(r,{...c,unit:"minute"}):"",n?d(n,{...c,unit:"hour"}):"").trim()},formatDecimal(){const e=(0,s.sprintf)(
// translators: %s: number of seconds with "s" as the abbreviated unit.
// translators: %s: number of seconds with "s" as the abbreviated unit.
(0,s.__)("%ds","google-site-kit"),i);if(0===t)return e;const o=(0,s.sprintf)(
// translators: %s: number of minutes with "m" as the abbreviated unit.
// translators: %s: number of minutes with "m" as the abbreviated unit.
(0,s.__)("%dm","google-site-kit"),r),a=(0,s.sprintf)(
// translators: %s: number of hours with "h" as the abbreviated unit.
// translators: %s: number of hours with "h" as the abbreviated unit.
(0,s.__)("%dh","google-site-kit"),n);return(0,s.sprintf)(/* translators: 1: formatted seconds, 2: formatted minutes, 3: formatted hours */ /* translators: 1: formatted seconds, 2: formatted minutes, 3: formatted hours */
(0,s._x)("%3$s %2$s %1$s","duration of time: hh mm ss","google-site-kit"),i?e:"",r?o:"",n?a:"").trim()}}}(t,e);try{return n()}catch{return r()}}function c(t){t=parseInt(t,10),Number.isNaN(t)&&(t=0);return{hours:Math.floor(t/60/60),minutes:Math.floor(t/60%60),seconds:Math.floor(t%60)}}function u(t){return 1e6<=t?Math.round(t/1e5)/10:1e4<=t?Math.round(t/1e3):1e3<=t?Math.round(t/100)/10:t}function l(t){let e={};return"%"===t?e={style:"percent",maximumFractionDigits:2}:"s"===t?e={style:"duration",unitDisplay:"narrow"}:t&&"string"==typeof t?e={style:"currency",currency:t}:(0,r.isPlainObject)(t)&&(e={...t}),e}function g(t,e={}){t=(0,r.isFinite)(t)?t:Number(t),(0,r.isFinite)(t)||(console.warn("Invalid number",t,typeof t),t=0);const n=l(e),{style:i="metric"}=n;return"metric"===i?function(t){const e={minimumFractionDigits:1,maximumFractionDigits:1};return 1e6<=t?(0,s.sprintf)(
// translators: %s: an abbreviated number in millions.
// translators: %s: an abbreviated number in millions.
(0,s.__)("%sM","google-site-kit"),d(u(t),t%10==0?{}:e)):1e4<=t?(0,s.sprintf)(
// translators: %s: an abbreviated number in thousands.
// translators: %s: an abbreviated number in thousands.
(0,s.__)("%sK","google-site-kit"),d(u(t))):1e3<=t?(0,s.sprintf)(
// translators: %s: an abbreviated number in thousands.
// translators: %s: an abbreviated number in thousands.
(0,s.__)("%sK","google-site-kit"),d(u(t),t%10==0?{}:e)):d(t,{signDisplay:"never",maximumFractionDigits:1})}(t):"duration"===i?a(t,n):"durationISO"===i?function(t){let{hours:e,minutes:n,seconds:r}=c(t);return r=("0"+r).slice(-2),n=("0"+n).slice(-2),e=("0"+e).slice(-2),"00"===e?`${n}:${r}`:`${e}:${n}:${r}`}(t):d(t,n)}const f=o()(console.warn);function d(t,e={}){const{locale:n=p(),...r}=e;try{return new Intl.NumberFormat(n,r).format(t)}catch(e){f(`Site Kit numberFormat error: Intl.NumberFormat( ${JSON.stringify(n)}, ${JSON.stringify(r)} ).format( ${typeof t} )`,e.message)}const i={currencyDisplay:"narrow",currencySign:"accounting",style:"unit"},o=["signDisplay","compactDisplay"],s={};for(const[t,e]of Object.entries(r))i[t]&&e===i[t]||o.includes(t)||(s[t]=e);try{return new Intl.NumberFormat(n,s).format(t)}catch{return new Intl.NumberFormat(n).format(t)}}function m(t,e={}){const{locale:n=p(),style:r="long",type:i="conjunction"}=e;if(Intl.ListFormat){return new Intl.ListFormat(n,{style:r,type:i}).format(t)}
/* translators: used between list items, there is a space after the comma. */const o=(0,s.__)(", ","google-site-kit");return t.join(o)}function p(t=n.g){const e=(0,r.get)(t,["_googlesitekitLegacyData","locale"]);if(e){const t=e.match(/^(\w{2})?(_)?(\w{2})/);if(t&&t[0])return t[0].replace(/_/g,"-")}return t.navigator.language}},82046:(t,e,n)=>{n.d(e,{C:()=>o,p:()=>i});var r=n(15210);function i(t,e={}){return{__html:r.O.sanitize(t,e)}}function o(t){const e="object"==typeof t?t.toString():t;return e?.replace?.(/\/+$/,"")}},82871:t=>{t.exports=googlesitekit.i18n},89318:(t,e,n)=>{n.d(e,{M9:()=>S,sx:()=>N,BI:()=>D});var r=n(17243);const i="_googlesitekitDataLayer",o="data-googlesitekit-gtag";function s(t){return function(){t[i]=t[i]||[],t[i].push(arguments)}}var a=n(65214);const c={activeModules:[],isAuthenticated:!1,referenceSiteURL:"",trackingEnabled:!1,trackingID:"",userIDHash:"",userRoles:[]};const{activeModules:u=[],isSiteKitScreen:l,trackingEnabled:g,trackingID:f,referenceSiteURL:d,userIDHash:m,isAuthenticated:p,userRoles:h}=n.g._googlesitekitTrackingData||{},{GOOGLESITEKIT_VERSION:y}=n.g,b={activeModules:u,trackingEnabled:g,trackingID:f,referenceSiteURL:d,userIDHash:m,isSiteKitScreen:l,userRoles:h,isAuthenticated:p,pluginVersion:y},{enableTracking:k,disableTracking:w,isTrackingEnabled:v,initializeSnippet:_,trackEvent:N,trackEventOnce:D}=function(t,e=n.g,u=n.g){const l={...c,...t};l.referenceSiteURL&&(l.referenceSiteURL=l.referenceSiteURL.toString().replace(/\/+$/,""));const g=function(t,e){const r=s(e);let c;const{activeModules:u,referenceSiteURL:l,userIDHash:g,userRoles:f=[],isAuthenticated:d,pluginVersion:m}=t;return function(){const{document:e}=n.g;if(void 0===c&&(c=!!e.querySelector(`script[${o}]`)),c)return!1;c=!0;const s=f?.length?f.join(","):"";r("js",new Date),r("config",t.trackingID,{groups:"site_kit",send_page_view:t.isSiteKitScreen,domain:l,plugin_version:m||"",enabled_features:Array.from(a.t).join(","),active_modules:u.join(","),authenticated:d?"1":"0",user_properties:{user_roles:s,user_identifier:g}});const p=e.createElement("script");return p.setAttribute(o,""),p.async=!0,p.src=`https://www.googletagmanager.com/gtag/js?id=${t.trackingID}&l=${i}`,e.head.appendChild(p),{scriptTagSrc:`https://www.googletagmanager.com/gtag/js?id=${t.trackingID}&l=${i}`}}}(l,e),f=function(t,e,n,r){const i=s(e);return async function(e,o,s,a){const{trackingEnabled:c}=t;if(!c)return null;n();const u={send_to:"site_kit",event_category:e,event_label:s,value:a};return new Promise(t=>{const n=setTimeout(function(){r.console.warn(`Tracking event "${o}" (category "${e}") took too long to fire.`),t()},1e3);function s(){clearTimeout(n),t()}i("event",o,{...u,event_callback:s}),r._gaUserPrefs?.ioo?.()&&s()})}}(l,e,g,u),d={};return{enableTracking:function(){l.trackingEnabled=!0},disableTracking:function(){l.trackingEnabled=!1},initializeSnippet:g,isTrackingEnabled:function(){return!!l.trackingEnabled},trackEvent:f,trackEventOnce:function(...t){const e=JSON.stringify(t);d[e]||(d[e]=(0,r.once)(f)),d[e](...t)}}}(b);function S(t){t?k():w()}l&&g&&_()},94552:(t,e,n)=>{n.d(e,{A:()=>c});var r=n(62688),i=n.n(r),o=n(4452),s=n.n(o),a=n(62540);function ChangeArrow({direction:t,invertColor:e,width:n,height:r}){return(0,a.jsx)("svg",{className:s()("googlesitekit-change-arrow",`googlesitekit-change-arrow--${t}`,{"googlesitekit-change-arrow--inverted-color":e}),width:n,height:r,viewBox:"0 0 10 10",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:(0,a.jsx)("path",{d:"M5.625 10L5.625 2.375L9.125 5.875L10 5L5 -1.76555e-07L-2.7055e-07 5L0.875 5.875L4.375 2.375L4.375 10L5.625 10Z",fill:"currentColor"})})}ChangeArrow.propTypes={direction:i().string,invertColor:i().bool,width:i().number,height:i().number},ChangeArrow.defaultProps={direction:"up",invertColor:!1,width:9,height:9};const c=ChangeArrow}},t=>{t.O(0,[660],()=>{return e=71715,t(t.s=e);var e});t.O()}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
"use strict";(globalThis.__googlesitekit_webpackJsonp=globalThis.__googlesitekit_webpackJsonp||[]).push([[290],{1102:(t,e,o)=>{o.d(e,{w:()=>i});var n=o(35470),c=o(63696),r=o(2422);function s(){}function i(t,e){const o=(0,r.W)({sticky:!0}),i=(0,c.useRef)(),a=(0,c.useCallback)(t,e),u=(0,n.A)(o?a:s);return o&&(i.current=u),i.current}},2422:(t,e,o)=>{o.d(e,{W:()=>a});var n=o(79257),c=o(35470),r=o(63696),s=o(6732),i=o(7081);function a({sticky:t=!1}={}){const e=(0,r.useContext)(s.A),[o,a]=(0,r.useState)(!1),u=(0,c.A)(t=>t(i.n).getInViewResetCount()),l=(0,c.A)(t=>t(i.n).getValue("forceInView"));return(0,r.useEffect)(()=>{e.value&&!o&&a(!0)},[o,e,a]),(0,r.useEffect)(()=>{l&&a(!0)},[l]),(0,n.A)(()=>{a(!1)},[u]),!(!t||!o)||!!e.value}},6732:(t,e,o)=>{o.d(e,{A:()=>n});const n=(0,o(63696).createContext)(!1)},7081:(t,e,o)=>{o.d(e,{F:()=>c,n:()=>n});const n="core/ui",c="activeContextID"},47121:(t,e,o)=>{o.d(e,{$C:()=>I,RF:()=>_,WI:()=>R,_5:()=>h,jU:()=>S,o3:()=>w,x0:()=>b});var n=o(32091),c=o.n(n),r=o(50532),s=o.n(r),i=o(17243),a=o(78913);const u="GET_REGISTRY",l="AWAIT";function g(...t){const e=t.reduce((t,e)=>({...t,...e}),{}),o=k(t.reduce((t,e)=>[...t,...Object.keys(e)],[]));return c()(0===o.length,`collect() cannot accept collections with duplicate keys. Your call to collect() contains the following duplicated functions: ${o.join(", ")}. Check your data stores for duplicates.`),e}const f=g,d=g;function p(...t){const e=[...t];let o;return"function"!=typeof e[0]&&(o=e.shift()),(t=o,n={})=>e.reduce((t,e)=>e(t,n),t)}const y=g,A=g,v=g;function m(t){return t}function w(...t){const e=v(...t.map(t=>t.initialState||{}));return{initialState:e,controls:d(...t.map(t=>t.controls||{})),actions:f(...t.map(t=>t.actions||{})),reducer:p(e,...t.map(t=>t.reducer||m)),resolvers:y(...t.map(t=>t.resolvers||{})),selectors:A(...t.map(t=>t.selectors||{}))}}const S={getRegistry:()=>({payload:{},type:u}),*await(t){return{payload:{value:t},type:l}}},h={[u]:(0,a.b)(t=>()=>t),[l]:({payload:t})=>t.value};function k(t){const e=[],o={};for(let n=0;n<t.length;n++){const c=t[n];o[c]=o[c]>=1?o[c]+1:1,o[c]>1&&e.push(c)}return e}const b={actions:S,controls:h,reducer:m};function R(t){return e=>C(t(e))}const C=s()(t=>(0,i.mapValues)(t,(t,e)=>(...o)=>{const n=t(...o);return c()(void 0!==n,`${e}(...) is not resolved`),n}));function _(t,{negate:e=!1}={}){return{safeSelector:(0,a.N)(o=>(n,...c)=>{const r=!e,s=!!e;try{return t(o,n,...c),r}catch{return s}}),dangerousSelector:(0,a.N)(e=>(o,...n)=>{t(e,o,...n)})}}function I(t,e){return c()("function"==typeof t,"a validator function is required."),c()("function"==typeof e,"an action creator function is required."),c()("Generator"!==t[Symbol.toStringTag]&&"GeneratorFunction"!==t[Symbol.toStringTag],"an actions validator function must not be a generator."),(...o)=>(t(...o),e(...o))}},74595:(t,e,o)=>{o.d(e,{wA:()=>i.A,ws:()=>f.w,WM:()=>s.A});var n=o(29725),c=o(56805),r=o(78913),s=o(35470),i=o(63737),a=o(44319),u=o(31170),l=o(30043),g=o(66293),f=o(1102),d=o(47121),p=o(97241);const y=(0,n.I)({},o.g.wp?.data);y.combineStores=d.o3,y.commonActions=d.jU,y.commonControls=d._5,y.commonStore=d.x0,y.createReducer=function(t){return(0,p.Ay)(t)},y.useInViewSelect=f.w,y.controls=c.n,y.createRegistryControl=r.b,y.createRegistrySelector=r.N,y.useSelect=s.A,y.useDispatch=i.A,y.useRegistry=a.A,y.withSelect=u.A,y.withDispatch=l.A,y.RegistryProvider=g.Ay;const A=y;void 0===o.g.googlesitekit&&(o.g.googlesitekit={}),o.g.googlesitekit.data=A}},t=>{t.O(0,[660],()=>{return e=74595,t(t.s=e);var e});t.O()}]);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
"use strict";(globalThis.__googlesitekit_webpackJsonp=globalThis.__googlesitekit_webpackJsonp||[]).push([[943],{45472:(t,e,o)=>{o.d(e,{M:()=>n});const n="core/location"},47121:(t,e,o)=>{o.d(e,{$C:()=>I,RF:()=>G,WI:()=>k,_5:()=>b,jU:()=>h,o3:()=>S,x0:()=>R});var n=o(32091),r=o.n(n),a=o(50532),i=o.n(a),c=o(17243),s=o(78913);const l="GET_REGISTRY",u="AWAIT";function g(...t){const e=t.reduce((t,e)=>({...t,...e}),{}),o=_(t.reduce((t,e)=>[...t,...Object.keys(e)],[]));return r()(0===o.length,`collect() cannot accept collections with duplicate keys. Your call to collect() contains the following duplicated functions: ${o.join(", ")}. Check your data stores for duplicates.`),e}const p=g,d=g;function f(...t){const e=[...t];let o;return"function"!=typeof e[0]&&(o=e.shift()),(t=o,n={})=>e.reduce((t,e)=>e(t,n),t)}const y=g,v=g,T=g;function m(t){return t}function S(...t){const e=T(...t.map(t=>t.initialState||{}));return{initialState:e,controls:d(...t.map(t=>t.controls||{})),actions:p(...t.map(t=>t.actions||{})),reducer:f(e,...t.map(t=>t.reducer||m)),resolvers:y(...t.map(t=>t.resolvers||{})),selectors:v(...t.map(t=>t.selectors||{}))}}const h={getRegistry:()=>({payload:{},type:l}),*await(t){return{payload:{value:t},type:u}}},b={[l]:(0,s.b)(t=>()=>t),[u]:({payload:t})=>t.value};function _(t){const e=[],o={};for(let n=0;n<t.length;n++){const r=t[n];o[r]=o[r]>=1?o[r]+1:1,o[r]>1&&e.push(r)}return e}const R={actions:h,controls:b,reducer:m};function k(t){return e=>N(t(e))}const N=i()(t=>(0,c.mapValues)(t,(t,e)=>(...o)=>{const n=t(...o);return r()(void 0!==n,`${e}(...) is not resolved`),n}));function G(t,{negate:e=!1}={}){return{safeSelector:(0,s.N)(o=>(n,...r)=>{const a=!e,i=!!e;try{return t(o,n,...r),a}catch{return i}}),dangerousSelector:(0,s.N)(e=>(o,...n)=>{t(e,o,...n)})}}function I(t,e){return r()("function"==typeof t,"a validator function is required."),r()("function"==typeof e,"an action creator function is required."),r()("Generator"!==t[Symbol.toStringTag]&&"GeneratorFunction"!==t[Symbol.toStringTag],"an actions validator function must not be a generator."),(...o)=>(t(...o),e(...o))}},50539:t=>{t.exports=googlesitekit.data},79760:(t,e,o)=>{var n=o(50539),r=o.n(n),a=o(45472),i=o(32091),c=o.n(i),s=o(47121);const l="DO_NAVIGATE_TO",u="SET_NAVIGATING_TO",g={initialState:{navigatingTo:void 0},actions:{navigateTo:(0,s.$C)(t=>{let e=!1;try{e=new URL(t)}catch{}c()(!!e,"url must be a valid URI.")},function*(t){const e={url:t};return yield{type:u,payload:e},yield{type:l,payload:e}})},controls:{[l]:({payload:t})=>{o.g.location.assign(t.url)}},reducer:(0,n.createReducer)((t,{type:e,payload:o})=>{if(e===u)t.navigatingTo=o.url}),resolvers:{},selectors:{isNavigating:t=>!!t.navigatingTo,isNavigatingTo(t,e){const{navigatingTo:o}=t;return c()("string"==typeof e||e instanceof RegExp,"url must be either a string or a regular expression."),"string"==typeof e?o===e:e.test(o)},getNavigateURL:t=>t.navigatingTo||null}},p=(0,n.combineStores)(n.commonStore,g);p.initialState,p.actions,p.controls,p.reducer,p.resolvers,p.selectors;r().registerStore(a.M,p)}},t=>{t.O(0,[660],()=>{return e=79760,t(t.s=e);var e});t.O()}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(()=>{"use strict";var e,t,r,a,l,o={};o.d=(e,t)=>{for(var r in t)o.o(t,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(e=o.g.jQuery)&&(e("body").on("edd_cart_item_added",(e,t)=>{var r,a;const{name:l,value:i}=function(e){var t,r;const a=(new DOMParser).parseFromString(e,"text/html"),l=(null===(t=a.querySelector(".edd-cart-item-title"))||void 0===t?void 0:t.textContent.trim())||"";let o=((null===(r=a.querySelector(".edd-cart-item-price"))||void 0===r?void 0:r.textContent.trim())||"").replace(/[^\d.,]/g,"").trim();const i=o.lastIndexOf(","),n=o.lastIndexOf(".");return i>-1&&n>-1?o=i>n?o.replace(/\./g,"").replace(",","."):o.replace(/,/g,""):i>-1?o=3===o.length-i?o.replace(",","."):o.replace(/,/g,""):n>-1&&3!==o.length-n&&(o=o.replace(/\./g,"")),{name:l,value:parseFloat(o)||0}}(t.cart_item),n=o.g._googlesitekit.easyDigitalDownloadsCurrency;null===(r=o.g._googlesitekit)||void 0===r||null===(a=r.gtagEvent)||void 0===a||a.call(r,"add_to_cart",{currency:n,value:i,items:[{item_name:l,price:i}]})}),null!==(t=o.g._googlesitekit)&&void 0!==t&&t.gtagUserData&&null!==(r=o.g._googlesitekit)&&void 0!==r&&null!==(r=r.edddata)&&void 0!==r&&null!==(r=r.purchase)&&void 0!==r&&r.user_data&&(null===(a=o.g._googlesitekit)||void 0===a||null===(l=a.gtagEvent)||void 0===l||l.call(a,"purchase",{user_data:o.g._googlesitekit.edddata.purchase.user_data})))})();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(()=>{"use strict";var e={};function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function n(e,t,n){return(t=function(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var n=t.call(e,"string");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}();const r="email",i="phone",l="name",a={[r]:["email","e-mail","mail","email address"],[i]:["phone","tel","mobile","cell","telephone","phone number"],[l]:["name","full-name","full name","full_name","fullname","first-name","first name","first_name","firstname","last-name","last name","last_name","lastname","given-name","given name","given_name","givenname","family-name","family name","family_name","familyname","fname","lname","first","last","your-name","your name"]};function o(e){return e&&"string"==typeof e?e.trim().toLowerCase():""}function u(e){const t=o(e),n=t.lastIndexOf("@");if(-1===n)return t;const r=t.slice(n+1);if(["gmail.com","googlemail.com"].includes(r)){const e=t.slice(0,n).replace(/\./g,"");return"".concat(e,"@").concat(r)}return t}function s(e){const t=o(e),n=t.replace(/\D/g,"");return t.startsWith("+")?"+".concat(n):n}function c(e){const r=e.filter(e=>{let{type:t}=e;return t===l}).map(e=>{let{value:t}=e;return o(t)}).filter(Boolean);if(!r.length)return;const[i,...a]=1===r.length?r[0].split(" "):r;return function(e){for(var r=1;r<arguments.length;r++){var i=null!=arguments[r]?arguments[r]:{};r%2?t(Object(i),!0).forEach(function(t){n(e,t,i[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):t(Object(i)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))})}return e}({first_name:i},(null==a?void 0:a.length)>0?{last_name:a.join(" ")}:{})}function f(e){var t;return null===(t=e.find(e=>{let{type:t}=e;return t===r}))||void 0===t?void 0:t.value}function m(e){var t;return null===(t=e.find(e=>{let{type:t}=e;return t===i}))||void 0===t?void 0:t.value}((t,n,v)=>{if(!t||!n||!v)return;const g=n.Object.extend({initialize(){this.listenTo(v.Radio.channel("forms"),"submit:response",this.actionSubmit)},actionSubmit(t){var n,v,g;const y=(null===(n=e.g._googlesitekit)||void 0===n?void 0:n.gtagUserData)?(d=t.data.fields,function(e){const t=[["address",c(e)],["email",f(e)],["phone_number",m(e)]].filter(e=>{let[,t]=e;return t});if(0!==t.length)return Object.fromEntries(t)}(Object.values(d).map(e=>{var t;const{label:n,type:c,value:f,key:m}=e;return function(e){let{type:t,name:n,value:c,label:f}=e||{};switch(t=o(t),n=o(n),c=o(c),f=function(e){return e&&"string"==typeof e?e.trim().toLowerCase().replace(/\s*\*+\s*$/,"").replace(/\s*\(required\)\s*$/i,"").replace(/\s*:\s*$/,"").trim():""}(f),t){case"email":return{type:r,value:u(c)};case"tel":return{type:i,value:s(c)}}return function(e){if(!e)return!1;const t=u(e);return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}(c)||a[r].includes(n)||a[r].includes(f)?{type:r,value:u(c)}:a[i].includes(n)||a[i].includes(f)?{type:i,value:s(c)}:a[l].includes(n)||a[l].includes(f)?{type:l,value:o(c)}:function(e){if(!e)return!1;if(!function(e){const t=e.replace(/\D/g,"");return!(t.length<7||t.length<e.length/2)&&/^[\s\-()+.\d]*$/.test(e)}(e))return!1;const t=s(e);if(!/^\+?\d{7,}$/.test(t))return!1;const n=/[\s\-()+.]/.test(e),r=e.trim().startsWith("+");return!(!n&&!r)}(c)?{type:i,value:s(c)}:null}({label:n,type:null!==(t=p[c])&&void 0!==t?t:c,value:f,name:m})}).filter(Boolean))):void 0;var d;null===(v=e.g._googlesitekit)||void 0===v||null===(g=v.gtagEvent)||void 0===g||g.call(v,"submit_lead_form",y?{user_data:y}:void 0)}});t(document).ready(()=>{new g})})(e.g.jQuery,e.g.Marionette,e.g.Backbone);const p={phone:"tel",textbox:"text"}})();

View File

@@ -0,0 +1 @@
(()=>{"use strict";var e={};function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function n(e,t,n){return(t=function(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var n=t.call(e,"string");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}();const r="email",i="phone",l="name",a={[r]:["email","e-mail","mail","email address"],[i]:["phone","tel","mobile","cell","telephone","phone number"],[l]:["name","full-name","full name","full_name","fullname","first-name","first name","first_name","firstname","last-name","last name","last_name","lastname","given-name","given name","given_name","givenname","family-name","family name","family_name","familyname","fname","lname","first","last","your-name","your name"]};function o(e){return e&&"string"==typeof e?e.trim().toLowerCase():""}function u(e){const t=o(e),n=t.lastIndexOf("@");if(-1===n)return t;const r=t.slice(n+1);if(["gmail.com","googlemail.com"].includes(r)){const e=t.slice(0,n).replace(/\./g,"");return"".concat(e,"@").concat(r)}return t}function c(e){const t=o(e),n=t.replace(/\D/g,"");return t.startsWith("+")?"+".concat(n):n}function s(e){const r=e.filter(e=>{let{type:t}=e;return t===l}).map(e=>{let{value:t}=e;return o(t)}).filter(Boolean);if(!r.length)return;const[i,...a]=1===r.length?r[0].split(" "):r;return function(e){for(var r=1;r<arguments.length;r++){var i=null!=arguments[r]?arguments[r]:{};r%2?t(Object(i),!0).forEach(function(t){n(e,t,i[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):t(Object(i)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))})}return e}({first_name:i},(null==a?void 0:a.length)>0?{last_name:a.join(" ")}:{})}function f(e){var t;return null===(t=e.find(e=>{let{type:t}=e;return t===r}))||void 0===t?void 0:t.value}function m(e){var t;return null===(t=e.find(e=>{let{type:t}=e;return t===i}))||void 0===t?void 0:t.value}e.g.document.addEventListener("om.Analytics.track",t=>{let{detail:n}=t;if("conversion"===n.Analytics.type){var p,v,g,y;const t=(null===(p=e.g._googlesitekit)||void 0===p?void 0:p.gtagUserData)&&null!==(v=n.Campaign)&&void 0!==v&&v.Form?function(e){if(!e||!e.inputs)return;const t=Array.isArray(e.inputs)?e.inputs:Object.values(e.inputs);return t.length?function(e){const t=[["address",s(e)],["email",f(e)],["phone_number",m(e)]].filter(e=>{let[,t]=e;return t});if(0!==t.length)return Object.fromEntries(t)}(t.map(e=>{var t;if("hidden"===e.type)return null;const n=e.id?null===(t=document.querySelector("label[for='".concat(e.id,"']")))||void 0===t?void 0:t.textContent:e.placeholder||"";return function(e){let{type:t,name:n,value:s,label:f}=e||{};switch(t=o(t),n=o(n),s=o(s),f=function(e){return e&&"string"==typeof e?e.trim().toLowerCase().replace(/\s*\*+\s*$/,"").replace(/\s*\(required\)\s*$/i,"").replace(/\s*:\s*$/,"").trim():""}(f),t){case"email":return{type:r,value:u(s)};case"tel":return{type:i,value:c(s)}}return function(e){if(!e)return!1;const t=u(e);return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}(s)||a[r].includes(n)||a[r].includes(f)?{type:r,value:u(s)}:a[i].includes(n)||a[i].includes(f)?{type:i,value:c(s)}:a[l].includes(n)||a[l].includes(f)?{type:l,value:o(s)}:function(e){if(!e)return!1;if(!function(e){const t=e.replace(/\D/g,"");return!(t.length<7||t.length<e.length/2)&&/^[\s\-()+.\d]*$/.test(e)}(e))return!1;const t=c(e);if(!/^\+?\d{7,}$/.test(t))return!1;const n=/[\s\-()+.]/.test(e),r=e.trim().startsWith("+");return!(!n&&!r)}(s)?{type:i,value:c(s)}:null}({type:e.type,name:e.name,value:e.value,label:n})}).filter(Boolean)):void 0}(n.Campaign.Form):null,d={campaignID:n.Campaign.id,campaignType:n.Campaign.type};t&&(d.user_data=t),null===(g=e.g._googlesitekit)||void 0===g||null===(y=g.gtagEvent)||void 0===y||y.call(g,"submit_lead_form",d)}})})();

View File

@@ -0,0 +1 @@
(()=>{"use strict";var e={};function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function n(e,t,n){return(t=function(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var n=t.call(e,"string");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}();const r="email",i="phone",l="name",o={[r]:["email","e-mail","mail","email address"],[i]:["phone","tel","mobile","cell","telephone","phone number"],[l]:["name","full-name","full name","full_name","fullname","first-name","first name","first_name","firstname","last-name","last name","last_name","lastname","given-name","given name","given_name","givenname","family-name","family name","family_name","familyname","fname","lname","first","last","your-name","your name"]};function a(e){return e&&"string"==typeof e?e.trim().toLowerCase():""}function u(e){const t=a(e),n=t.lastIndexOf("@");if(-1===n)return t;const r=t.slice(n+1);if(["gmail.com","googlemail.com"].includes(r)){const e=t.slice(0,n).replace(/\./g,"");return"".concat(e,"@").concat(r)}return t}function c(e){const t=a(e),n=t.replace(/\D/g,"");return t.startsWith("+")?"+".concat(n):n}function s(e){const r=e.filter(e=>{let{type:t}=e;return t===l}).map(e=>{let{value:t}=e;return a(t)}).filter(Boolean);if(!r.length)return;const[i,...o]=1===r.length?r[0].split(" "):r;return function(e){for(var r=1;r<arguments.length;r++){var i=null!=arguments[r]?arguments[r]:{};r%2?t(Object(i),!0).forEach(function(t){n(e,t,i[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):t(Object(i)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))})}return e}({first_name:i},(null==o?void 0:o.length)>0?{last_name:o.join(" ")}:{})}function f(e){var t;return null===(t=e.find(e=>{let{type:t}=e;return t===r}))||void 0===t?void 0:t.value}function m(e){var t;return null===(t=e.find(e=>{let{type:t}=e;return t===i}))||void 0===t?void 0:t.value}var p,v;p=e.g.jQuery,v=e.g.PUM,p&&v&&v.hooks.addAction("pum.integration.form.success",(t,n)=>{var p,v,g;const y=(null===(p=e.g._googlesitekit)||void 0===p?void 0:p.gtagUserData)&&(b=n.formProvider,!d.includes(b))?function(e){if(!(e=e instanceof HTMLFormElement?e:e[0]))return;const t=new FormData(e);return function(e){const t=[["address",s(e)],["email",f(e)],["phone_number",m(e)]].filter(e=>{let[,t]=e;return t});if(0!==t.length)return Object.fromEntries(t)}(Array.from(t.entries()).map(t=>{var n,s;let[f,m]=t;const p=e.querySelector("[name='".concat(f,"']")),v=null==p?void 0:p.type;return"hidden"===v?null:function(e){let{type:t,name:n,value:s,label:f}=e||{};switch(t=a(t),n=a(n),s=a(s),f=function(e){return e&&"string"==typeof e?e.trim().toLowerCase().replace(/\s*\*+\s*$/,"").replace(/\s*\(required\)\s*$/i,"").replace(/\s*:\s*$/,"").trim():""}(f),t){case"email":return{type:r,value:u(s)};case"tel":return{type:i,value:c(s)}}return function(e){if(!e)return!1;const t=u(e);return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}(s)||o[r].includes(n)||o[r].includes(f)?{type:r,value:u(s)}:o[i].includes(n)||o[i].includes(f)?{type:i,value:c(s)}:o[l].includes(n)||o[l].includes(f)?{type:l,value:a(s)}:function(e){if(!e)return!1;if(!function(e){const t=e.replace(/\D/g,"");return!(t.length<7||t.length<e.length/2)&&/^[\s\-()+.\d]*$/.test(e)}(e))return!1;const t=c(e);if(!/^\+?\d{7,}$/.test(t))return!1;const n=/[\s\-()+.]/.test(e),r=e.trim().startsWith("+");return!(!n&&!r)}(s)?{type:i,value:c(s)}:null}({type:v,label:(null!=p&&p.id?null===(n=e.querySelector("label[for='".concat(null==p?void 0:p.id,"']")))||void 0===n?void 0:n.textContent:null)||(null==p||null===(s=p.closest("label"))||void 0===s?void 0:s.textContent),name:f,value:m})}).filter(Boolean))}(t):void 0;var b;null===(v=e.g._googlesitekit)||void 0===v||null===(g=v.gtagEvent)||void 0===g||g.call(v,"submit_lead_form",y?{user_data:y}:void 0)});const d=["wpforms","contactform7","ninjaforms","mc4wp"]})();

View File

@@ -0,0 +1 @@
(()=>{var t={};t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),((o,n)=>{if(!o)return;const{currency:e,products:i,purchase:a,add_to_cart:l,eventsToTrack:c}=(null===(n=t.g._googlesitekit)||void 0===n?void 0:n.wcdata)||{},r=null==c?void 0:c.includes("add_to_cart"),d=null==c?void 0:c.includes("purchase");if(l&&r){var u,s;const{price:o}=l,n=f(o,e,l);null===(u=t.g._googlesitekit)||void 0===u||null===(s=u.gtagEvent)||void 0===s||s.call(u,"add_to_cart",n)}if(a&&d){var g,_,p;const{id:o,totals:n,items:e,user_data:i}=a,l=f(n.total_price,n.currency_code,e,o,n.shipping_total,n.tax_total);null!==(g=t.g._googlesitekit)&&void 0!==g&&g.gtagUserData&&i&&(l.user_data=i),null===(_=t.g._googlesitekit)||void 0===_||null===(p=_.gtagEvent)||void 0===p||p.call(_,"purchase",l)}const v=o("body");function f(t,o,n){let e=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,i=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,a=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null;const l={value:m(t),currency:o,items:[]};if(e&&(l.transaction_id=e),"number"==typeof i&&(l.shipping=i),"number"==typeof a&&(l.tax=a),n&&n.length)for(const t of n)l.items.push(h(t));else n&&n.id&&(l.items=[h(n)]);return l}function h(t){const{id:o,name:n,price:e,variation:i,quantity:a,categories:l}=t,c={item_id:o,item_name:n,price:m(e)};if(a&&(c.quantity=a),i&&(c.item_variant=i),l&&null!=l&&l.length){let t=1;for(const o of l)c[t>1?"item_category".concat(t):"item_category"]=o.name,t++}return c}function m(t){let o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:2;return parseInt(t,10)/10**o}r&&(v.on("added_to_cart",(o,n,a,l)=>{var c,r;const d=parseInt(l.data("product_id"),10);if(!d)return;const u=(null==i?void 0:i.find(t=>(null==t?void 0:t.id)===d))||{},{price:s}=u,g=f(s,e,u);null===(c=t.g._googlesitekit)||void 0===c||null===(r=c.gtagEvent)||void 0===r||r.call(c,"add_to_cart",g)}),o(".products-block-post-template .product, .wc-block-product-template .product").each(function(){const n=o(this),a=parseInt(n.find("[data-product_id]").attr("data-product_id"),10);a&&n.on("click",n=>{var l,c;const r=o(n.target).closest(".wc-block-components-product-button [data-product_id]");if(!r.length||!r.hasClass("add_to_cart_button")||r.hasClass("product_type_variable"))return;const d=(null==i?void 0:i.find(t=>(null==t?void 0:t.id)===a))||{},{price:u}=d,s=f(u,e,d);null===(l=t.g._googlesitekit)||void 0===l||null===(c=l.gtagEvent)||void 0===c||c.call(l,"add_to_cart",s)})}))})(t.g.jQuery)})();

View File

@@ -0,0 +1 @@
(()=>{"use strict";var e={};function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function n(e,t,n){return(t=function(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var n=t.call(e,"string");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}();const r="email",i="phone",l="name",o={[r]:["email","e-mail","mail","email address"],[i]:["phone","tel","mobile","cell","telephone","phone number"],[l]:["name","full-name","full name","full_name","fullname","first-name","first name","first_name","firstname","last-name","last name","last_name","lastname","given-name","given name","given_name","givenname","family-name","family name","family_name","familyname","fname","lname","first","last","your-name","your name"]};function a(e){return e&&"string"==typeof e?e.trim().toLowerCase():""}function u(e){const t=a(e),n=t.lastIndexOf("@");if(-1===n)return t;const r=t.slice(n+1);if(["gmail.com","googlemail.com"].includes(r)){const e=t.slice(0,n).replace(/\./g,"");return"".concat(e,"@").concat(r)}return t}function c(e){const t=a(e),n=t.replace(/\D/g,"");return t.startsWith("+")?"+".concat(n):n}function s(e){const r=e.filter(e=>{let{type:t}=e;return t===l}).map(e=>{let{value:t}=e;return a(t)}).filter(Boolean);if(!r.length)return;const[i,...o]=1===r.length?r[0].split(" "):r;return function(e){for(var r=1;r<arguments.length;r++){var i=null!=arguments[r]?arguments[r]:{};r%2?t(Object(i),!0).forEach(function(t){n(e,t,i[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):t(Object(i)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))})}return e}({first_name:i},(null==o?void 0:o.length)>0?{last_name:o.join(" ")}:{})}function f(e){var t;return null===(t=e.find(e=>{let{type:t}=e;return t===r}))||void 0===t?void 0:t.value}function m(e){var t;return null===(t=e.find(e=>{let{type:t}=e;return t===i}))||void 0===t?void 0:t.value}var v;(v=e.g.jQuery)&&v(e.g.document.body).on("wpformsAjaxSubmitSuccess",t=>{var n,v,d;const p=(null===(n=e.g._googlesitekit)||void 0===n?void 0:n.gtagUserData)?function(e){if(!(e&&e instanceof HTMLFormElement))return;const t=new FormData(e);return function(e){const t=[["address",s(e)],["email",f(e)],["phone_number",m(e)]].filter(e=>{let[,t]=e;return t});if(0!==t.length)return Object.fromEntries(t)}(Array.from(t.entries()).map(t=>{var n,s,f,m,v,d;let[p,y]=t,g=e.querySelector("[name='".concat(p,"']"));"hidden"===(null===(n=g)||void 0===n?void 0:n.type)&&"hidden"!==(null===(s=g)||void 0===s||null===(s=s.previousSibling)||void 0===s?void 0:s.type)&&(g=g.previousSibling);const b=null===(f=g)||void 0===f?void 0:f.type;return"hidden"===b||"submit"===b?null:function(e){let{type:t,name:n,value:s,label:f}=e||{};switch(t=a(t),n=a(n),s=a(s),f=function(e){return e&&"string"==typeof e?e.trim().toLowerCase().replace(/\s*\*+\s*$/,"").replace(/\s*\(required\)\s*$/i,"").replace(/\s*:\s*$/,"").trim():""}(f),t){case"email":return{type:r,value:u(s)};case"tel":return{type:i,value:c(s)}}return function(e){if(!e)return!1;const t=u(e);return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}(s)||o[r].includes(n)||o[r].includes(f)?{type:r,value:u(s)}:o[i].includes(n)||o[i].includes(f)?{type:i,value:c(s)}:o[l].includes(n)||o[l].includes(f)?{type:l,value:a(s)}:function(e){if(!e)return!1;if(!function(e){const t=e.replace(/\D/g,"");return!(t.length<7||t.length<e.length/2)&&/^[\s\-()+.\d]*$/.test(e)}(e))return!1;const t=c(e);if(!/^\+?\d{7,}$/.test(t))return!1;const n=/[\s\-()+.]/.test(e),r=e.trim().startsWith("+");return!(!n&&!r)}(s)?{type:i,value:c(s)}:null}({type:b,label:null!==(m=g)&&void 0!==m&&m.id?null===(v=e.querySelector("label[for='".concat(null===(d=g)||void 0===d?void 0:d.id,"']")))||void 0===v?void 0:v.textContent:void 0,name:p,value:y})}).filter(Boolean))}(t.target):null;null===(v=e.g._googlesitekit)||void 0===v||null===(d=v.gtagEvent)||void 0===d||d.call(v,"submit_lead_form",p?{user_data:p}:void 0)})})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(globalThis.__googlesitekit_webpackJsonp=globalThis.__googlesitekit_webpackJsonp||[]).push([[986],{28481:(s,e,t)=>{t.p=`${t.g._googlesitekitBaseData.assetsURL}js/`,async function(){void 0===t.g.IntersectionObserver&&await t.e(315).then(t.t.bind(t,91315,23))}()}},s=>{var e;e=28481,s(s.s=e)}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(()=>{"use strict";var e,r,_,t,a={},i={};function __webpack_require__(e){var r=i[e];if(void 0!==r)return r.exports;var _=i[e]={id:e,loaded:!1,exports:{}};return a[e].call(_.exports,_,_.exports,__webpack_require__),_.loaded=!0,_.exports}__webpack_require__.m=a,e=[],__webpack_require__.O=(r,_,t,a)=>{if(!_){var i=1/0;for(n=0;n<e.length;n++){for(var[_,t,a]=e[n],o=!0,c=0;c<_.length;c++)(!1&a||i>=a)&&Object.keys(__webpack_require__.O).every(e=>__webpack_require__.O[e](_[c]))?_.splice(c--,1):(o=!1,a<i&&(i=a));if(o){e.splice(n--,1);var u=t();void 0!==u&&(r=u)}}return r}a=a||0;for(var n=e.length;n>0&&e[n-1][2]>a;n--)e[n]=e[n-1];e[n]=[_,t,a]},__webpack_require__.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(r,{a:r}),r},_=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,__webpack_require__.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var a=Object.create(null);__webpack_require__.r(a);var i={};r=r||[null,_({}),_([]),_(_)];for(var o=2&t&&e;"object"==typeof o&&!~r.indexOf(o);o=_(o))Object.getOwnPropertyNames(o).forEach(r=>i[r]=()=>e[r]);return i.default=()=>e,__webpack_require__.d(a,i),a},__webpack_require__.d=(e,r)=>{for(var _ in r)__webpack_require__.o(r,_)&&!__webpack_require__.o(e,_)&&Object.defineProperty(e,_,{enumerable:!0,get:r[_]})},__webpack_require__.f={},__webpack_require__.e=e=>Promise.all(Object.keys(__webpack_require__.f).reduce((r,_)=>(__webpack_require__.f[_](e,r),r),[])),__webpack_require__.u=e=>e+"-"+{146:"0b0fbc579abcbffcc013",201:"bfc0d3aab9dc3f2f6822",314:"5de05bb0772a9b99adb1",315:"8efb84c505eda33243df",379:"ab0828b6563a66528385",590:"d8b228a1522eb952e6a2",640:"cb6aaf742e94e522352c",909:"27b62c0405cff1d1b57f"}[e]+".js",__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.hmd=e=>((e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set:()=>{throw new Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e),__webpack_require__.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},__webpack_require__.l=(e,r,_,a)=>{if(t[e])t[e].push(r);else{var i,o;if(void 0!==_)for(var c=document.getElementsByTagName("script"),u=0;u<c.length;u++){var n=c[u];if(n.getAttribute("src")==e){i=n;break}}i||(o=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,__webpack_require__.nc&&i.setAttribute("nonce",__webpack_require__.nc),i.src=e),t[e]=[r];var b=(r,_)=>{i.onerror=i.onload=null,clearTimeout(p);var a=t[e];if(delete t[e],i.parentNode&&i.parentNode.removeChild(i),a&&a.forEach(e=>e(_)),r)return r(_)},p=setTimeout(b.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=b.bind(null,i.onerror),i.onload=b.bind(null,i.onload),o&&document.head.appendChild(i)}},__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),__webpack_require__.p="",(()=>{var e={121:0};__webpack_require__.f.j=(r,_)=>{var t=__webpack_require__.o(e,r)?e[r]:void 0;if(0!==t)if(t)_.push(t[2]);else if(121!=r){var a=new Promise((_,a)=>t=e[r]=[_,a]);_.push(t[2]=a);var i=__webpack_require__.p+__webpack_require__.u(r),o=new Error;__webpack_require__.l(i,_=>{if(__webpack_require__.o(e,r)&&(0!==(t=e[r])&&(e[r]=void 0),t)){var a=_&&("load"===_.type?"missing":_.type),i=_&&_.target&&_.target.src;o.message="Loading chunk "+r+" failed.\n("+a+": "+i+")",o.name="ChunkLoadError",o.type=a,o.request=i,t[1](o)}},"chunk-"+r,r)}else e[r]=0},__webpack_require__.O.j=r=>0===e[r];var r=(r,_)=>{var t,a,[i,o,c]=_,u=0;if(i.some(r=>0!==e[r])){for(t in o)__webpack_require__.o(o,t)&&(__webpack_require__.m[t]=o[t]);if(c)var n=c(__webpack_require__)}for(r&&r(_);u<i.length;u++)a=i[u],__webpack_require__.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return __webpack_require__.O(n)},_=globalThis.__googlesitekit_webpackJsonp=globalThis.__googlesitekit_webpackJsonp||[];_.forEach(r.bind(null,0)),_.push=r.bind(null,_.push.bind(_))})()})();

View File

@@ -0,0 +1,11 @@
<?php
/**
* @package Google\Site_Kit
* @copyright 2025 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
return array(
'features' => array( 'adsPax','googleTagGateway','gtagUserData','privacySandboxModule','proactiveUserEngagement','setupFlowRefresh' ),
);

View File

@@ -0,0 +1,68 @@
<?php
/**
* @package Google\Site_Kit
* @copyright 2025 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
return array(
'googlesitekit-consent-mode' => array( "googlesitekit-consent-mode-bc2e26cfa69fcd4a8261.js", null ),
'googlesitekit-events-provider-contact-form-7' => array( "googlesitekit-events-provider-contact-form-7-40476021fb6e59177033.js", null ),
'googlesitekit-events-provider-easy-digital-downloads' => array( "googlesitekit-events-provider-easy-digital-downloads-85026152b9292f580065.js", null ),
'googlesitekit-events-provider-mailchimp' => array( "googlesitekit-events-provider-mailchimp-766d83b09856fae7cf87.js", null ),
'googlesitekit-events-provider-ninja-forms' => array( "googlesitekit-events-provider-ninja-forms-1bcc43dc33ac5df43991.js", null ),
'googlesitekit-events-provider-optin-monster' => array( "googlesitekit-events-provider-optin-monster-e6fa11a9d13d20a7ece5.js", null ),
'googlesitekit-events-provider-popup-maker' => array( "googlesitekit-events-provider-popup-maker-561440dc30d29e4d73d1.js", null ),
'googlesitekit-events-provider-woocommerce' => array( "googlesitekit-events-provider-woocommerce-56777fd664fb7392edc2.js", null ),
'googlesitekit-events-provider-wpforms' => array( "googlesitekit-events-provider-wpforms-ed443a3a3d45126a22ce.js", null ),
'googlesitekit-i18n' => array( "googlesitekit-i18n-f0c172b703253d0876a2.js", null ),
'analytics-advanced-tracking' => array( "analytics-advanced-tracking-78f90889e2d99b97b685.js", null ),
'reader-revenue-manager/block-editor-plugin/index' => array( "reader-revenue-manager/block-editor-plugin/index.js", null ),
'reader-revenue-manager/block-editor-plugin/editor-styles' => array( "reader-revenue-manager/block-editor-plugin/editor-styles.js", null ),
'reader-revenue-manager/contribute-with-google/index' => array( "reader-revenue-manager/contribute-with-google/index.js", null ),
'reader-revenue-manager/contribute-with-google/non-site-kit-user' => array( "reader-revenue-manager/contribute-with-google/non-site-kit-user.js", null ),
'reader-revenue-manager/subscribe-with-google/index' => array( "reader-revenue-manager/subscribe-with-google/index.js", null ),
'reader-revenue-manager/subscribe-with-google/non-site-kit-user' => array( "reader-revenue-manager/subscribe-with-google/non-site-kit-user.js", null ),
'reader-revenue-manager/common/editor-styles' => array( "reader-revenue-manager/common/editor-styles.js", null ),
'sign-in-with-google/index' => array( "sign-in-with-google/index.js", null ),
'sign-in-with-google/editor-styles' => array( "sign-in-with-google/editor-styles.js", null ),
'googlesitekit-admin-css' => array( "googlesitekit-admin-css-2d878b524029ec067eec.min.css", null ),
'googlesitekit-adminbar-css' => array( "googlesitekit-adminbar-css-d193e67e2cbecf306061.min.css", null ),
'googlesitekit-wp-dashboard-css' => array( "googlesitekit-wp-dashboard-css-bd43e70c4bc5ecdc3695.min.css", null ),
'googlesitekit-authorize-application-css' => array( "googlesitekit-authorize-application-css-5b98b536e7f34c6411c1.min.css", null ),
'googlesitekit-api' => array( "googlesitekit-api-4acd9eba95567bccec3c.js", null ),
'googlesitekit-data' => array( "googlesitekit-data-2868d5c75a96f60bd472.js", null ),
'googlesitekit-datastore-site' => array( "googlesitekit-datastore-site-3c39e3fbabbd2f01e016.js", null ),
'googlesitekit-datastore-user' => array( "googlesitekit-datastore-user-554efe90316700e16739.js", null ),
'googlesitekit-datastore-forms' => array( "googlesitekit-datastore-forms-d643ba5ba26668542bde.js", null ),
'googlesitekit-datastore-location' => array( "googlesitekit-datastore-location-a7fd5d1461e0562c934a.js", null ),
'googlesitekit-datastore-ui' => array( "googlesitekit-datastore-ui-ab5c239e3cf8b9ab02b0.js", null ),
'googlesitekit-modules' => array( "googlesitekit-modules-bee95690a7bf78bc07b4.js", null ),
'googlesitekit-notifications' => array( "googlesitekit-notifications-2736e03463ce2aa1bf04.js", null ),
'googlesitekit-widgets' => array( "googlesitekit-widgets-fa50a53f55279b0e87e6.js", null ),
'googlesitekit-modules-ads' => array( "googlesitekit-modules-ads-6547fa863cf79755997f.js", null ),
'googlesitekit-modules-adsense' => array( "googlesitekit-modules-adsense-8ffefc093acfb03d3939.js", null ),
'googlesitekit-modules-analytics-4' => array( "googlesitekit-modules-analytics-4-b55732b33a3e8ecabb92.js", null ),
'googlesitekit-modules-pagespeed-insights' => array( "googlesitekit-modules-pagespeed-insights-ce16569db5c5c3700246.js", null ),
'googlesitekit-modules-reader-revenue-manager' => array( "googlesitekit-modules-reader-revenue-manager-0eaef957f6a1121c2632.js", null ),
'googlesitekit-modules-search-console' => array( "googlesitekit-modules-search-console-a503191f619f430f2e1f.js", null ),
'googlesitekit-modules-sign-in-with-google' => array( "googlesitekit-modules-sign-in-with-google-7a7375c0e3afa603c730.js", null ),
'googlesitekit-modules-tagmanager' => array( "googlesitekit-modules-tagmanager-5cccb7a1aa3b8068a19d.js", null ),
'googlesitekit-user-input' => array( "googlesitekit-user-input-d8854daef0c71e933cbd.js", null ),
'googlesitekit-ad-blocking-recovery' => array( "googlesitekit-ad-blocking-recovery-04056d66d815c45fe98f.js", null ),
'googlesitekit-block-tracking' => array( "googlesitekit-block-tracking-1032e2e997b7685502b9.js", null ),
'googlesitekit-polyfills' => array( "googlesitekit-polyfills-8b59b9627a8949df6deb.js", null ),
'googlesitekit-components' => array( "googlesitekit-components-75585de31a179a264558.js", null ),
'googlesitekit-metric-selection' => array( "googlesitekit-metric-selection-fa2203e0c4f9ef557b3b.js", null ),
'googlesitekit-key-metrics-setup' => array( "googlesitekit-key-metrics-setup-b01b0e10038a0489334a.js", null ),
'googlesitekit-activation' => array( "googlesitekit-activation-cf67b1551538a8d25daf.js", null ),
'googlesitekit-adminbar' => array( "googlesitekit-adminbar-a78c323609736404fc2f.js", null ),
'googlesitekit-settings' => array( "googlesitekit-settings-d1c3d5b87d5d2f793f91.js", null ),
'googlesitekit-main-dashboard' => array( "googlesitekit-main-dashboard-28000cf844561eb9fe35.js", null ),
'googlesitekit-entity-dashboard' => array( "googlesitekit-entity-dashboard-676fd34c9ca371e65aae.js", null ),
'googlesitekit-splash' => array( "googlesitekit-splash-a647b5365ab8001fb00a.js", null ),
'googlesitekit-wp-dashboard' => array( "googlesitekit-wp-dashboard-4d2dbf3c1b69b854232d.js", null ),
'googlesitekit-runtime' => array( "runtime-4300400569c01237fc9a.js", null ),
'googlesitekit-vendor' => array( "googlesitekit-vendor-599848015879dcc843df.js", null ),
);

View File

@@ -0,0 +1,125 @@
<?php
/**
* Plugin main file.
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*
* @wordpress-plugin
* Plugin Name: Site Kit by Google
* Plugin URI: https://sitekit.withgoogle.com
* Description: Site Kit is a one-stop solution for WordPress users to use everything Google has to offer to make them successful on the web.
* Version: 1.165.0
* Requires at least: 5.2
* Requires PHP: 7.4
* Author: Google
* Author URI: https://opensource.google.com
* License: Apache License 2.0
* License URI: https://www.apache.org/licenses/LICENSE-2.0
* Text Domain: google-site-kit
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
// Define most essential constants.
define( 'GOOGLESITEKIT_VERSION', '1.165.0' );
define( 'GOOGLESITEKIT_PLUGIN_MAIN_FILE', __FILE__ );
define( 'GOOGLESITEKIT_PHP_MINIMUM', '7.4.0' );
define( 'GOOGLESITEKIT_WP_MINIMUM', '5.2.0' );
/**
* Handles plugin activation.
*
* Throws an error if the plugin is activated with an insufficient version of PHP.
*
* @since 1.0.0
* @since 1.3.0 Minimum required version of PHP raised to 5.6
* @since 1.125.0 Minimum required version of PHP raised to 7.4
* @access private
*
* @param bool $network_wide Whether to activate network-wide.
*/
function googlesitekit_activate_plugin( $network_wide ) {
if ( version_compare( PHP_VERSION, GOOGLESITEKIT_PHP_MINIMUM, '<' ) ) {
wp_die(
/* translators: %s: version number */
esc_html( sprintf( __( 'Site Kit requires PHP version %s or higher', 'google-site-kit' ), GOOGLESITEKIT_PHP_MINIMUM ) ),
esc_html__( 'Error Activating', 'google-site-kit' )
);
}
if ( version_compare( get_bloginfo( 'version' ), GOOGLESITEKIT_WP_MINIMUM, '<' ) ) {
wp_die(
/* translators: %s: version number */
esc_html( sprintf( __( 'Site Kit requires WordPress version %s or higher', 'google-site-kit' ), GOOGLESITEKIT_WP_MINIMUM ) ),
esc_html__( 'Error Activating', 'google-site-kit' )
);
}
if ( $network_wide ) {
return;
}
do_action( 'googlesitekit_activation', $network_wide );
}
register_activation_hook( __FILE__, 'googlesitekit_activate_plugin' );
/**
* Handles plugin deactivation.
*
* @since 1.0.0
* @access private
*
* @param bool $network_wide Whether to deactivate network-wide.
*/
function googlesitekit_deactivate_plugin( $network_wide ) {
if ( version_compare( PHP_VERSION, GOOGLESITEKIT_PHP_MINIMUM, '<' ) ) {
return;
}
if ( $network_wide ) {
return;
}
do_action( 'googlesitekit_deactivation', $network_wide );
}
register_deactivation_hook( __FILE__, 'googlesitekit_deactivate_plugin' );
/**
* Resets opcache if possible.
*
* @since 1.3.0
* @access private
*/
function googlesitekit_opcache_reset() {
if ( version_compare( PHP_VERSION, GOOGLESITEKIT_PHP_MINIMUM, '<' ) ) {
return;
}
if ( ! function_exists( 'opcache_reset' ) ) {
return;
}
if ( ! empty( ini_get( 'opcache.restrict_api' ) ) && strpos( __FILE__, ini_get( 'opcache.restrict_api' ) ) !== 0 ) {
return;
}
// `opcache_reset` is prohibited on the WordPress VIP platform due to memory corruption.
if ( defined( 'WPCOM_IS_VIP_ENV' ) && WPCOM_IS_VIP_ENV ) {
return;
}
opcache_reset(); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.opcache_opcache_reset
}
add_action( 'upgrader_process_complete', 'googlesitekit_opcache_reset' );
if (
version_compare( PHP_VERSION, GOOGLESITEKIT_PHP_MINIMUM, '>=' ) &&
version_compare( get_bloginfo( 'version' ), GOOGLESITEKIT_WP_MINIMUM, '>=' )
) {
require_once plugin_dir_path( __FILE__ ) . 'includes/loader.php';
}

View File

@@ -0,0 +1,614 @@
<?php
/**
* GoogleTagGatewayServing measurement request proxy file
*
* @package Google\GoogleTagGatewayLibrary\Proxy
* @copyright 2024 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*
* @version a8ee614
*
* NOTICE: This file has been modified from its original version in accordance with the Apache License, Version 2.0.
*/
// This file should run in isolation from any other PHP file. This means using
// minimal to no external dependencies, which leads us to suppressing the
// following linting rules:
//
// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols
// phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses
/* Start of Site Kit modified code. */
namespace {
if ( isset( $_GET['healthCheck'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
echo 'ok';
exit;
}
// Return early when including to use in external health check.
// All classes will be defined but no further statements will be executed.
if ( defined( 'GOOGLESITEKIT_GTG_ENDPOINT_HEALTH_CHECK' ) ) {
return;
}
}
/* End of Site Kit modified code. */
namespace Google\GoogleTagGatewayLibrary\Proxy {
use Google\GoogleTagGatewayLibrary\Http\RequestHelper;
use Google\GoogleTagGatewayLibrary\Http\ServerRequestContext;
/** Runner class to execute the proxy request. */
final class Runner
{
/**
* Request helper functions.
*
* @var RequestHelper
*/
private RequestHelper $helper;
/**
* Measurement request helper.
*
* @var Measurement
*/
private Measurement $measurement;
/**
* Constructor.
*
* @param RequestHelper $helper
*/
public function __construct(RequestHelper $helper, Measurement $measurement)
{
$this->helper = $helper;
$this->measurement = $measurement;
}
/** Run the core logic for forwarding traffic. */
public function run(): void
{
$response = $this->measurement->run();
$this->helper->setHeaders($response['headers']);
http_response_code($response['statusCode']);
echo $response['body'];
}
/** Create an instance of the runner with the system defaults. */
public static function create()
{
$helper = new RequestHelper();
$context = ServerRequestContext::create();
$measurement = new Measurement($helper, $context);
return new self($helper, $measurement);
}
}
}
namespace Google\GoogleTagGatewayLibrary\Http {
/**
* Isolates network requests and other methods like exit to inject into classes.
*/
class RequestHelper
{
/**
* Helper method to exit the script early and send back a status code.
*
* @param int $statusCode
*/
public function invalidRequest(int $statusCode): void
{
http_response_code($statusCode);
exit;
}
/**
* Set the headers from a headers array.
*
* @param string[] $headers
*/
public function setHeaders(array $headers): void
{
foreach ($headers as $header) {
if (!empty($header)) {
header($header);
}
}
}
/**
* Sanitizes a path to a URL path.
*
* This function performs two critical actions:
* 1. Extract ONLY the path component, discarding any scheme, host, port,
* user, pass, query, or fragment.
* Primary defense against Server-Side Request Forgery (SSRF).
* 2. Normalize the path to resolve directory traversal segments like
* '.' and '..'.
* Prevents path traversal attacks.
*
* @param string $pathInput The raw path string.
* @return string|false The sanitized and normalized URL path.
*/
public static function sanitizePathForUrl(string $pathInput): string
{
if (empty($pathInput)) {
return false;
}
// Normalize directory separators to forward slashes for Windows like directories.
$path = str_replace('\\', '/', $pathInput);
// 2. Normalize the path to resolve '..' and '.' segments.
$parts = [];
// Explode the path into segments. filter removes empty segments (e.g., from '//').
$segments = explode('/', trim($path, '/'));
foreach ($segments as $segment) {
if ($segment === '.' || $segment === '') {
// Ignore current directory and empty segments.
continue;
}
if ($segment === '..') {
// Go up one level by removing the last part.
if (array_pop($parts) === null) {
// If we try and traverse too far back, outside of the root
// directory, this is likely an invalid configuration so
// return false to have caller handle this error.
return false;
}
} else {
// Add the segment to our clean path.
$parts[] = rawurlencode($segment);
}
}
// Rebuild the final path.
$sanitizedPath = implode('/', $parts);
return '/' . $sanitizedPath;
}
/**
* Helper method to send requests depending on the PHP environment.
*
* @param string $method
* @param string $url
* @param array $headers
* @param string $body
*
* @return array{
* body: string,
* headers: string[],
* statusCode: int,
* }
*/
public function sendRequest(string $method, string $url, array $headers = [], ?string $body = null): array
{
if ($this->isCurlInstalled()) {
$response = $this->sendCurlRequest($method, $url, $headers, $body);
} else {
$response = $this->sendFileGetContents($method, $url, $headers, $body);
}
return $response;
}
/**
* Send a request using curl.
*
* @param string $method
* @param string $url
* @param array $headers
* @param string $body
*
* @return array{
* body: string,
* headers: string[],
* statusCode: int,
* }
*/
protected function sendCurlRequest(string $method, string $url, array $headers = [], ?string $body = null): array
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_URL, $url);
$method = strtoupper($method);
switch ($method) {
case 'GET':
curl_setopt($ch, CURLOPT_HTTPGET, true);
break;
case 'POST':
curl_setopt($ch, CURLOPT_POST, true);
break;
default:
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
break;
}
if (!empty($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
if (!empty($body)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$result = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headersString = substr($result, 0, $headerSize);
$headers = explode("\r\n", $headersString);
$headers = $this->normalizeHeaders($headers);
$body = substr($result, $headerSize);
curl_close($ch);
return array('body' => $body, 'headers' => $headers, 'statusCode' => $statusCode);
}
/**
* Send a request using file_get_contents.
*
* @param string $method
* @param string $url
* @param array $headers
* @param string $body
*
* @return array{
* body: string,
* headers: string[],
* statusCode: int,
* }
*/
protected function sendFileGetContents(string $method, string $url, array $headers = [], ?string $body = null): array
{
$httpContext = ['method' => strtoupper($method), 'follow_location' => 0, 'max_redirects' => 0, 'ignore_errors' => true];
if (!empty($headers)) {
$httpContext['header'] = implode("\r\n", $headers);
}
if (!empty($body)) {
$httpContext['content'] = $body;
}
$streamContext = stream_context_create(['http' => $httpContext]);
// Calling file_get_contents will set the variable $http_response_header
// within the local scope.
$result = file_get_contents($url, false, $streamContext);
/** @var string[] $headers */
$headers = $http_response_header ?? [];
$statusCode = 200;
if (!empty($headers)) {
// The first element in the headers array will be the HTTP version
// and status code used, parse out the status code and remove this
// value from the headers.
preg_match('/HTTP\/\d\.\d\s+(\d+)/', $headers[0], $statusHeader);
$statusCode = intval($statusHeader[1]) ?? 200;
}
$headers = $this->normalizeHeaders($headers);
return array('body' => $result, 'headers' => $headers, 'statusCode' => $statusCode);
}
protected function isCurlInstalled(): bool
{
return extension_loaded('curl');
}
/** @param string[] $headers */
protected function normalizeHeaders(array $headers): array
{
if (empty($headers)) {
return $headers;
}
// The first element in the headers array will be the HTTP version
// and status code used, this value is not needed in the headers.
array_shift($headers);
return $headers;
}
/**
* Takes a single URL query parameter which has not been encoded and
* ensures its key & value are encoded.
*
* @param string $parameter Query parameter to encode.
* @return string The new query parameter encoded.
*/
public static function encodeQueryParameter(string $parameter): string
{
list($key, $value) = explode('=', $parameter, 2) + ['', ''];
// We just manually encode to avoid any nuances that may occur as a
// result of `http_build_query`. One such nuance is that
// `http_build_query` will add an index to query parameters that
// are repeated through an array. We would only be able to store
// repeated values as an array as associative arrays cannot have the
// same key multiple times. This makes `http_build_query`
// undesirable as we should pass parameters through as they come in
// and not modify them or change the key.
$key = rawurlencode($key);
$value = rawurlencode($value);
return "{$key}={$value}";
}
}
}
namespace Google\GoogleTagGatewayLibrary\Http {
/** Request context populated with common server set values. */
final class ServerRequestContext
{
/**
* Server set associative array. Normally the same as $_SERVER.
*
* @var array
*/
private $serverParams;
/**
* Associative array of query parameters. Normally the same as $_GET
*
* @var array
*/
private $queryParams;
/**
* The current server request's body.
*
* @var string
*/
private $requestBody;
/**
* Constructor
*
* @param array $serverParams
* @param array $queryParams
* @param string $requestBody
*/
public function __construct(array $serverParams, array $queryParams, string $requestBody)
{
$this->serverParams = $serverParams;
$this->queryParams = $queryParams;
$this->requestBody = $requestBody;
}
/** Create an instance with the system defaults. */
public static function create()
{
$body = file_get_contents("php://input") ?? '';
return new self($_SERVER, $_GET, $body);
}
/**
* Fetch the current request's request body.
*
* @return string The current request body.
*/
public function getBody(): string
{
return $this->requestBody ?? '';
}
public function getRedirectorFile()
{
$redirectorFile = $this->serverParams['SCRIPT_NAME'] ?? '';
if (empty($redirectorFile)) {
return '';
}
return RequestHelper::sanitizePathForUrl($redirectorFile);
}
/**
* Get headers from the current request as an array of strings.
* Similar to how you set headers using the `headers` function.
*
* @param array $filterHeaders Filter out headers from the return value.
*/
public function getHeaders(array $filterHeaders = []): array
{
$headers = [];
// Extra headers not prefixed with `HTTP_`
$extraHeaders = ["CONTENT_TYPE" => 'content-type', "CONTENT_LENGTH" => 'content-length', "CONTENT_MD5" => 'content-md5'];
foreach ($this->serverParams as $key => $value) {
# Skip reserved headers
if (isset($filterHeaders[$key])) {
continue;
}
# All PHP request headers are available under the $_SERVER variable
# and have a key prefixed with `HTTP_` according to:
# https://www.php.net/manual/en/reserved.variables.server.php#refsect1-reserved.variables.server-description
$headerKey = '';
if (substr($key, 0, 5) === 'HTTP_') {
# PHP defaults to every header key being all capitalized.
# Format header key as lowercase with `-` as word separator.
# For example: cache-control
$headerKey = strtolower(str_replace('_', '-', substr($key, 5)));
} elseif (isset($extraHeaders[$key])) {
$headerKey = $extraHeaders[$key];
}
if (empty($headerKey) || empty($value)) {
continue;
}
$headers[] = "{$headerKey}: {$value}";
}
// Add extra x-forwarded-for if remote address is present.
if (isset($this->serverParams['REMOTE_ADDR'])) {
$headers[] = "x-forwarded-for: {$this->serverParams['REMOTE_ADDR']}";
}
// Add extra geo if present in the query parameters.
$geo = $this->getGeoParam();
if (!empty($geo)) {
$headers[] = "x-forwarded-countryregion: {$geo}";
}
return $headers;
}
/**
* Get the request method made for the current request.
*
* @return string
*/
public function getMethod()
{
return @$this->serverParams['REQUEST_METHOD'] ?: 'GET';
}
/** Get and validate the geo parameter from the request. */
public function getGeoParam()
{
$geo = $this->queryParams['geo'] ?? '';
// Basic geo validation
if (!preg_match('/^[A-Za-z0-9-]+$/', $geo)) {
return '';
}
return $geo;
}
/** Get the tag id query parameter from the request. */
public function getTagId()
{
$tagId = $this->queryParams['id'] ?? '';
// Validate tagId
if (!preg_match('/^[A-Za-z0-9-]+$/', $tagId)) {
return '';
}
return $tagId;
}
/** Get the destination query parameter from the request. */
public function getDestination()
{
$path = $this->queryParams['s'] ?? '';
// When measurement path is present it might accidentally pass an empty
// path character depending on how the url rules are processed so as a
// safety when path is empty we should assume that it is a request to
// the root.
if (empty($path)) {
$path = '/';
}
// Remove reserved query parameters from the query string
$params = $this->queryParams;
unset($params['id'], $params['s'], $params['geo'], $params['mpath']);
$containsQueryParameters = strpos($path, '?') !== false;
if ($containsQueryParameters) {
list($path, $query) = explode('?', $path, 2);
$path .= '?' . RequestHelper::encodeQueryParameter($query);
}
if (!empty($params)) {
$paramSeparator = $containsQueryParameters ? '&' : '?';
$path .= $paramSeparator . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
}
return $path;
}
/**Get the measurement path query parameter from the request. */
public function getMeasurementPath()
{
return $this->queryParams['mpath'] ?? '';
}
}
}
namespace Google\GoogleTagGatewayLibrary\Proxy {
use Google\GoogleTagGatewayLibrary\Http\RequestHelper;
use Google\GoogleTagGatewayLibrary\Http\ServerRequestContext;
/** Core measurement.php logic. */
final class Measurement
{
private const TAG_ID_QUERY = '?id=';
private const GEO_QUERY = '&geo=';
private const PATH_QUERY = '&s=';
private const FPS_PATH = 'PHP_GTG_REPLACE_PATH';
/**
* Reserved request headers that should not be sent as part of the
* measurement request.
*
* @var array<string, bool>
*/
private const RESERVED_HEADERS = [
# PHP managed headers which will be auto populated by curl or file_get_contents.
'HTTP_ACCEPT_ENCODING' => true,
'HTTP_CONNECTION' => true,
'HTTP_CONTENT_LENGTH' => true,
'CONTENT_LENGTH' => true,
'HTTP_EXPECT' => true,
'HTTP_HOST' => true,
'HTTP_TRANSFER_ENCODING' => true,
# Sensitive headers to exclude from all requests.
'HTTP_AUTHORIZATION' => true,
'HTTP_PROXY_AUTHORIZATION' => true,
'HTTP_X_API_KEY' => true,
];
/**
* Request helper.
*
* @var RequestHelper
*/
private RequestHelper $helper;
/**
* Server request context.
*
* @var ServerRequestContext
*/
private ServerRequestContext $serverRequest;
/**
* Create the measurement request handler.
*
* @param RequestHelper $helper
* @param ServerRequestContext $serverReqeust
*/
public function __construct(RequestHelper $helper, ServerRequestContext $serverRequest)
{
$this->helper = $helper;
$this->serverRequest = $serverRequest;
}
/** Run the measurement logic. */
public function run()
{
$redirectorFile = $this->serverRequest->getRedirectorFile();
if (empty($redirectorFile)) {
$this->helper->invalidRequest(500);
return "";
}
$tagId = $this->serverRequest->getTagId();
$path = $this->serverRequest->getDestination();
$geo = $this->serverRequest->getGeoParam();
$mpath = $this->serverRequest->getMeasurementPath();
if (empty($tagId) || empty($path)) {
$this->helper->invalidRequest(400);
return "";
}
$useMpath = empty($mpath) ? self::FPS_PATH : $mpath;
$fpsUrl = 'https://' . $tagId . '.fps.goog/' . $useMpath . $path;
$requestHeaders = $this->serverRequest->getHeaders(self::RESERVED_HEADERS);
$method = $this->serverRequest->getMethod();
$body = $this->serverRequest->getBody();
$response = $this->helper->sendRequest($method, $fpsUrl, $requestHeaders, $body);
if ($useMpath === self::FPS_PATH) {
$substitutionMpath = $redirectorFile . self::TAG_ID_QUERY . $tagId;
if (!empty($geo)) {
$substitutionMpath .= self::GEO_QUERY . $geo;
}
$substitutionMpath .= self::PATH_QUERY;
if (self::isScriptResponse($response['headers'])) {
$response['body'] = str_replace('/' . self::FPS_PATH . '/', $substitutionMpath, $response['body']);
} elseif (self::isRedirectResponse($response['statusCode']) && !empty($response['headers'])) {
foreach ($response['headers'] as $refKey => $header) {
// Ensure we are only processing strings.
if (!is_string($header)) {
continue;
}
$headerParts = explode(':', $response['headers'][$refKey], 2);
if (count($headerParts) !== 2) {
continue;
}
$key = trim($headerParts[0]);
$value = trim($headerParts[1]);
if (strtolower($key) !== 'location') {
continue;
}
$newValue = str_replace('/' . self::FPS_PATH, $substitutionMpath, $value);
$response['headers'][$refKey] = "{$key}: {$newValue}";
break;
}
}
}
return $response;
}
/**
* @param string[] $headers
*/
private static function isScriptResponse(array $headers): bool
{
if (empty($headers)) {
return false;
}
foreach ($headers as $header) {
if (empty($header)) {
continue;
}
$normalizedHeader = strtolower(str_replace(' ', '', $header));
if (strpos($normalizedHeader, 'content-type:application/javascript') === 0) {
return true;
}
}
return false;
}
/**
* Checks if the response is a redirect response.
* @param int $statusCode
*/
private static function isRedirectResponse(int $statusCode): bool
{
return $statusCode >= 300 && $statusCode < 400;
}
}
}
namespace {
use Google\GoogleTagGatewayLibrary\Proxy\Runner;
Runner::create()->run();
}

View File

@@ -0,0 +1,529 @@
<?php
/**
* Class Google\Site_Kit\Context
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit;
use AMP_Options_Manager;
use AMP_Theme_Support;
use Google\Site_Kit\Core\Util\Input;
use Google\Site_Kit\Core\Util\Entity;
use Google\Site_Kit\Core\Util\Entity_Factory;
/**
* Class representing the context in which the plugin is running.
*
* @since 1.0.0
* @access private
* @ignore
*/
class Context {
/**
* Primary "standard" AMP website mode.
*
* @since 1.0.0 Originally introduced.
* @since 1.36.0 Marked as unused, see description.
* @since 1.108.0 Removed the description and reinstated.
* @var string
*/
const AMP_MODE_PRIMARY = 'primary';
/**
* Secondary AMP website mode.
*
* @since 1.0.0
* @var string
*/
const AMP_MODE_SECONDARY = 'secondary';
/**
* Absolute path to the plugin main file.
*
* @since 1.0.0
* @var string
*/
private $main_file;
/**
* Internal storage for whether the plugin is network active or not.
*
* @since 1.0.0
* @var bool|null
*/
private $network_active = null;
/**
* Input access abstraction.
*
* @since 1.1.2
* @var Input
*/
private $input;
/**
* Constructor.
*
* @since 1.0.0
* @since 1.1.2 Added optional $input instance.
*
* @param string $main_file Absolute path to the plugin main file.
* @param Input $input Input instance.
*/
public function __construct( $main_file, ?Input $input = null ) {
$this->main_file = $main_file;
$this->input = $input ?: new Input();
}
/**
* Gets the absolute path for a path relative to the plugin directory.
*
* @since 1.0.0
*
* @param string $relative_path Optional. Relative path. Default '/'.
* @return string Absolute path.
*/
public function path( $relative_path = '/' ) {
return plugin_dir_path( $this->main_file ) . ltrim( $relative_path, '/' );
}
/**
* Gets the full URL for a path relative to the plugin directory.
*
* @since 1.0.0
*
* @param string $relative_path Optional. Relative path. Default '/'.
* @return string Full URL.
*/
public function url( $relative_path = '/' ) {
return plugin_dir_url( $this->main_file ) . ltrim( $relative_path, '/' );
}
/**
* Gets the Input instance.
*
* @since 1.1.2
*
* @return Input
*/
public function input() {
return $this->input;
}
/**
* Gets the full URL to an admin screen part of the plugin.
*
* @since 1.0.0
*
* @param string $slug Optional. Plugin admin screen slug. Default 'dashboard'.
* @param array $query_args Optional. Additional query args. Default empty array.
* @return string Full admin screen URL.
*/
public function admin_url( $slug = 'dashboard', array $query_args = array() ) {
unset( $query_args['page'] );
if ( $this->is_network_mode() ) {
$base_url = network_admin_url( 'admin.php' );
} else {
$base_url = admin_url( 'admin.php' );
}
return add_query_arg(
array_merge(
array( 'page' => Core\Admin\Screens::PREFIX . $slug ),
$query_args
),
$base_url
);
}
/**
* Determines whether the plugin is running in network mode.
*
* @since 1.0.0
*
* @return bool True if the plugin is in network mode, false otherwise.
*/
public function is_network_mode() {
// Bail if plugin is not network-active.
if ( ! $this->is_network_active() ) {
return false;
}
/**
* Filters whether network mode is active in Site Kit.
*
* This is always false by default since Site Kit does not support a network mode yet.
*
* @since 1.86.0
*
* @param bool $active Whether network mode is active.
*/
return (bool) apply_filters( 'googlesitekit_is_network_mode', false );
}
/**
* Gets the cannonical "home" URL.
*
* Returns the value from the `"googlesitekit_canonical_home_url"` filter.
*
* @since 1.18.0
*
* @return string Cannonical home URL.
*/
public function get_canonical_home_url() {
/**
* Filters the canonical home URL considered by Site Kit.
*
* Typically this is okay to be the unmodified `home_url()`, but certain plugins (e.g. multilingual plugins)
* that dynamically modify that value based on context can use this filter to ensure that the URL considered
* by Site Kit remains stable.
*
* @since 1.18.0
*
* @param string $home_url The value of `home_url()`.
*/
return apply_filters( 'googlesitekit_canonical_home_url', home_url() );
}
/**
* Gets the site URL of the reference site to use for stats.
*
* @since 1.0.0
*
* @return string Reference site URL.
*/
public function get_reference_site_url() {
return $this->filter_reference_url();
}
/**
* Gets the entity for the current request context.
*
* An entity in Site Kit terminology is based on a canonical URL, i.e. every
* canonical URL has an associated entity.
*
* An entity may also have a type, a title, and an ID.
*
* @since 1.7.0
*
* @return Entity|null The current entity, or null if none could be determined.
*/
public function get_reference_entity() {
// Support specific URL stats being checked in Site Kit dashboard details view.
if ( is_admin() && 'googlesitekit-dashboard' === $this->input()->filter( INPUT_GET, 'page' ) ) {
$entity_url_query_param = $this->input()->filter( INPUT_GET, 'permaLink' );
if ( ! empty( $entity_url_query_param ) ) {
return $this->get_reference_entity_from_url( $entity_url_query_param );
}
}
$entity = Entity_Factory::from_context();
return $this->filter_entity_reference_url( $entity );
}
/**
* Gets the entity for the given URL, if available.
*
* An entity in Site Kit terminology is based on a canonical URL, i.e. every
* canonical URL has an associated entity.
*
* An entity may also have a type, a title, and an ID.
*
* @since 1.10.0
*
* @param string $url URL to determine the entity from.
* @return Entity|null The current entity, or null if none could be determined.
*/
public function get_reference_entity_from_url( $url ) {
// Ensure local URL is used for lookup.
$url = str_replace(
$this->get_reference_site_url(),
untrailingslashit( $this->get_canonical_home_url() ),
$url
);
$entity = Entity_Factory::from_url( $url );
return $this->filter_entity_reference_url( $entity );
}
/**
* Gets the permalink of the reference site to use for stats.
*
* @since 1.0.0
*
* @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
*
* @return string|false The reference permalink URL or false if post does not exist.
*/
public function get_reference_permalink( $post = 0 ) {
// If post is provided, get URL for that.
if ( $post ) {
$permalink = get_permalink( $post );
if ( false === $permalink ) {
return false;
}
return $this->filter_reference_url( $permalink );
}
// Otherwise use entity detection.
$entity = $this->get_reference_entity();
if ( ! $entity || 'post' !== $entity->get_type() ) {
return false;
}
return $entity->get_url();
}
/**
* Gets the canonical url for the current request.
*
* @since 1.0.0
*
* @return string|false The reference canonical URL or false if no URL was identified.
*/
public function get_reference_canonical() {
$entity = $this->get_reference_entity();
if ( ! $entity ) {
return false;
}
return $entity->get_url();
}
/**
* Checks whether AMP content is being served.
*
* @since 1.0.0
*
* @return bool True if an AMP request, false otherwise.
*/
public function is_amp() {
if ( is_singular( 'web-story' ) ) {
return true;
}
return function_exists( 'is_amp_endpoint' ) && is_amp_endpoint();
}
/**
* Gets the current AMP mode.
*
* @since 1.0.0
* @since 1.108.0 Extracted AMP plugin related logic to `get_amp_mode_from_amp_plugin` function.
*
* @return bool|string 'primary' if in standard mode,
* 'secondary' if in transitional or reader modes, or the Web Stories plugin is active
* false if AMP not active, or unknown mode
*/
public function get_amp_mode() {
$amp_mode = $this->get_amp_mode_from_amp_plugin();
if ( false === $amp_mode ) {
// If the Web Stories plugin is enabled, consider the site to be running
// in Secondary AMP mode.
if ( defined( 'WEBSTORIES_VERSION' ) ) {
return self::AMP_MODE_SECONDARY;
}
}
return $amp_mode;
}
/**
* Gets the current AMP mode from the AMP plugin.
*
* @since 1.108.0
*
* @return bool|string 'primary' if in standard mode,
* 'secondary' if in transitional or reader modes
* false if AMP not active, or unknown mode
*/
private function get_amp_mode_from_amp_plugin() {
if ( ! class_exists( 'AMP_Theme_Support' ) ) {
return false;
}
$exposes_support_mode = defined( 'AMP_Theme_Support::STANDARD_MODE_SLUG' )
&& defined( 'AMP_Theme_Support::TRANSITIONAL_MODE_SLUG' )
&& defined( 'AMP_Theme_Support::READER_MODE_SLUG' );
if ( defined( 'AMP__VERSION' ) ) {
$amp_plugin_version = AMP__VERSION;
if ( strpos( $amp_plugin_version, '-' ) !== false ) {
$amp_plugin_version = explode( '-', $amp_plugin_version )[0];
}
$amp_plugin_version_2_or_higher = version_compare( $amp_plugin_version, '2.0.0', '>=' );
} else {
$amp_plugin_version_2_or_higher = false;
}
if ( $amp_plugin_version_2_or_higher ) {
$exposes_support_mode = class_exists( 'AMP_Options_Manager' )
&& method_exists( 'AMP_Options_Manager', 'get_option' )
&& $exposes_support_mode;
} else {
$exposes_support_mode = class_exists( 'AMP_Theme_Support' )
&& method_exists( 'AMP_Theme_Support', 'get_support_mode' )
&& $exposes_support_mode;
}
if ( $exposes_support_mode ) {
// If recent version, we can properly detect the mode.
if ( $amp_plugin_version_2_or_higher ) {
$mode = AMP_Options_Manager::get_option( 'theme_support' );
} else {
$mode = AMP_Theme_Support::get_support_mode();
}
if ( AMP_Theme_Support::STANDARD_MODE_SLUG === $mode ) {
return self::AMP_MODE_PRIMARY;
}
if ( in_array( $mode, array( AMP_Theme_Support::TRANSITIONAL_MODE_SLUG, AMP_Theme_Support::READER_MODE_SLUG ), true ) ) {
return self::AMP_MODE_SECONDARY;
}
} elseif ( function_exists( 'amp_is_canonical' ) ) {
// On older versions, if it is not primary AMP, it is definitely secondary AMP (transitional or reader mode).
if ( amp_is_canonical() ) {
return self::AMP_MODE_PRIMARY;
}
return self::AMP_MODE_SECONDARY;
}
return false;
}
/**
* Checks whether the plugin is network active.
*
* @since 1.0.0
*
* @return bool True if plugin is network active, false otherwise.
*/
public function is_network_active() {
// Determine $network_active property just once per request, to not unnecessarily run this complex logic on every call.
if ( null === $this->network_active ) {
if ( is_multisite() ) {
$network_active_plugins = wp_get_active_network_plugins();
// Consider MU plugins and network-activated plugins as network-active.
$this->network_active = strpos( wp_normalize_path( __FILE__ ), wp_normalize_path( WPMU_PLUGIN_DIR ) ) === 0
|| in_array( WP_PLUGIN_DIR . '/' . GOOGLESITEKIT_PLUGIN_BASENAME, $network_active_plugins, true );
} else {
$this->network_active = false;
}
}
return $this->network_active;
}
/**
* Filters the given entity's reference URL, effectively creating a copy of
* the entity with the reference URL accounted for.
*
* @since 1.15.0
*
* @param Entity|null $entity Entity to filter reference ID for, or null.
* @return Entity|null Filtered entity or null, based on $entity.
*/
private function filter_entity_reference_url( ?Entity $entity = null ) {
if ( ! $entity ) {
return null;
}
return new Entity(
$this->filter_reference_url( $entity->get_url() ),
array(
'type' => $entity->get_type(),
'title' => $entity->get_title(),
'id' => $entity->get_id(),
)
);
}
/**
* Filters the given URL to ensure the reference URL is used as part of it.
*
* If the site reference URL differs from the home URL (e.g. via filters),
* this method performs the necessary replacement.
*
* @since 1.7.0
*
* @param string $url Optional. Input URL. If not provided, returns the plain reference site URL.
* @return string URL that starts with the reference site URL.
*/
private function filter_reference_url( $url = '' ) {
$site_url = untrailingslashit( $this->get_canonical_home_url() );
/**
* Filters the reference site URL to use for stats.
*
* This can be used to override the current site URL, for example when using the plugin on a non-public site,
* such as in a staging environment.
*
* @since 1.0.0
*
* @param string $site_url Reference site URL, typically the WordPress home URL.
*/
$reference_site_url = apply_filters( 'googlesitekit_site_url', $site_url );
$reference_site_url = untrailingslashit( $reference_site_url );
// Ensure this is not empty.
if ( empty( $reference_site_url ) ) {
$reference_site_url = $site_url;
}
// If no URL given, just return the reference site URL.
if ( empty( $url ) ) {
return $reference_site_url;
}
// Replace site URL with the reference site URL.
if ( $reference_site_url !== $site_url ) {
$url = str_replace( $site_url, $reference_site_url, $url );
}
return $url;
}
/**
* Calls the WordPress core functions to get the locale and return it in the required format.
*
* @since 1.32.0
*
* @param string $context Optional. Defines which WordPress core locale function to call.
* @param string $format Optional. Defines the format the locale is returned in.
* @return string Locale in the required format.
*/
public function get_locale( $context = 'site', $format = 'default' ) {
// Get the site or user locale.
if ( 'user' === $context ) {
$wp_locale = get_user_locale();
} else {
$wp_locale = get_locale();
}
// Return locale in the required format.
if ( 'language-code' === $format ) {
$code_array = explode( '_', $wp_locale );
return $code_array[0];
} elseif ( 'language-variant' === $format ) {
$variant_array = explode( '_', $wp_locale );
$variant_string = implode( '_', array_slice( $variant_array, 0, 2 ) );
return $variant_string;
}
return $wp_locale;
}
}

View File

@@ -0,0 +1,129 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Authorize_Application
*
* @package Google\Site_Kit
* @copyright 2024 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
use Google\Site_Kit\Context;
use Google\Site_Kit\Core\Assets\Assets;
use Google\Site_Kit\Core\Util\Method_Proxy_Trait;
/**
* Class to handle all wp-admin Authorize Application related functionality.
*
* @since 1.126.0
* @access private
* @ignore
*/
final class Authorize_Application {
use Method_Proxy_Trait;
/**
* Plugin context.
*
* @since 1.126.0
* @var Context
*/
private $context;
/**
* Assets instance.
*
* @since 1.126.0
* @var Assets
*/
private $assets;
/**
* Constructor.
*
* @since 1.126.0
*
* @param Context $context Plugin context.
* @param Assets $assets Optional. Assets API instance. Default is a new instance.
*/
public function __construct(
Context $context,
?Assets $assets = null
) {
$this->context = $context;
$this->assets = $assets ?: new Assets( $this->context );
}
/**
* Registers functionality through WordPress hooks.
*
* @since 1.126.0
*/
public function register() {
add_action( 'admin_enqueue_scripts', $this->get_method_proxy( 'enqueue_assets' ) );
add_action( 'admin_footer', $this->get_method_proxy( 'render_custom_footer' ) );
}
/**
* Checks if the current screen is the Authorize Application screen.
*
* @since 1.126.0
*
* @return bool True if the current screen is the Authorize Application screen, false otherwise.
*/
protected function is_authorize_application_screen() {
$current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( $current_screen instanceof \WP_Screen && 'authorize-application' === $current_screen->id ) {
return true;
}
return false;
}
/**
* Checks if the current service is a Google service.
*
* @since 1.126.0
*
* @return bool True if the current service is a Google service, false otherwise.
*/
protected function is_google_service() {
$success_url = isset( $_GET['success_url'] ) ? esc_url_raw( wp_unslash( $_GET['success_url'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
$success_url = sanitize_text_field( $success_url );
$parsed_url = wp_parse_url( $success_url );
if ( empty( $parsed_url['host'] ) ) {
return false;
}
// Check if the domain is a '*.google.com' domain.
return preg_match( '/\.google\.com$/', $parsed_url['host'] ) === 1;
}
/**
* Enqueues assets for the Authorize Application screen.
*
* @since 1.126.0
*/
private function enqueue_assets() {
if ( $this->is_authorize_application_screen() && $this->is_google_service() ) {
$this->assets->enqueue_asset( 'googlesitekit-authorize-application-css' );
}
}
/**
* Renders custom footer for the Authorize Application screen if the service is a Google service.
*
* @since 1.126.0
*/
private function render_custom_footer() {
if ( $this->is_authorize_application_screen() && $this->is_google_service() ) {
echo '<div class="googlesitekit-authorize-application__footer"><p>' . esc_html__( 'Powered by Site Kit', 'google-site-kit' ) . '</p></div>';
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Available_Tools
*
* @package Google\Site_Kit\Core\Admin
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
use Google\Site_Kit\Core\Permissions\Permissions;
use Google\Site_Kit\Core\Util\Method_Proxy_Trait;
use Google\Site_Kit\Core\Util\Reset;
/**
* Class for extending available tools for Site Kit.
*
* @since 1.30.0
* @access private
* @ignore
*/
class Available_Tools {
use Method_Proxy_Trait;
/**
* Registers functionality through WordPress hooks.
*
* @since 1.30.0
*/
public function register() {
add_action( 'tool_box', $this->get_method_proxy( 'render_tool_box' ) );
}
/**
* Renders tool box output.
*
* @since 1.30.0
*/
private function render_tool_box() {
if ( ! current_user_can( Permissions::SETUP ) ) {
return;
}
?>
<div class="card">
<h2 class="title"><?php esc_html_e( 'Reset Site Kit', 'google-site-kit' ); ?></h2>
<p>
<?php
esc_html_e(
'Resetting will disconnect all users and remove all Site Kit settings and data within WordPress. You and any other users who wish to use Site Kit will need to reconnect to restore access.',
'google-site-kit'
)
?>
</p>
<p>
<a
class="button button-primary"
href="<?php echo esc_url( Reset::url() ); ?>"
>
<?php esc_html_e( 'Reset Site Kit', 'google-site-kit' ); ?>
</a>
</p>
</div>
<?php
}
}

View File

@@ -0,0 +1,203 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Dashboard
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
use Google\Site_Kit\Context;
use Google\Site_Kit\Core\Assets\Assets;
use Google\Site_Kit\Core\Authentication\Authentication;
use Google\Site_Kit\Core\Modules\Modules;
use Google\Site_Kit\Core\Permissions\Permissions;
use Google\Site_Kit\Core\Util\Requires_Javascript_Trait;
/**
* Class to handle all wp-admin Dashboard related functionality.
*
* @since 1.0.0
* @access private
* @ignore
*/
final class Dashboard {
use Requires_Javascript_Trait;
/**
* Plugin context.
*
* @since 1.0.0
* @var Context
*/
private $context;
/**
* Assets Instance.
*
* @since 1.0.0
* @var Assets
*/
private $assets;
/**
* Modules instance.
*
* @since 1.7.0
* @var Modules
*/
private $modules;
/**
* Authentication instance.
*
* @since 1.120.0
* @var Authentication
*/
private $authentication;
/**
* Constructor.
*
* @since 1.0.0
*
* @param Context $context Plugin context.
* @param Assets $assets Optional. Assets API instance. Default is a new instance.
* @param Modules $modules Optional. Modules instance. Default is a new instance.
*/
public function __construct(
Context $context,
?Assets $assets = null,
?Modules $modules = null
) {
$this->context = $context;
$this->assets = $assets ?: new Assets( $this->context );
$this->modules = $modules ?: new Modules( $this->context );
$this->authentication = new Authentication( $this->context );
}
/**
* Registers functionality through WordPress hooks.
*
* @since 1.0.0
*/
public function register() {
add_action(
'wp_dashboard_setup',
function () {
$this->add_widgets();
}
);
}
/**
* Add a Site Kit by Google widget to the WordPress admin dashboard.
*
* @since 1.0.0
*/
private function add_widgets() {
if ( ! current_user_can( Permissions::VIEW_WP_DASHBOARD_WIDGET ) ) {
return;
}
// Enqueue styles.
$this->assets->enqueue_asset( 'googlesitekit-wp-dashboard-css' );
// Enqueue scripts.
$this->assets->enqueue_asset( 'googlesitekit-wp-dashboard' );
$this->modules->enqueue_assets();
wp_add_dashboard_widget(
'google_dashboard_widget',
__( 'Site Kit Summary', 'google-site-kit' ),
function () {
$this->render_googlesitekit_wp_dashboard();
}
);
}
/**
* Render the Site Kit WordPress Dashboard widget.
*
* @since 1.0.0
* @since 1.120.0 Added the `data-view-only` attribute.
*/
private function render_googlesitekit_wp_dashboard() {
$active_modules = $this->modules->get_active_modules();
$analytics_connected = isset( $active_modules['analytics-4'] ) && $active_modules['analytics-4']->is_connected();
$search_console_connected = isset( $active_modules['search-console'] ) && $active_modules['search-console']->is_connected();
$is_view_only = ! $this->authentication->is_authenticated();
$can_view_shared_analytics = current_user_can( Permissions::READ_SHARED_MODULE_DATA, 'analytics-4' );
$can_view_shared_search_console = current_user_can( Permissions::READ_SHARED_MODULE_DATA, 'search-console' );
$display_analytics_data = ( ! $is_view_only && $analytics_connected ) || ( $is_view_only && $can_view_shared_analytics );
$display_search_console_data = ( ! $is_view_only && $search_console_connected ) || ( $is_view_only && $can_view_shared_search_console );
$class_names = array();
if ( $analytics_connected && $display_analytics_data ) {
$class_names[] = 'googlesitekit-wp-dashboard-analytics_active_and_connected';
}
if ( $search_console_connected && $display_search_console_data ) {
$class_names[] = 'googlesitekit-wp-dashboard-search_console_active_and_connected';
}
if ( ! $analytics_connected && ! $is_view_only ) {
$class_names[] = 'googlesitekit-wp-dashboard-analytics-activate-cta';
}
$class_names = implode( ' ', $class_names );
$this->render_noscript_html();
?>
<div id="js-googlesitekit-wp-dashboard" data-view-only="<?php echo esc_attr( $is_view_only ); ?>" class="googlesitekit-plugin <?php echo esc_attr( $class_names ); ?>">
<div class="googlesitekit-wp-dashboard googlesitekit-wp-dashboard-loading">
<?php
$this->render_loading_container( 'googlesitekit-wp-dashboard__cta' );
?>
<div class="googlesitekit-wp-dashboard-stats">
<?php
if ( $display_analytics_data ) {
$this->render_loading_container( 'googlesitekit-wp-dashboard-loading__can_view_analytics' );
}
if ( $display_search_console_data ) {
$this->render_loading_container( 'googlesitekit-wp-dashboard-loading__search_console_active_and_connected' );
}
if ( ! $analytics_connected && ! $is_view_only ) {
$this->render_loading_container( 'googlesitekit-wp-dashboard-stats__cta' );
}
if ( $display_analytics_data ) {
$this->render_loading_container( 'googlesitekit-unique-visitors-chart-widget' );
$this->render_loading_container( 'googlesitekit-search-console-widget' );
}
?>
</div>
</div>
</div>
<?php
}
/**
* Render the loading container when data is not available and being fetched.
*
* @since 1.144.0
* @param string $class_names Class names to add to the container.
* @return void
*/
private function render_loading_container( $class_names ) {
?>
<div class="googlesitekit-preview-block <?php echo esc_attr( $class_names ); ?>">
<div class="googlesitekit-preview-block__wrapper"></div>
</div>
<?php
}
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Notice
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
/**
* Class representing a single notice.
*
* @since 1.0.0
* @access private
* @ignore
*/
final class Notice {
const TYPE_SUCCESS = 'success';
const TYPE_INFO = 'info';
const TYPE_WARNING = 'warning';
const TYPE_ERROR = 'error';
/**
* Unique notice slug.
*
* @since 1.0.0
* @var string
*/
private $slug;
/**
* Notice arguments.
*
* @since 1.0.0
* @var array
*/
private $args = array();
/**
* Constructor.
*
* @since 1.0.0
*
* @param string $slug Unique notice slug.
* @param array $args {
* Associative array of notice arguments.
*
* @type string $content Required notice content. May contain inline HTML tags.
* @type string $type Notice type. Either 'success', 'info', 'warning', 'error'. Default 'info'.
* @type callable $active_callback Callback function to determine whether the notice is active in the
* current context. The current admin screen's hook suffix is passed to
* the callback. Default is that the notice is active unconditionally.
* @type bool $dismissible Whether the notice should be dismissible. Default false.
* }
*/
public function __construct( $slug, array $args ) {
$this->slug = $slug;
$this->args = wp_parse_args(
$args,
array(
'content' => '',
'type' => self::TYPE_INFO,
'active_callback' => null,
'dismissible' => false,
)
);
}
/**
* Gets the notice slug.
*
* @since 1.0.0
*
* @return string Unique notice slug.
*/
public function get_slug() {
return $this->slug;
}
/**
* Checks whether the notice is active.
*
* This method executes the active callback in order to determine whether the notice should be active or not.
*
* @since 1.0.0
*
* @param string $hook_suffix The current admin screen hook suffix.
* @return bool True if the notice is active, false otherwise.
*/
public function is_active( $hook_suffix ) {
if ( ! $this->args['content'] ) {
return false;
}
if ( ! $this->args['active_callback'] ) {
return true;
}
return (bool) call_user_func( $this->args['active_callback'], $hook_suffix );
}
/**
* Renders the notice.
*
* @since 1.0.0
*/
public function render() {
if ( is_callable( $this->args['content'] ) ) {
$content = call_user_func( $this->args['content'] );
if ( empty( $content ) ) {
return;
}
} else {
$content = '<p>' . wp_kses( $this->args['content'], 'googlesitekit_admin_notice' ) . '</p>';
}
$class = 'notice notice-' . $this->args['type'];
if ( $this->args['dismissible'] ) {
$class .= ' is-dismissible';
}
?>
<div id="<?php echo esc_attr( 'googlesitekit-notice-' . $this->slug ); ?>" class="<?php echo esc_attr( $class ); ?>">
<?php echo $content; /* phpcs:ignore WordPress.Security.EscapeOutput */ ?>
</div>
<?php
}
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Notices
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
/**
* Class managing admin notices.
*
* @since 1.0.0
* @access private
* @ignore
*/
final class Notices {
/**
* Registers functionality through WordPress hooks.
*
* @since 1.0.0
*/
public function register() {
$callback = function () {
global $hook_suffix;
if ( empty( $hook_suffix ) ) {
return;
}
$this->render_notices( $hook_suffix );
};
add_action( 'admin_notices', $callback );
add_action( 'network_admin_notices', $callback );
}
/**
* Renders admin notices.
*
* @since 1.0.0
*
* @param string $hook_suffix The current admin screen hook suffix.
*/
private function render_notices( $hook_suffix ) {
$notices = $this->get_notices();
if ( empty( $notices ) ) {
return;
}
/**
* Notice object.
*
* @var Notice $notice Notice object.
*/
foreach ( $notices as $notice ) {
if ( ! $notice->is_active( $hook_suffix ) ) {
continue;
}
$notice->render();
}
}
/**
* Gets available admin notices.
*
* @since 1.0.0
*
* @return array List of Notice instances.
*/
private function get_notices() {
/**
* Filters the list of available admin notices.
*
* @since 1.0.0
*
* @param array $notices List of Notice instances.
*/
$notices = apply_filters( 'googlesitekit_admin_notices', array() );
return array_filter(
$notices,
function ( $notice ) {
return $notice instanceof Notice;
}
);
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Plugin_Action_Links
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
use Google\Site_Kit\Context;
use Google\Site_Kit\Core\Permissions\Permissions;
/**
* Class for managing plugin action links.
*
* @since 1.41.0
* @access private
* @ignore
*/
class Plugin_Action_Links {
/**
* Plugin context.
*
* @since 1.41.0
* @var Context
*/
private $context;
/**
* Constructor.
*
* @since 1.41.0
*
* @param Context $context Plugin context.
*/
public function __construct(
Context $context
) {
$this->context = $context;
}
/**
* Registers functionality through WordPress hooks.
*
* @since 1.41.0
*/
public function register() {
add_filter(
'plugin_action_links_' . GOOGLESITEKIT_PLUGIN_BASENAME,
function ( $links ) {
if ( current_user_can( Permissions::MANAGE_OPTIONS ) ) {
$settings_link = sprintf(
'<a href="%s">%s</a>',
esc_url( $this->context->admin_url( 'settings' ) ),
esc_html__( 'Settings', 'google-site-kit' )
);
array_unshift( $links, $settings_link );
}
return $links;
}
);
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Plugin_Row_Meta
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
/**
* Class for managing plugin row meta.
*
* @since 1.24.0
* @access private
* @ignore
*/
class Plugin_Row_Meta {
/**
* Registers functionality through WordPress hooks.
*
* @since 1.24.0
*/
public function register() {
add_filter(
'plugin_row_meta',
function ( $meta, $plugin_file ) {
if ( GOOGLESITEKIT_PLUGIN_BASENAME === $plugin_file ) {
return array_merge( $meta, $this->get_plugin_row_meta() );
}
return $meta;
},
10,
2
);
}
/**
* Builds an array of anchor elements to be shown in the plugin row.
*
* @since 1.24.0
*
* @return string[] Array of links as HTML strings.
*/
private function get_plugin_row_meta() {
return array(
'<a href="https://wordpress.org/support/plugin/google-site-kit/reviews/#new-post">' . __( 'Rate Site Kit', 'google-site-kit' ) . '</a>',
'<a href="https://wordpress.org/support/plugin/google-site-kit/#new-post">' . __( 'Support', 'google-site-kit' ) . '</a>',
);
}
}

View File

@@ -0,0 +1,151 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Pointer
*
* @package Google\Site_Kit
* @copyright 2022 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
/**
* Class representing a single pointer.
*
* @since 1.83.0
* @access private
* @ignore
*/
final class Pointer {
/**
* Unique pointer slug.
*
* @since 1.83.0
* @var string
*/
private $slug;
/**
* Pointer arguments.
*
* @since 1.83.0
* @var array
*/
private $args = array();
/**
* Constructor.
*
* @since 1.83.0
*
* @param string $slug Unique pointer slug.
* @param array $args {
* Associative array of pointer arguments.
*
* @type string $title Required. Pointer title.
* @type string $content Required. Pointer content. May contain inline HTML tags.
* @type string $target_id Required. ID of the element the pointer should be attached to.
* @type string|array $position Optional. Position of the pointer. Can be 'top', 'bottom', 'left', 'right',
* or an array of `edge` and `align`. Default 'top'.
* @type callable $active_callback Optional. Callback function to determine whether the pointer is active in
* the current context. The current admin screen's hook suffix is passed to
* the callback. Default is that the pointer is active unconditionally.
* }
*/
public function __construct( $slug, array $args ) {
$this->slug = $slug;
$this->args = wp_parse_args(
$args,
array(
'title' => '',
'content' => '',
'target_id' => '',
'position' => 'top',
'active_callback' => null,
)
);
}
/**
* Gets the pointer slug.
*
* @since 1.83.0
*
* @return string Unique pointer slug.
*/
public function get_slug() {
return $this->slug;
}
/**
* Gets the pointer title.
*
* @since 1.83.0
*
* @return string Pointer title.
*/
public function get_title() {
return $this->args['title'];
}
/**
* Gets the pointer content.
*
* @since 1.83.0
*
* @return string Pointer content.
*/
public function get_content() {
if ( is_callable( $this->args['content'] ) ) {
return call_user_func( $this->args['content'] );
} else {
return '<p>' . wp_kses( $this->args['content'], 'googlesitekit_admin_pointer' ) . '</p>';
}
}
/**
* Gets the pointer target ID.
*
* @since 1.83.0
*
* @return string Pointer target ID.
*/
public function get_target_id() {
return $this->args['target_id'];
}
/**
* Gets the pointer position.
*
* @since 1.83.0
*
* @return string|array Pointer position.
*/
public function get_position() {
return $this->args['position'];
}
/**
* Checks whether the pointer is active.
*
* This method executes the active callback in order to determine whether the pointer should be active or not.
*
* @since 1.83.0
*
* @param string $hook_suffix The current admin screen hook suffix.
* @return bool True if the pointer is active, false otherwise.
*/
public function is_active( $hook_suffix ) {
if ( empty( $this->args['title'] ) || empty( $this->args['content'] ) || empty( $this->args['target_id'] ) ) {
return false;
}
if ( ! is_callable( $this->args['active_callback'] ) ) {
return true;
}
return (bool) call_user_func( $this->args['active_callback'], $hook_suffix );
}
}

View File

@@ -0,0 +1,150 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Pointers
*
* @package Google\Site_Kit\Core\Admin
* @copyright 2022 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
use Google\Site_Kit\Core\Util\BC_Functions;
use Google\Site_Kit\Core\Util\Method_Proxy_Trait;
/**
* Class for managing pointers.
*
* @since 1.83.0
* @access private
* @ignore
*/
class Pointers {
use Method_Proxy_Trait;
/**
* Registers functionality through WordPress hooks.
*
* @since 1.83.0
*/
public function register() {
add_action( 'admin_enqueue_scripts', $this->get_method_proxy( 'enqueue_pointers' ) );
}
/**
* Enqueues pointer scripts.
*
* @since 1.83.0
*
* @param string $hook_suffix The current admin page.
*/
private function enqueue_pointers( $hook_suffix ) {
if ( empty( $hook_suffix ) ) {
return;
}
$pointers = $this->get_pointers();
if ( empty( $pointers ) ) {
return;
}
$active_pointers = array_filter(
$pointers,
function ( Pointer $pointer ) use ( $hook_suffix ) {
return $pointer->is_active( $hook_suffix );
}
);
if ( empty( $active_pointers ) ) {
return;
}
wp_enqueue_style( 'wp-pointer' );
wp_enqueue_script( 'wp-pointer' );
add_action(
'admin_print_footer_scripts',
function () use ( $active_pointers ) {
foreach ( $active_pointers as $pointer ) {
$this->print_pointer_script( $pointer );
}
}
);
}
/**
* Gets pointers.
*
* @since 1.83.0
*
* @return Pointer[] Array of pointers.
*/
private function get_pointers() {
/**
* Filters the list of available pointers.
*
* @since 1.83.0
*
* @param array $pointers List of Pointer instances.
*/
$pointers = apply_filters( 'googlesitekit_admin_pointers', array() );
return array_filter(
$pointers,
function ( $pointer ) {
return $pointer instanceof Pointer;
}
);
}
/**
* Prints script for a given pointer.
*
* @since 1.83.0
*
* @param Pointer $pointer Pointer to print.
*/
private function print_pointer_script( $pointer ) {
$content = $pointer->get_content();
if ( empty( $content ) ) {
return;
}
$slug = $pointer->get_slug();
BC_Functions::wp_print_inline_script_tag(
sprintf(
'
jQuery( function() {
var options = {
content: "<h3>%s</h3>%s",
position: %s,
pointerWidth: 420,
close: function() {
jQuery.post(
window.ajaxurl,
{
pointer: "%s",
action: "dismiss-wp-pointer",
}
);
}
};
jQuery( "#%s" ).pointer( options ).pointer( "open" );
} );
',
esc_js( $pointer->get_title() ),
$content,
wp_json_encode( $pointer->get_position() ),
esc_js( $slug ),
esc_js( $pointer->get_target_id() )
),
array(
'id' => $slug,
)
);
}
}

View File

@@ -0,0 +1,260 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Screen
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
use Google\Site_Kit\Context;
use Google\Site_Kit\Core\Assets\Assets;
use Google\Site_Kit\Core\Util\Google_Icon;
use Google\Site_Kit\Core\Util\Requires_Javascript_Trait;
/**
* Class representing a single screen.
*
* @since 1.0.0
* @access private
* @ignore
*/
final class Screen {
use Requires_Javascript_Trait;
const MENU_SLUG = 'googlesitekit';
/**
* Unique screen slug.
*
* @since 1.0.0
* @var string
*/
private $slug;
/**
* Screen arguments.
*
* @since 1.0.0
* @var array
*/
private $args = array();
/**
* Constructor.
*
* @since 1.0.0
*
* @param string $slug Unique screen slug.
* @param array $args {
* Associative array of screen arguments.
*
* @type callable $render_callback Required callback to render the page content.
* @type string $title Required screen title.
* @type string $capability Capability required to access the screen. Default is 'manage_options'.
* @type string $menu_title Title to display in the menu (only if $add_to_menu is true). Default is
* the value of $title.
* @type string $parent_slug Slug of the parent menu screen (only if $add_to_menu is true). Default
* empty string (which means it will be a top-level page).
* @type callable $enqueue_callback Callback to enqueue additional scripts or stylesheets. The base admin
* script and stylesheet will always be enqueued. Default null.
* @type callable $initialize_callback Callback to run actions when initializing the screen, before headers are
* sent and markup is generated. Default null.
* }
*/
public function __construct( $slug, array $args ) {
$this->slug = $slug;
$this->args = wp_parse_args(
$args,
array(
'render_callback' => null,
'title' => '',
'capability' => 'manage_options',
'menu_title' => '',
'parent_slug' => self::MENU_SLUG,
'enqueue_callback' => null,
'initialize_callback' => null,
)
);
if ( empty( $this->args['menu_title'] ) ) {
$this->args['menu_title'] = $this->args['title'];
}
$this->args['title'] = __( 'Site Kit by Google', 'google-site-kit' ) . ' ' . $this->args['title'];
}
/**
* Gets the unique screen slug.
*
* @since 1.0.0
*
* @return string Unique screen slug.
*/
public function get_slug() {
return $this->slug;
}
/**
* Adds the screen to the WordPress admin backend.
*
* @since 1.0.0
*
* @param Context $context Plugin context, used for URL generation.
* @return string Hook suffix of the screen, or empty string if not added.
*/
public function add( Context $context ) {
static $menu_slug = null;
if ( ! $this->args['title'] ) {
return '';
}
// A parent slug of null means the screen will not appear in the menu.
$parent_slug = null;
// If parent slug is provided, use it as parent.
if ( ! empty( $this->args['parent_slug'] ) ) {
$parent_slug = $this->args['parent_slug'];
// If parent slug is 'googlesitekit', append to main Site Kit menu.
if ( self::MENU_SLUG === $parent_slug ) {
// If this is null, it means no menu has been added yet.
if ( null === $menu_slug ) {
add_menu_page(
$this->args['title'],
__( 'Site Kit', 'google-site-kit' ),
$this->args['capability'],
$this->slug,
'',
'data:image/svg+xml;base64,' . Google_Icon::to_base64()
);
$menu_slug = $this->slug;
/**
* An SVG icon file needs to be colored (filled) based on the theme color setting.
*
* This exists in js as wp.svgPainter() per:
* https://github.com/WordPress/WordPress/blob/5.7/wp-admin/js/svg-painter.js
*
* The downside of the js approach is that we get a brief flash of an unstyled icon
* until the JS runs.
*
* A user can pick a custom Admin Color Scheme, which is only available in admin_init
* or later actions. add_menu_page runs on the admin_menu action, which precedes admin_init
* per https://codex.wordpress.org/Plugin_API/Action_Reference
*
* WordPress provides some color schemes out of the box, but they can also be added via
* wp_admin_css_color()
*
* Our workaround is to set the icon and subsequently replace it in current_screen, which is
* what we do in the following action.
*/
add_action(
'current_screen',
function () {
global $menu, $_wp_admin_css_colors;
if ( ! is_array( $menu ) ) {
return;
}
$color_scheme = get_user_option( 'admin_color' ) ?: 'fresh';
// If we're on one of the sitekit pages, use the 'current' color, otherwise use the 'base' color.
// @see wp_admin_css_color().
$color_key = false === strpos( get_current_screen()->id, 'googlesitekit' ) ? 'base' : 'current';
if ( empty( $_wp_admin_css_colors[ $color_scheme ]->icon_colors[ $color_key ] ) ) {
return;
}
$color = $_wp_admin_css_colors[ $color_scheme ]->icon_colors[ $color_key ];
foreach ( $menu as &$item ) {
if ( 'googlesitekit-dashboard' === $item[2] ) {
$item[6] = 'data:image/svg+xml;base64,' . Google_Icon::to_base64( Google_Icon::with_fill( $color ) );
break;
}
}
},
100
);
}
// Set parent slug to actual slug of main Site Kit menu.
$parent_slug = $menu_slug;
}
}
// If submenu item or not in menu, use add_submenu_page().
return (string) add_submenu_page(
$parent_slug,
$this->args['title'],
$this->args['menu_title'],
$this->args['capability'],
$this->slug,
function () use ( $context ) {
$this->render( $context );
}
);
}
/**
* Runs actions when initializing the screen, before sending headers and generating markup.
*
* @since 1.0.0
*
* @param Context $context Plugin context.
*/
public function initialize( Context $context ) {
if ( ! $this->args['initialize_callback'] ) {
return;
}
call_user_func( $this->args['initialize_callback'], $context );
}
/**
* Enqueues assets for the screen.
*
* @since 1.0.0
*
* @param Assets $assets Assets instance to rely on for enqueueing assets.
*/
public function enqueue_assets( Assets $assets ) {
// Enqueue base admin screen stylesheet.
$assets->enqueue_asset( 'googlesitekit-admin-css' );
$cb = is_callable( $this->args['enqueue_callback'] )
? $this->args['enqueue_callback']
: function ( Assets $assets ) {
$assets->enqueue_asset( $this->slug );
};
call_user_func( $cb, $assets );
}
/**
* Renders the screen content.
*
* @since 1.0.0
*
* @param Context $context Plugin context.
*/
private function render( Context $context ) {
$cb = is_callable( $this->args['render_callback'] )
? $this->args['render_callback']
: function () {
printf( '<div id="js-%s" class="googlesitekit-page"></div>', esc_attr( $this->slug ) );
};
echo '<div class="googlesitekit-plugin">';
$this->render_noscript_html();
call_user_func( $cb, $context );
echo '</div>';
}
}

View File

@@ -0,0 +1,517 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Screens
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
use Google\Site_Kit\Context;
use Google\Site_Kit\Core\Assets\Assets;
use Google\Site_Kit\Core\Authentication\Authentication;
use Google\Site_Kit\Core\Dismissals\Dismissed_Items;
use Google\Site_Kit\Core\Key_Metrics\Key_Metrics_Setup_Completed_By;
use Google\Site_Kit\Core\Modules\Modules;
use Google\Site_Kit\Core\Permissions\Permissions;
use Google\Site_Kit\Core\Storage\Options;
use Google\Site_Kit\Core\Storage\User_Options;
/**
* Class managing admin screens.
*
* @since 1.0.0
* @access private
* @ignore
*/
final class Screens {
const PREFIX = 'googlesitekit-';
const PARENT_SLUG_NULL = self::PREFIX . 'null';
/**
* Plugin context.
*
* @since 1.0.0
* @var Context
*/
private $context;
/**
* Assets API instance.
*
* @since 1.0.0
* @var Assets
*/
private $assets;
/**
* Modules instance.
*
* @since 1.7.0
* @var Modules
*/
private $modules;
/**
* Authentication instance.
*
* @since 1.72.0
* @var Authentication
*/
private $authentication;
/**
* Associative array of $hook_suffix => $screen pairs.
*
* @since 1.0.0
* @var array
*/
private $screens = array();
/**
* Constructor.
*
* @since 1.0.0
*
* @param Context $context Plugin context.
* @param Assets $assets Optional. Assets API instance. Default is a new instance.
* @param Modules $modules Optional. Modules instance. Default is a new instance.
* @param Authentication $authentication Optional. Authentication instance. Default is a new instance.
*/
public function __construct(
Context $context,
?Assets $assets = null,
?Modules $modules = null,
?Authentication $authentication = null
) {
$this->context = $context;
$this->assets = $assets ?: new Assets( $this->context );
$this->modules = $modules ?: new Modules( $this->context );
$this->authentication = $authentication ?: new Authentication( $this->context );
}
/**
* Registers functionality through WordPress hooks.
*
* @since 1.0.0
*/
public function register() {
if ( $this->context->is_network_mode() ) {
add_action(
'network_admin_menu',
function () {
$this->add_screens();
}
);
}
add_action(
'admin_menu',
function () {
$this->add_screens();
}
);
add_action(
'admin_enqueue_scripts',
function ( $hook_suffix ) {
$this->enqueue_screen_assets( $hook_suffix );
}
);
add_action(
'admin_page_access_denied',
function () {
// Redirect dashboard to splash if no dashboard access (yet).
$this->no_access_redirect_dashboard_to_splash();
// Redirect splash to (shared) dashboard if splash is dismissed.
$this->no_access_redirect_splash_to_dashboard();
// Redirect module pages to dashboard.
$this->no_access_redirect_module_to_dashboard();
}
);
// Ensure the menu icon always is rendered correctly, without enqueueing a global CSS file.
add_action(
'admin_head',
function () {
?>
<style type="text/css">
#adminmenu .toplevel_page_googlesitekit-dashboard img {
width: 16px;
}
#adminmenu .toplevel_page_googlesitekit-dashboard.current img,
#adminmenu .toplevel_page_googlesitekit-dashboard.wp-has-current-submenu img {
opacity: 1;
}
</style>
<?php
}
);
$remove_notices_callback = function () {
global $hook_suffix;
if ( empty( $hook_suffix ) ) {
return;
}
if ( isset( $this->screens[ $hook_suffix ] ) ) {
remove_all_actions( current_action() );
}
};
add_action( 'admin_notices', $remove_notices_callback, -9999 );
add_action( 'network_admin_notices', $remove_notices_callback, -9999 );
add_action( 'all_admin_notices', $remove_notices_callback, -9999 );
add_filter( 'custom_menu_order', '__return_true' );
add_filter(
'menu_order',
function ( array $menu_order ) {
// Move the Site Kit dashboard menu item to be one after the index.php item if it exists.
$dashboard_index = array_search( 'index.php', $menu_order, true );
$sitekit_index = false;
foreach ( $menu_order as $key => $value ) {
if ( strpos( $value, self::PREFIX ) === 0 ) {
$sitekit_index = $key;
$sitekit_value = $value;
break;
}
}
if ( false === $dashboard_index || false === $sitekit_index ) {
return $menu_order;
}
unset( $menu_order[ $sitekit_index ] );
array_splice( $menu_order, $dashboard_index + 1, 0, $sitekit_value );
return $menu_order;
}
);
}
/**
* Gets the Screen instance for a given hook suffix.
*
* @since 1.11.0
*
* @param string $hook_suffix The hook suffix associated with the screen to retrieve.
* @return Screen|null Screen instance if available, otherwise null;
*/
public function get_screen( $hook_suffix ) {
return isset( $this->screens[ $hook_suffix ] ) ? $this->screens[ $hook_suffix ] : null;
}
/**
* Adds all screens to the admin.
*
* @since 1.0.0
*/
private function add_screens() {
$screens = $this->get_screens();
array_walk( $screens, array( $this, 'add_screen' ) );
}
/**
* Adds the given screen to the admin.
*
* @since 1.0.0
*
* @param Screen $screen Screen to add.
*/
private function add_screen( Screen $screen ) {
$hook_suffix = $screen->add( $this->context );
if ( empty( $hook_suffix ) ) {
return;
}
add_action(
"load-{$hook_suffix}",
function () use ( $screen ) {
$screen->initialize( $this->context );
}
);
$this->screens[ $hook_suffix ] = $screen;
}
/**
* Enqueues assets if a plugin screen matches the given hook suffix.
*
* @since 1.0.0
*
* @param string $hook_suffix Hook suffix for the current admin screen.
*/
private function enqueue_screen_assets( $hook_suffix ) {
if ( ! isset( $this->screens[ $hook_suffix ] ) ) {
return;
}
$this->screens[ $hook_suffix ]->enqueue_assets( $this->assets );
$this->modules->enqueue_assets();
}
/**
* Redirects from the dashboard to the splash screen if permissions to access the dashboard are currently not met.
*
* Dashboard permission access is conditional based on whether the user has successfully authenticated. When
* e.g. accessing the dashboard manually or having it open in a separate tab while disconnecting in the other tab,
* it is a better user experience to redirect to the splash screen so that the user can re-authenticate.
*
* The only time the dashboard should fail with the regular WordPress permissions error is when the current user is
* not eligible for accessing Site Kit entirely, i.e. if they are not allowed to authenticate.
*
* @since 1.12.0
*/
private function no_access_redirect_dashboard_to_splash() {
global $plugin_page;
// At this point, our preferred `$hook_suffix` is not set, and the dashboard page will not even be registered,
// so we need to rely on the `$plugin_page` global here.
if ( ! isset( $plugin_page ) || self::PREFIX . 'dashboard' !== $plugin_page ) {
return;
}
if ( current_user_can( Permissions::VIEW_SPLASH ) ) {
wp_safe_redirect(
$this->context->admin_url( 'splash' )
);
exit;
}
}
/**
* Redirects from the splash to the dashboard screen if permissions to access the splash are currently not met.
*
* Admins always have the ability to view the splash page, so this redirects non-admins who have access
* to view the shared dashboard if the splash has been dismissed.
* Currently the dismissal check is built into the capability for VIEW_SPLASH so this is implied.
*
* @since 1.77.0
*/
private function no_access_redirect_splash_to_dashboard() {
global $plugin_page;
if ( ! isset( $plugin_page ) || self::PREFIX . 'splash' !== $plugin_page ) {
return;
}
if ( current_user_can( Permissions::VIEW_DASHBOARD ) ) {
wp_safe_redirect(
$this->context->admin_url()
);
exit;
}
}
/**
* Redirects module pages to the dashboard or splash based on user capability.
*
* @since 1.69.0
*/
private function no_access_redirect_module_to_dashboard() {
global $plugin_page;
$legacy_module_pages = array(
self::PREFIX . 'module-adsense',
self::PREFIX . 'module-analytics',
self::PREFIX . 'module-search-console',
);
if ( ! in_array( $plugin_page, $legacy_module_pages, true ) ) {
return;
}
// Note: the use of add_query_arg is intentional below because it preserves
// the current query parameters in the URL.
if ( current_user_can( Permissions::VIEW_DASHBOARD ) ) {
wp_safe_redirect(
add_query_arg( 'page', self::PREFIX . 'dashboard' )
);
exit;
}
if ( current_user_can( Permissions::VIEW_SPLASH ) ) {
wp_safe_redirect(
add_query_arg( 'page', self::PREFIX . 'splash' )
);
exit;
}
}
/**
* Gets available admin screens.
*
* @since 1.0.0
*
* @return array List of Screen instances.
*/
private function get_screens() {
$show_splash_in_menu = current_user_can( Permissions::VIEW_SPLASH ) && ! current_user_can( Permissions::VIEW_DASHBOARD );
$screens = array(
new Screen(
self::PREFIX . 'dashboard',
array(
'title' => __( 'Dashboard', 'google-site-kit' ),
'capability' => Permissions::VIEW_DASHBOARD,
'enqueue_callback' => function ( Assets $assets ) {
if ( $this->context->input()->filter( INPUT_GET, 'permaLink' ) ) {
$assets->enqueue_asset( 'googlesitekit-entity-dashboard' );
} else {
$assets->enqueue_asset( 'googlesitekit-main-dashboard' );
}
},
'render_callback' => function ( Context $context ) {
$is_view_only = ! $this->authentication->is_authenticated();
$setup_slug = htmlspecialchars( $context->input()->filter( INPUT_GET, 'slug' ) ?: '' );
$reauth = $context->input()->filter( INPUT_GET, 'reAuth', FILTER_VALIDATE_BOOLEAN );
if ( $context->input()->filter( INPUT_GET, 'permaLink' ) ) {
?>
<div id="js-googlesitekit-entity-dashboard" data-view-only="<?php echo esc_attr( $is_view_only ); ?>" class="googlesitekit-page"></div>
<?php
} else {
$setup_module_slug = $setup_slug && $reauth ? $setup_slug : '';
if ( $setup_module_slug ) {
$active_modules = $this->modules->get_active_modules();
if ( ! array_key_exists( $setup_module_slug, $active_modules ) ) {
try {
$module_details = $this->modules->get_module( $setup_module_slug );
/* translators: %s: The module name */
$message = sprintf( __( 'The %s module cannot be set up as it has not been activated yet.', 'google-site-kit' ), $module_details->name );
} catch ( \Exception $e ) {
$message = $e->getMessage();
}
wp_die( sprintf( '<span class="googlesitekit-notice">%s</span>', esc_html( $message ) ), 403 );
}
}
?>
<div id="js-googlesitekit-main-dashboard" data-view-only="<?php echo esc_attr( $is_view_only ); ?>" data-setup-module-slug="<?php echo esc_attr( $setup_module_slug ); ?>" class="googlesitekit-page"></div>
<?php
}
},
)
),
new Screen(
self::PREFIX . 'splash',
array(
'title' => __( 'Dashboard', 'google-site-kit' ),
'capability' => Permissions::VIEW_SPLASH,
'parent_slug' => $show_splash_in_menu ? Screen::MENU_SLUG : self::PARENT_SLUG_NULL,
// This callback will redirect to the dashboard on successful authentication.
'initialize_callback' => function ( Context $context ) {
// Get the dismissed items for this user.
$user_options = new User_Options( $context );
$dismissed_items = new Dismissed_Items( $user_options );
$splash_context = $context->input()->filter( INPUT_GET, 'googlesitekit_context' );
$reset_session = $context->input()->filter( INPUT_GET, 'googlesitekit_reset_session', FILTER_VALIDATE_BOOLEAN );
// If the user is authenticated, redirect them to the disconnect URL and then send them back here.
if ( ! $reset_session && 'revoked' === $splash_context && $this->authentication->is_authenticated() ) {
$this->authentication->disconnect();
wp_safe_redirect( add_query_arg( array( 'googlesitekit_reset_session' => 1 ) ) );
exit;
}
// Don't consider redirect if the current user cannot access the dashboard (yet).
if ( ! current_user_can( Permissions::VIEW_DASHBOARD ) ) {
return;
}
// Redirect to dashboard if user is authenticated or if
// they have already accessed the shared dashboard.
if (
$this->authentication->is_authenticated() ||
(
! current_user_can( Permissions::AUTHENTICATE ) &&
$dismissed_items->is_dismissed( 'shared_dashboard_splash' ) &&
current_user_can( Permissions::VIEW_SHARED_DASHBOARD )
)
) {
wp_safe_redirect(
$context->admin_url(
'dashboard',
array(
// Pass through the notification parameter, or removes it if none.
'notification' => $context->input()->filter( INPUT_GET, 'notification' ),
)
)
);
exit;
}
},
)
),
new Screen(
self::PREFIX . 'settings',
array(
'title' => __( 'Settings', 'google-site-kit' ),
'capability' => Permissions::MANAGE_OPTIONS,
)
),
);
$screens[] = new Screen(
self::PREFIX . 'user-input',
array(
'title' => __( 'User Input', 'google-site-kit' ),
'capability' => Permissions::MANAGE_OPTIONS,
'parent_slug' => self::PARENT_SLUG_NULL,
)
);
$screens[] = new Screen(
self::PREFIX . 'ad-blocking-recovery',
array(
'title' => __( 'Ad Blocking Recovery', 'google-site-kit' ),
'capability' => Permissions::MANAGE_OPTIONS,
'parent_slug' => self::PARENT_SLUG_NULL,
)
);
$screens[] = new Screen(
self::PREFIX . 'metric-selection',
array(
'title' => __( 'Select Key Metrics', 'google-site-kit' ),
'capability' => Permissions::MANAGE_OPTIONS,
'parent_slug' => self::PARENT_SLUG_NULL,
// This callback will redirect to the dashboard if key metrics is already set up.
'initialize_callback' => function ( Context $context ) {
$options = new Options( $context );
$is_key_metrics_setup = ( new Key_Metrics_Setup_Completed_By( $options ) )->get();
if ( $is_key_metrics_setup ) {
wp_safe_redirect(
$context->admin_url( 'dashboard' )
);
exit;
}
},
)
);
$screens[] = new Screen(
self::PREFIX . 'key-metrics-setup',
array(
'title' => __( 'Key Metrics Setup', 'google-site-kit' ),
'capability' => Permissions::MANAGE_OPTIONS,
'parent_slug' => self::PARENT_SLUG_NULL,
)
);
return $screens;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin\Standalone
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin;
use Google\Site_Kit\Context;
use Google\Site_Kit\Core\Assets\Stylesheet;
/**
* Class managing standalone mode.
*
* @since 1.8.0
* @access private
* @ignore
*/
final class Standalone {
/**
* Plugin context.
*
* @since 1.8.0
*
* @var Context
*/
private $context;
/**
* Constructor.
*
* @since 1.8.0
*
* @param Context $context Plugin context.
*/
public function __construct( Context $context ) {
$this->context = $context;
}
/**
* Standalone mode
*
* @since 1.8.0
*/
public function register() {
if ( ! $this->is_standalone() ) {
return;
}
/**
* Appends the standalone admin body class.
*
* @since 1.8.0
*
* @param string $admin_body_classes Admin body classes.
* @return string Filtered admin body classes.
*/
add_filter(
'admin_body_class',
function ( $admin_body_classes ) {
return "{$admin_body_classes} googlesitekit-standalone";
}
);
remove_action( 'in_admin_header', 'wp_admin_bar_render', 0 );
add_filter( 'admin_footer_text', '__return_empty_string', PHP_INT_MAX );
add_filter( 'update_footer', '__return_empty_string', PHP_INT_MAX );
add_action(
'admin_head',
function () {
$this->print_standalone_styles();
}
);
}
/**
* Detects if we are in Google Site Kit standalone mode.
*
* @since 1.8.0
*
* @return boolean True when in standalone mode, else false.
*/
public function is_standalone() {
global $pagenow;
$page = htmlspecialchars( $this->context->input()->filter( INPUT_GET, 'page' ) ?: '' );
$standalone = $this->context->input()->filter( INPUT_GET, 'googlesitekit-standalone', FILTER_VALIDATE_BOOLEAN );
return ( 'admin.php' === $pagenow && false !== strpos( $page, 'googlesitekit' ) && $standalone );
}
/**
* Enqueues styles for standalone mode.
*
* @since 1.8.0
*/
private function print_standalone_styles() {
?>
<style type="text/css">
html {
padding-top: 0 !important;
}
body.googlesitekit-standalone #adminmenumain {
display: none;
}
body.googlesitekit-standalone #wpcontent {
margin-left: 0;
}
</style>
<?php
}
}

View File

@@ -0,0 +1,406 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin_Bar\Admin_Bar
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin_Bar;
use Google\Site_Kit\Context;
use Google\Site_Kit\Core\Modules\Modules;
use Google\Site_Kit\Core\Permissions\Permissions;
use Google\Site_Kit\Core\Assets\Assets;
use Google\Site_Kit\Core\Authentication\Authentication;
use Google\Site_Kit\Core\REST_API\REST_Route;
use Google\Site_Kit\Core\REST_API\REST_Routes;
use Google\Site_Kit\Core\Storage\Options;
use Google\Site_Kit\Core\Util\Method_Proxy_Trait;
use Google\Site_Kit\Core\Util\Requires_Javascript_Trait;
use WP_REST_Server;
use WP_REST_Request;
/**
* Class handling the plugin's admin bar menu.
*
* @since 1.0.0
* @access private
* @ignore
*/
final class Admin_Bar {
use Requires_Javascript_Trait;
use Method_Proxy_Trait;
/**
* Plugin context.
*
* @since 1.0.0
* @var Context
*/
private $context;
/**
* Assets Instance.
*
* @since 1.0.0
* @var Assets
*/
private $assets;
/**
* Modules instance.
*
* @since 1.4.0
* @var Modules
*/
private $modules;
/**
* Admin_Bar_Enabled instance.
*
* @since 1.39.0
* @var Admin_Bar_Enabled
*/
private $admin_bar_enabled;
/**
* Authentication instance.
*
* @since 1.120.0
* @var Authentication
*/
private $authentication;
/**
* Constructor.
*
* @since 1.0.0
*
* @param Context $context Plugin context.
* @param Assets $assets Optional. Assets API instance. Default is a new instance.
* @param Modules $modules Optional. Modules instance. Default is a new instance.
*/
public function __construct(
Context $context,
?Assets $assets = null,
?Modules $modules = null
) {
$this->context = $context;
$this->assets = $assets ?: new Assets( $this->context );
$this->modules = $modules ?: new Modules( $this->context );
$options = new Options( $this->context );
$this->admin_bar_enabled = new Admin_Bar_Enabled( $options );
$this->authentication = new Authentication( $this->context );
}
/**
* Registers functionality through WordPress hooks.
*
* @since 1.0.0
*/
public function register() {
add_action( 'admin_bar_menu', $this->get_method_proxy( 'add_menu_button' ), 99 );
add_action( 'admin_enqueue_scripts', $this->get_method_proxy( 'enqueue_assets' ), 40 );
add_action( 'wp_enqueue_scripts', $this->get_method_proxy( 'enqueue_assets' ), 40 );
// TODO: This can be removed at some point, see https://github.com/ampproject/amp-wp/pull/4001.
add_filter( 'amp_dev_mode_element_xpaths', array( $this, 'add_amp_dev_mode' ) );
add_filter(
'googlesitekit_rest_routes',
function ( $routes ) {
return array_merge( $routes, $this->get_rest_routes() );
}
);
add_filter(
'googlesitekit_apifetch_preload_paths',
function ( $routes ) {
return array_merge(
$routes,
array(
'/' . REST_Routes::REST_ROOT . '/core/site/data/admin-bar-settings',
)
);
}
);
$this->admin_bar_enabled->register();
}
/**
* Add data-ampdevmode attributes to the elements that need it.
*
* @see \Google\Site_Kit\Core\Assets\Assets::get_assets() The 'googlesitekit' string is added to all inline scripts.
* @see \Google\Site_Kit\Core\Assets\Assets::add_amp_dev_mode_attributes() The data-ampdevmode attribute is added to registered scripts/styles here.
*
* @param string[] $xpath_queries XPath queries for elements that should get the data-ampdevmode attribute.
* @return string[] XPath queries.
*/
public function add_amp_dev_mode( $xpath_queries ) {
$xpath_queries[] = '//script[ contains( text(), "googlesitekit" ) ]';
return $xpath_queries;
}
/**
* Render the Adminbar button.
*
* @since 1.0.0
*
* @param object $wp_admin_bar The WP AdminBar object.
*/
private function add_menu_button( $wp_admin_bar ) {
if ( ! $this->is_active() ) {
return;
}
$args = array(
'id' => 'google-site-kit',
'title' => '<span class="googlesitekit-wp-adminbar__icon"></span> <span class="googlesitekit-wp-adminbar__label">Site Kit</span>',
'href' => '#',
'meta' => array(
'class' => 'menupop googlesitekit-wp-adminbar',
),
);
if ( $this->context->is_amp() && ! $this->is_amp_dev_mode() ) {
$post = get_post();
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
return;
}
$args['href'] = add_query_arg( 'googlesitekit_adminbar_open', 'true', get_edit_post_link( $post->ID ) );
} else {
$args['meta']['html'] = $this->menu_markup();
}
$wp_admin_bar->add_node( $args );
}
/**
* Checks if admin bar menu is active and displaying.
*
* @since 1.0.0
*
* @return bool True if Admin bar should display, False when it's not.
*/
public function is_active() {
// Only active if the admin bar is showing.
if ( ! is_admin_bar_showing() ) {
return false;
}
// In the admin, never show the admin bar except for the post editing screen.
if ( is_admin() && ! $this->is_admin_post_screen() ) {
return false;
}
if ( ! current_user_can( Permissions::VIEW_ADMIN_BAR_MENU ) ) {
return false;
}
$enabled = $this->admin_bar_enabled->get();
if ( ! $enabled ) {
return false;
}
// No entity was identified - don't display the admin bar menu.
$entity = $this->context->get_reference_entity();
if ( ! $entity ) {
return false;
}
// Check permissions for viewing post data.
if ( in_array( $entity->get_type(), array( 'post', 'blog' ), true ) && $entity->get_id() ) {
// If a post entity, check permissions for that post.
if ( ! current_user_can( Permissions::VIEW_POST_INSIGHTS, $entity->get_id() ) ) {
return false;
}
}
$current_url = $entity->get_url();
/**
* Filters whether the Site Kit admin bar menu should be displayed.
*
* The admin bar menu is only shown when there is data for the current URL and the current
* user has the correct capability to view the data. Modules use this filter to indicate the
* presence of valid data.
*
* @since 1.0.0
*
* @param bool $display Whether to display the admin bar menu.
* @param string $current_url The URL of the current request.
*/
return apply_filters( 'googlesitekit_show_admin_bar_menu', true, $current_url );
}
/**
* Checks if current screen is an admin edit post screen.
*
* @since 1.0.0
*/
private function is_admin_post_screen() {
$current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false;
// No screen context available.
if ( ! $current_screen instanceof \WP_Screen ) {
return false;
}
// Only show for post screens.
if ( 'post' !== $current_screen->base ) {
return false;
}
// Don't show for new post screen.
if ( 'add' === $current_screen->action ) {
return false;
}
return true;
}
/**
* Checks whether AMP dev mode is enabled.
*
* This is only relevant if the current context is AMP.
*
* @since 1.1.0
* @since 1.120.0 Added the `data-view-only` attribute.
*
* @return bool True if AMP dev mode is enabled, false otherwise.
*/
private function is_amp_dev_mode() {
return function_exists( 'amp_is_dev_mode' ) && amp_is_dev_mode();
}
/**
* Return the Adminbar content markup.
*
* @since 1.0.0
*/
private function menu_markup() {
// Start buffer output.
ob_start();
$is_view_only = ! $this->authentication->is_authenticated();
?>
<div class="googlesitekit-plugin ab-sub-wrapper">
<?php $this->render_noscript_html(); ?>
<div id="js-googlesitekit-adminbar" data-view-only="<?php echo esc_attr( $is_view_only ); ?>" class="googlesitekit-adminbar">
<?php
/**
* Display server rendered content before JS-based adminbar modules.
*
* @since 1.0.0
*/
do_action( 'googlesitekit_adminbar_modules_before' );
?>
<section id="js-googlesitekit-adminbar-modules" class="googlesitekit-adminbar-modules"></section>
<?php
/**
* Display server rendered content after JS-based adminbar modules.
*
* @since 1.0.0
*/
do_action( 'googlesitekit_adminbar_modules_after' );
?>
</div>
</div>
<?php
// Get the buffer output.
$markup = ob_get_clean();
return $markup;
}
/**
* Enqueues assets.
*
* @since 1.39.0
*/
private function enqueue_assets() {
if ( ! $this->is_active() ) {
return;
}
// Enqueue styles.
$this->assets->enqueue_asset( 'googlesitekit-adminbar-css' );
if ( $this->context->is_amp() && ! $this->is_amp_dev_mode() ) {
// AMP Dev Mode support was added in v1.4, and if it is not enabled then short-circuit since scripts will be invalid.
return;
}
// Enqueue scripts.
$this->assets->enqueue_asset( 'googlesitekit-adminbar' );
$this->modules->enqueue_assets();
}
/**
* Gets related REST routes.
*
* @since 1.39.0
*
* @return array List of REST_Route objects.
*/
private function get_rest_routes() {
$can_authenticate = function () {
return current_user_can( Permissions::AUTHENTICATE );
};
$settings_callback = function () {
return array(
'enabled' => $this->admin_bar_enabled->get(),
);
};
return array(
new REST_Route(
'core/site/data/admin-bar-settings',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => $settings_callback,
'permission_callback' => $can_authenticate,
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => function ( WP_REST_Request $request ) use ( $settings_callback ) {
$data = $request->get_param( 'data' );
if ( isset( $data['enabled'] ) ) {
$this->admin_bar_enabled->set( ! empty( $data['enabled'] ) );
}
return $settings_callback( $request );
},
'permission_callback' => $can_authenticate,
'args' => array(
'data' => array(
'type' => 'object',
'required' => true,
'properties' => array(
'enabled' => array(
'type' => 'boolean',
'required' => false,
),
),
),
),
),
)
),
);
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* Class Google\Site_Kit\Core\Admin_Bar\Admin_Bar_Enabled
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Admin_Bar;
use Google\Site_Kit\Core\Storage\Setting;
/**
* Class handling the admin bar menu settings.
*
* @since 1.39.0
* @access private
* @ignore
*/
class Admin_Bar_Enabled extends Setting {
/**
* The option_name for this setting.
*/
const OPTION = 'googlesitekit_admin_bar_menu_enabled';
/**
* Gets the value of the setting.
*
* @since 1.39.0
*
* @return bool Value set for the option, or registered default if not set.
*/
public function get() {
return (bool) parent::get();
}
/**
* Gets the expected value type.
*
* @since 1.39.0
*
* @return string The type name.
*/
protected function get_type() {
return 'boolean';
}
/**
* Gets the default value.
*
* @since 1.39.0
*
* @return boolean The default value.
*/
protected function get_default() {
return true;
}
/**
* Gets the callback for sanitizing the setting's value before saving.
*
* @since 1.39.0
*
* @return callable The callable sanitize callback.
*/
protected function get_sanitize_callback() {
return 'boolval';
}
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* Class Google\Site_Kit\Core\Assets\Asset
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Assets;
use Google\Site_Kit\Context;
/**
* Class representing a single asset.
*
* @since 1.0.0
* @access private
* @ignore
*/
abstract class Asset {
// Various page contexts for Site Kit in the WordPress Admin.
const CONTEXT_ADMIN_GLOBAL = 'admin-global';
const CONTEXT_ADMIN_POST_EDITOR = 'admin-post-editor';
const CONTEXT_ADMIN_BLOCK_EDITOR = 'admin-block-editor';
const CONTEXT_ADMIN_POSTS = 'admin-posts';
const CONTEXT_ADMIN_SITEKIT = 'admin-sitekit';
/**
* Unique asset handle.
*
* @since 1.0.0
* @var string
*/
protected $handle;
/**
* Asset arguments.
*
* @since 1.0.0
* @var array
*/
protected $args = array();
/**
* Constructor.
*
* @since 1.0.0
* @since 1.37.0 Add the 'load_contexts' argument.
*
* @param string $handle Unique asset handle.
* @param array $args {
* Associative array of asset arguments.
*
* @type string $src Required asset source URL.
* @type array $dependencies List of asset dependencies. Default empty array.
* @type string $version Asset version. Default is the version of Site Kit.
* @type bool $fallback Whether to only register as a fallback. Default false.
* @type callable $before_print Optional callback to execute before printing. Default none.
* @type string[] $load_contexts Optional array of page context values to determine on which page types to load this asset (see the `CONTEXT_` variables above).
* }
*/
public function __construct( $handle, array $args ) {
$this->handle = $handle;
$this->args = wp_parse_args(
$args,
array(
'src' => '',
'dependencies' => array(),
'version' => GOOGLESITEKIT_VERSION,
'fallback' => false,
'before_print' => null,
'load_contexts' => array( self::CONTEXT_ADMIN_SITEKIT ),
)
);
}
/**
* Gets the notice handle.
*
* @since 1.0.0
*
* @return string Unique notice handle.
*/
public function get_handle() {
return $this->handle;
}
/**
* Checks to see if the specified context exists for the current request.
*
* @since 1.37.0
*
* @param string $context Context value (see the `CONTEXT_` variables above).
* @return bool TRUE if context exists; FALSE otherwise.
*/
public function has_context( $context ) {
return in_array( $context, $this->args['load_contexts'], true );
}
/**
* Registers the asset.
*
* @since 1.0.0
* @since 1.15.0 Adds $context parameter.
*
* @param Context $context Plugin context.
*/
abstract public function register( Context $context );
/**
* Enqueues the asset.
*
* @since 1.0.0
*/
abstract public function enqueue();
/**
* Executes the extra callback if defined before printing the asset.
*
* @since 1.2.0
*/
final public function before_print() {
if ( ! is_callable( $this->args['before_print'] ) ) {
return;
}
call_user_func( $this->args['before_print'], $this->handle );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
<?php
/**
* Class Google\Site_Kit\Core\Assets\Manifest
*
* @package GoogleSite_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Assets;
use Google\Site_Kit\Plugin;
/**
* Assets manifest.
*
* @since 1.15.0
* @access private
* @ignore
*/
class Manifest {
/**
* Entries as $handle => [ $filename, $hash ] map.
*
* @since 1.48.0
* @var array
*/
private static $data;
/**
* Gets the manifest entry for the given handle.
*
* @since 1.48.0
*
* @param string $handle Asset handle to get manifest data for.
* @return array List of $filename and $hash, or `null` for both if not found.
*/
public static function get( $handle ) {
if ( null === self::$data ) {
self::load();
}
if ( isset( self::$data[ $handle ] ) ) {
return self::$data[ $handle ];
}
return array( null, null );
}
/**
* Loads the generated manifest file.
*
* @since 1.48.0
*/
private static function load() {
$path = Plugin::instance()->context()->path( 'dist/manifest.php' );
if ( file_exists( $path ) ) {
// If the include fails, $data will be `false`
// so this should only be attempted once.
self::$data = include $path;
}
}
}

View File

@@ -0,0 +1,174 @@
<?php
/**
* Class Google\Site_Kit\Core\Assets\Script
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Assets;
use Google\Site_Kit\Context;
use Google\Site_Kit\Core\Util\BC_Functions;
/**
* Class representing a single script.
*
* @since 1.0.0
* @access private
* @ignore
*/
class Script extends Asset {
/**
* Constructor.
*
* @since 1.0.0
*
* @param string $handle Unique script handle.
* @param array $args {
* Associative array of script arguments.
*
* @type string $src Required script source URL.
* @type array $dependencies List of script dependencies. Default empty array.
* @type string $version Script version. Default is the version of Site Kit.
* @type bool $fallback Whether to only register as a fallback. Default false.
* @type callable $before_print Optional callback to execute before printing. Default none.
* @type bool $in_footer Whether to load script in footer. Default true.
* @type string $execution How to handle script execution, e.g. 'defer'. Default empty string.
* }
*/
public function __construct( $handle, array $args ) {
parent::__construct( $handle, $args );
$this->args = wp_parse_args(
$this->args,
array(
'in_footer' => true,
'execution' => '',
)
);
}
/**
* Registers the script.
*
* @since 1.0.0
* @since 1.15.0 Adds $context parameter.
*
* @param Context $context Plugin context.
*/
public function register( Context $context ) {
if ( $this->args['fallback'] && wp_script_is( $this->handle, 'registered' ) ) {
return;
}
$src = $this->args['src'];
$version = $this->args['version'];
if ( $src ) {
$entry = Manifest::get( $this->handle );
if ( is_array( $entry[0] ) ) {
// If the first entry item is an array, we can assume `$entry` is an array of entries in the format filename => hash.
// In this scenario we want to match the nested entry against the filename provided in `$src`.
$src_filename = basename( $src );
foreach ( $entry as $entry_pair ) {
if ( $this->is_matching_manifest_entry( $entry_pair, $src_filename ) ) {
list( $filename, $hash ) = $entry_pair;
break;
}
}
} else {
// Otherwise, `$entry` will be a single entry in the format filename => hash.
list( $filename, $hash ) = $entry;
}
if ( $filename ) {
$src = $context->url( 'dist/assets/js/' . $filename );
$version = $hash;
}
}
wp_register_script(
$this->handle,
$src,
(array) $this->args['dependencies'],
$version,
$this->args['in_footer']
);
if ( ! empty( $this->args['execution'] ) ) {
wp_script_add_data( $this->handle, 'script_execution', $this->args['execution'] );
}
if ( ! empty( $src ) ) {
$this->set_locale_data();
}
}
/**
* Enqueues the script.
*
* @since 1.0.0
*/
public function enqueue() {
wp_enqueue_script( $this->handle );
}
/**
* Checks if the provided manifest entry matches the given filename.
*
* @since 1.89.0
*
* @param array $entry Array of filename, hash.
* @param string $src_filename Filename to check.
* @return bool
*/
private function is_matching_manifest_entry( array $entry, $src_filename ) {
list ( $filename, $hash ) = $entry;
if ( ! isset( $hash ) ) {
// If the hash is not set, it means the hash is embedded in the entry filename.
// Remove the hash then compare to the src filename.
$entry_filename_without_hash = preg_replace( '/-[a-f0-9]+\.js$/', '.js', $filename );
if ( $src_filename === $entry_filename_without_hash ) {
return true;
}
}
if ( $filename === $src_filename ) {
return true;
}
return false;
}
/**
* Sets locale data for the script, if it has translations.
*
* @since 1.21.0
*/
private function set_locale_data() {
$json_translations = load_script_textdomain( $this->handle, 'google-site-kit' );
if ( ! $json_translations ) {
return;
}
$output = <<<JS
( function( domain, translations ) {
try {
var localeData = translations.locale_data[ domain ] || translations.locale_data.messages;
localeData[""].domain = domain;
googlesitekit.i18n.setLocaleData( localeData, domain );
} catch {
}
} )( "google-site-kit", {$json_translations} );
JS;
wp_add_inline_script( $this->handle, $output, 'before' );
}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* Class Google\Site_Kit\Core\Assets\Script_Data
*
* @package Google\Site_Kit\Core\Assets
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Assets;
/**
* Class for virtual "data-only" scripts.
*
* @since 1.5.0
* @access private
* @ignore
*/
class Script_Data extends Script {
/**
* Constructor.
*
* @since 1.5.0
*
* @param string $handle Unique script handle.
* @param array $args {
* Associative array of script arguments.
*
* @type callable $data_callback Required. Function to return JSON-encodable data.
* @type string $global Required. Name of global variable to assign data to in Javascript.
* @type array $dependencies Optional. List of script dependencies. Default empty array.
* }
*/
public function __construct( $handle, array $args ) {
// Ensure required keys are always set.
$args = $args + array(
'data_callback' => null,
'global' => '',
);
// SRC will always be false.
$args['src'] = false;
parent::__construct( $handle, $args );
// Lazy-load script data before handle is to be printed.
$this->args['before_print'] = function ( $handle ) {
if ( empty( $this->args['global'] ) || ! is_callable( $this->args['data_callback'] ) ) {
return;
}
$data = call_user_func( $this->args['data_callback'], $handle );
$this->add_script_data( $data );
};
}
/**
* Adds the given data to the script handle's 'data' key.
*
* 'data' is the key used by `wp_localize_script`, which is output
* in older versions of WP even if the handle has no src (such as an alias).
* This is done manually instead of using `wp_localize_script` to avoid casting
* top-level keys to strings as this function is primarily intended for
* providing an array of translations to Javascript rather than arbitrary data.
*
* @see \WP_Scripts::localize
*
* @since 1.5.0
*
* @param mixed $data Data to be assigned to the defined global.
*/
private function add_script_data( $data ) {
$script_data = wp_scripts()->get_data( $this->handle, 'data' ) ?: '';
$js = sprintf(
'var %s = %s;',
preg_replace( '[^\w\d_-]', '', $this->args['global'] ), // Ensure only a-zA-Z0-9_- are allowed.
wp_json_encode( $data )
);
wp_scripts()->add_data( $this->handle, 'data', trim( "$script_data\n$js" ) );
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* Class Google\Site_Kit\Core\Assets\Stylesheet
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Assets;
use Google\Site_Kit\Context;
/**
* Class representing a single stylesheet.
*
* @since 1.0.0
* @access private
* @ignore
*/
final class Stylesheet extends Asset {
/**
* Constructor.
*
* @since 1.0.0
*
* @param string $handle Unique stylesheet handle.
* @param array $args {
* Associative array of stylesheet arguments.
*
* @type string $src Required stylesheet source URL.
* @type array $dependencies List of stylesheet dependencies. Default empty array.
* @type string $version Stylesheet version. Default is the version of Site Kit.
* @type bool $fallback Whether to only register as a fallback. Default false.
* @type callable $before_print Optional callback to execute before printing. Default none.
* @type string $media Media for which the stylesheet is defined. Default 'all'.
* }
*/
public function __construct( $handle, array $args ) {
parent::__construct( $handle, $args );
$this->args = wp_parse_args(
$this->args,
array(
'media' => 'all',
)
);
}
/**
* Registers the stylesheet.
*
* @since 1.0.0
* @since 1.15.0 Adds $context parameter.
*
* @param Context $context Plugin context.
*/
public function register( Context $context ) {
if ( $this->args['fallback'] && wp_style_is( $this->handle, 'registered' ) ) {
return;
}
$src = $this->args['src'];
$version = $this->args['version'];
list( $filename, $hash ) = Manifest::get( $this->handle );
if ( $filename ) {
$src = $context->url( 'dist/assets/css/' . $filename );
$version = $hash;
}
wp_register_style(
$this->handle,
$src,
(array) $this->args['dependencies'],
$version,
$this->args['media']
);
}
/**
* Enqueues the stylesheet.
*
* @since 1.0.0
*/
public function enqueue() {
wp_enqueue_style( $this->handle );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,197 @@
<?php
/**
* Class Google\Site_Kit\Core\Authentication\Clients\Client_Factory
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Authentication\Clients;
use Exception;
use Google\Site_Kit\Core\Authentication\Google_Proxy;
use Google\Site_Kit\Core\HTTP\Middleware;
use Google\Site_Kit_Dependencies\GuzzleHttp\Client;
use WP_HTTP_Proxy;
/**
* Class for creating Site Kit-specific Google_Client instances.
*
* @since 1.39.0
* @access private
* @ignore
*/
final class Client_Factory {
/**
* Creates a new Google client instance for the given arguments.
*
* @since 1.39.0
*
* @param array $args Associative array of arguments.
* @return Google_Site_Kit_Client|Google_Site_Kit_Proxy_Client The created Google client instance.
*/
public static function create_client( array $args ) {
$args = array_merge(
array(
'client_id' => '',
'client_secret' => '',
'redirect_uri' => '',
'token' => array(),
'token_callback' => null,
'token_exception_callback' => null,
'required_scopes' => array(),
'login_hint_email' => '',
'using_proxy' => true,
'proxy_url' => Google_Proxy::PRODUCTION_BASE_URL,
),
$args
);
if ( $args['using_proxy'] ) {
$client = new Google_Site_Kit_Proxy_Client(
array( 'proxy_base_path' => $args['proxy_url'] )
);
} else {
$client = new Google_Site_Kit_Client();
}
// Enable exponential retries, try up to three times.
$client->setConfig( 'retry', array( 'retries' => 3 ) );
$http_client = $client->getHttpClient();
$http_client_config = self::get_http_client_config( $http_client->getConfig() );
// In Guzzle 6+, the HTTP client is immutable, so only a new instance can be set.
$client->setHttpClient( new Client( $http_client_config ) );
$auth_config = self::get_auth_config( $args['client_id'], $args['client_secret'], $args['redirect_uri'] );
if ( ! empty( $auth_config ) ) {
try {
$client->setAuthConfig( $auth_config );
} catch ( Exception $e ) {
return $client;
}
}
// Offline access so we can access the refresh token even when the user is logged out.
$client->setAccessType( 'offline' );
$client->setPrompt( 'consent' );
$client->setRedirectUri( $args['redirect_uri'] );
$client->setScopes( (array) $args['required_scopes'] );
// Set the full token data.
if ( ! empty( $args['token'] ) ) {
$client->setAccessToken( $args['token'] );
}
// Set the callback which is called when the client refreshes the access token on-the-fly.
$token_callback = $args['token_callback'];
if ( $token_callback ) {
$client->setTokenCallback(
function ( $cache_key, $access_token ) use ( $client, $token_callback ) {
// The same token from this callback should also already be set in the client object, which is useful
// to get the full token data, all of which needs to be saved. Just in case, if that is not the same,
// we save the passed token only, relying on defaults for the other values.
$token = $client->getAccessToken();
if ( $access_token !== $token['access_token'] ) {
$token = array( 'access_token' => $access_token );
}
$token_callback( $token );
}
);
}
// Set the callback which is called when refreshing the access token on-the-fly fails.
$token_exception_callback = $args['token_exception_callback'];
if ( ! empty( $token_exception_callback ) ) {
$client->setTokenExceptionCallback( $token_exception_callback );
}
if ( ! empty( $args['login_hint_email'] ) ) {
$client->setLoginHint( $args['login_hint_email'] );
}
return $client;
}
/**
* Get HTTP client configuration.
*
* @since 1.115.0
*
* @param array $config Initial configuration.
* @return array The new HTTP client configuration.
*/
private static function get_http_client_config( $config ) {
// Override the default user-agent for the Guzzle client. This is used for oauth/token requests.
// By default this header uses the generic Guzzle client's user-agent and includes
// Guzzle, cURL, and PHP versions as it is normally shared.
// In our case however, the client is namespaced to be used by Site Kit only.
$config['headers']['User-Agent'] = Google_Proxy::get_application_name();
/** This filter is documented in wp-includes/class-http.php */
$ssl_verify = apply_filters( 'https_ssl_verify', true, null );
// If SSL verification is enabled (default) use the SSL certificate bundle included with WP.
if ( $ssl_verify ) {
$config['verify'] = ABSPATH . WPINC . '/certificates/ca-bundle.crt';
} else {
$config['verify'] = false;
}
// Configure the Google_Client's HTTP client to use the same HTTP proxy as WordPress HTTP, if set.
$http_proxy = new WP_HTTP_Proxy();
if ( $http_proxy->is_enabled() ) {
// See https://docs.guzzlephp.org/en/6.5/request-options.html#proxy for reference.
$auth = $http_proxy->use_authentication() ? "{$http_proxy->authentication()}@" : '';
$config['proxy'] = "{$auth}{$http_proxy->host()}:{$http_proxy->port()}";
}
// Respect WordPress HTTP request blocking settings.
$config['handler']->push(
Middleware::block_external_request()
);
/**
* Filters the IP version to force hostname resolution with.
*
* @since 1.115.0
*
* @param $force_ip_resolve null|string IP version to force. Default: null.
*/
$force_ip_resolve = apply_filters( 'googlesitekit_force_ip_resolve', null );
if ( in_array( $force_ip_resolve, array( null, 'v4', 'v6' ), true ) ) {
$config['force_ip_resolve'] = $force_ip_resolve;
}
return $config;
}
/**
* Returns the full OAuth credentials configuration data based on the given client ID and secret.
*
* @since 1.39.0
*
* @param string $client_id OAuth client ID.
* @param string $client_secret OAuth client secret.
* @param string $redirect_uri OAuth redirect URI.
* @return array Credentials data, or empty array if any of the given values is empty.
*/
private static function get_auth_config( $client_id, $client_secret, $redirect_uri ) {
if ( ! $client_id || ! $client_secret || ! $redirect_uri ) {
return array();
}
return array(
'client_id' => $client_id,
'client_secret' => $client_secret,
'auth_uri' => 'https://accounts.google.com/o/oauth2/v2/auth',
'token_uri' => 'https://oauth2.googleapis.com/token',
'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs',
'redirect_uris' => array( $redirect_uri ),
);
}
}

View File

@@ -0,0 +1,312 @@
<?php
/**
* Class Google\Site_Kit\Core\Authentication\Clients\Google_Site_Kit_Client
*
* @package Google\Site_Kit
* @copyright 2021 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Core\Authentication\Clients;
use Google\Site_Kit\Core\Authentication\Clients\OAuth2;
use Google\Site_Kit\Core\Authentication\Exception\Google_OAuth_Exception;
use Google\Site_Kit_Dependencies\Google_Client;
use Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpHandlerFactory;
use Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpClientCache;
use Google\Site_Kit_Dependencies\GuzzleHttp\ClientInterface;
use Google\Site_Kit_Dependencies\Psr\Http\Message\RequestInterface;
use Google\Site_Kit\Core\Util\URL;
use Exception;
use InvalidArgumentException;
use LogicException;
use WP_User;
/**
* Extended Google API client with custom functionality for Site Kit.
*
* @since 1.2.0
* @access private
* @ignore
*/
class Google_Site_Kit_Client extends Google_Client {
/**
* Callback to pass a potential exception to while refreshing an access token.
*
* @since 1.2.0
* @var callable|null
*/
protected $token_exception_callback;
/**
* Construct the Google client.
*
* @since 1.2.0
*
* @param array $config Client configuration.
*/
public function __construct( array $config = array() ) {
if ( isset( $config['token_exception_callback'] ) ) {
$this->setTokenExceptionCallback( $config['token_exception_callback'] );
}
unset( $config['token_exception_callback'] );
parent::__construct( $config );
}
/**
* Sets the function to be called when fetching an access token results in an exception.
*
* @since 1.2.0
*
* @param callable $exception_callback Function accepting an exception as single parameter.
*/
public function setTokenExceptionCallback( callable $exception_callback ) {
$this->token_exception_callback = $exception_callback;
}
/**
* Sets whether or not to return raw requests and returns a callback to reset to the previous value.
*
* @since 1.2.0
*
* @param bool $defer Whether or not to return raw requests.
* @return callable Callback function that resets to the original $defer value.
*/
public function withDefer( $defer ) {
$orig_defer = $this->shouldDefer();
$this->setDefer( $defer );
// Return a function to restore the original refer value.
return function () use ( $orig_defer ) {
$this->setDefer( $orig_defer );
};
}
/**
* Adds auth listeners to the HTTP client based on the credentials set in the Google API Client object.
*
* @since 1.2.0
*
* @param ClientInterface $http The HTTP client object.
* @return ClientInterface The HTTP client object.
*
* @throws Exception Thrown when fetching a new access token via refresh token on-the-fly fails.
*/
public function authorize( ?ClientInterface $http = null ) {
if ( $this->isUsingApplicationDefaultCredentials() ) {
return parent::authorize( $http );
}
$token = $this->getAccessToken();
if ( isset( $token['refresh_token'] ) && $this->isAccessTokenExpired() ) {
$callback = $this->getConfig( 'token_callback' );
try {
$token_response = $this->fetchAccessTokenWithRefreshToken( $token['refresh_token'] );
if ( $callback ) {
// Due to original callback signature this can only accept the token itself.
call_user_func( $callback, '', $token_response['access_token'] );
}
} catch ( Exception $e ) {
// Pass exception to special callback if provided.
if ( $this->token_exception_callback ) {
call_user_func( $this->token_exception_callback, $e );
}
throw $e;
}
}
return parent::authorize( $http );
}
/**
* Fetches an OAuth 2.0 access token by using a temporary code.
*
* @since 1.0.0
* @since 1.2.0 Ported from Google_Site_Kit_Proxy_Client.
* @since 1.149.0 Added $code_verifier param for client v2.15.0 compatibility. (@link https://github.com/googleapis/google-api-php-client/commit/bded223ece445a6130cde82417b20180b1d6698a)
*
* @param string $code Temporary authorization code, or undelegated token code.
* @param string $code_verifier The code verifier used for PKCE (if applicable).
*
* @return array Access token.
*
* @throws InvalidArgumentException Thrown when the passed code is empty.
*/
public function fetchAccessTokenWithAuthCode( $code, $code_verifier = null ) {
if ( strlen( $code ) === 0 ) {
throw new InvalidArgumentException( 'Invalid code' );
}
$auth = $this->getOAuth2Service();
$auth->setCode( $code );
$auth->setRedirectUri( $this->getRedirectUri() );
if ( $code_verifier ) {
$auth->setCodeVerifier( $code_verifier );
}
$http_handler = HttpHandlerFactory::build( $this->getHttpClient() );
$token_response = $this->fetchAuthToken( $auth, $http_handler );
if ( $token_response && isset( $token_response['access_token'] ) ) {
$token_response['created'] = time();
$this->setAccessToken( $token_response );
}
return $token_response;
}
/**
* Fetches a fresh OAuth 2.0 access token by using a refresh token.
*
* @since 1.0.0
* @since 1.2.0 Ported from Google_Site_Kit_Proxy_Client.
*
* @param string $refresh_token Optional. Refresh token. Unused here.
* @param array $extra_params Optional. Array of extra parameters to fetch with.
* @return array Access token.
*
* @throws LogicException Thrown when no refresh token is available.
*/
public function fetchAccessTokenWithRefreshToken( $refresh_token = null, $extra_params = array() ) {
if ( null === $refresh_token ) {
$refresh_token = $this->getRefreshToken();
if ( ! $refresh_token ) {
throw new LogicException( 'refresh token must be passed in or set as part of setAccessToken' );
}
}
$this->getLogger()->info( 'OAuth2 access token refresh' );
$auth = $this->getOAuth2Service();
$auth->setRefreshToken( $refresh_token );
$http_handler = HttpHandlerFactory::build( $this->getHttpClient() );
$token_response = $this->fetchAuthToken( $auth, $http_handler, $extra_params );
if ( $token_response && isset( $token_response['access_token'] ) ) {
$token_response['created'] = time();
if ( ! isset( $token_response['refresh_token'] ) ) {
$token_response['refresh_token'] = $refresh_token;
}
$this->setAccessToken( $token_response );
/**
* Fires when the current user has just been reauthorized to access Google APIs with a refreshed access token.
*
* In other words, this action fires whenever Site Kit has just obtained a new access token based on
* the refresh token for the current user, which typically happens once every hour when using Site Kit,
* since that is the lifetime of every access token.
*
* @since 1.25.0
*
* @param array $token_response Token response data.
*/
do_action( 'googlesitekit_reauthorize_user', $token_response );
}
return $token_response;
}
/**
* Executes deferred HTTP requests.
*
* @since 1.38.0
*
* @param RequestInterface $request Request object to execute.
* @param string $expected_class Expected class to return.
* @return object An object of the type of the expected class or Psr\Http\Message\ResponseInterface.
*/
public function execute( RequestInterface $request, $expected_class = null ) {
$request = $request->withHeader( 'X-Goog-Quota-User', self::getQuotaUser() );
return parent::execute( $request, $expected_class );
}
/**
* Returns a string that uniquely identifies a user of the application.
*
* @since 1.38.0
*
* @return string Unique user identifier.
*/
public static function getQuotaUser() {
$user_id = get_current_user_id();
$url = get_home_url();
$scheme = URL::parse( $url, PHP_URL_SCHEME );
$host = URL::parse( $url, PHP_URL_HOST );
$path = URL::parse( $url, PHP_URL_PATH );
return "{$scheme}://{$user_id}@{$host}{$path}";
}
/**
* Fetches an OAuth 2.0 access token using a given auth object and HTTP handler.
*
* This method is used in place of {@see OAuth2::fetchAuthToken()}.
*
* @since 1.0.0
* @since 1.2.0 Ported from Google_Site_Kit_Proxy_Client.
*
* @param OAuth2 $auth OAuth2 instance.
* @param callable|null $http_handler Optional. HTTP handler callback. Default null.
* @param array $extra_params Optional. Array of extra parameters to fetch with.
* @return array Access token.
*/
protected function fetchAuthToken( OAuth2 $auth, ?callable $http_handler = null, $extra_params = array() ) {
if ( is_null( $http_handler ) ) {
$http_handler = HttpHandlerFactory::build( HttpClientCache::getHttpClient() );
}
$request = $auth->generateCredentialsRequest( $extra_params );
$response = $http_handler( $request );
$credentials = $auth->parseTokenResponse( $response );
if ( ! empty( $credentials['error'] ) ) {
$this->handleAuthTokenErrorResponse( $credentials['error'], $credentials );
}
$auth->updateToken( $credentials );
return $credentials;
}
/**
* Handles an erroneous response from a request to fetch an auth token.
*
* @since 1.2.0
*
* @param string $error Error code / error message.
* @param array $data Associative array of full response data.
*
* @throws Google_OAuth_Exception Thrown with the given $error as message.
*/
protected function handleAuthTokenErrorResponse( $error, array $data ) {
throw new Google_OAuth_Exception( $error );
}
/**
* Create a default Google OAuth2 object.
*
* @return OAuth2 Created OAuth2 instance.
*/
protected function createOAuth2Service() {
$auth = new OAuth2(
array(
'clientId' => $this->getClientId(),
'clientSecret' => $this->getClientSecret(),
'authorizationUri' => self::OAUTH2_AUTH_URL,
'tokenCredentialUri' => self::OAUTH2_TOKEN_URI,
'redirectUri' => $this->getRedirectUri(),
'issuer' => $this->getConfig( 'client_id' ),
'signingKey' => $this->getConfig( 'signing_key' ),
'signingAlgorithm' => $this->getConfig( 'signing_algorithm' ),
)
);
return $auth;
}
}

Some files were not shown because too many files have changed in this diff Show More