PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | modules/wordads/js/wordads-ccpa.js +355 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,355 @@
1 +/**
2 + * Outputs Javascript to handle California IP detection, consent modal, and setting of default cookies.
3 + */
4 +( function () {
5 + /* global ccpaSettings */
6 +
7 + // Minimal Mozilla Cookie library.
8 + // https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie/Simple_document.cookie_framework
9 + var cookieLib = {
10 + getItem: function ( e ) {
11 + return (
12 + ( e &&
13 + decodeURIComponent(
14 + document.cookie.replace(
15 + new RegExp(
16 + '(?:(?:^|.*;)\\s*' +
17 + encodeURIComponent( e ).replace( /[-.+*]/g, '\\$&' ) +
18 + '\\s*\\=\\s*([^;]*).*$)|^.*$'
19 + ),
20 + '$1'
21 + )
22 + ) ) ||
23 + null
24 + );
25 + },
26 + setItem: function ( e, o, n, t, r, i ) {
27 + if ( ! e || /^(?:expires|max-age|path|domain|secure)$/i.test( e ) ) {
28 + return ! 1;
29 + }
30 + var c = '';
31 + if ( n ) {
32 + switch ( n.constructor ) {
33 + case Number:
34 + c = n === 1 / 0 ? '; expires=Fri, 31 Dec 9999 23:59:59 GMT' : '; max-age=' + n;
35 + break;
36 + case String:
37 + c = '; expires=' + n;
38 + break;
39 + case Date:
40 + c = '; expires=' + n.toUTCString();
41 + }
42 + }
43 + return (
44 + ( 'rootDomain' !== r && '.rootDomain' !== r ) ||
45 + ( r =
46 + ( '.rootDomain' === r ? '.' : '' ) +
47 + document.location.hostname.split( '.' ).slice( -2 ).join( '.' ) ),
48 + ( document.cookie =
49 + encodeURIComponent( e ) +
50 + '=' +
51 + encodeURIComponent( o ) +
52 + c +
53 + ( r ? '; domain=' + r : '' ) +
54 + ( t ? '; path=' + t : '' ) +
55 + ( i ? '; secure' : '' ) ),
56 + ! 0
57 + );
58 + },
59 + };
60 +
61 + // Implement IAB USP API.
62 + window.__uspapi = function ( command, version, callback ) {
63 + // Validate callback.
64 + if ( typeof callback !== 'function' ) {
65 + return;
66 + }
67 +
68 + // Validate the given command.
69 + if ( command !== 'getUSPData' || version !== 1 ) {
70 + callback( null, false );
71 + return;
72 + }
73 +
74 + // Check for GPC. If set, override any stored cookie.
75 + if ( navigator.globalPrivacyControl ) {
76 + callback( { version: 1, uspString: '1YYN' }, true );
77 + return;
78 + }
79 +
80 + // Check for cookie.
81 + var consent = cookieLib.getItem( 'usprivacy' );
82 +
83 + // Invalid cookie.
84 + if ( null === consent ) {
85 + callback( null, false );
86 + return;
87 + }
88 +
89 + // Everything checks out. Fire the provided callback with the consent data.
90 + callback( { version: 1, uspString: consent }, true );
91 + };
92 +
93 + var setDefaultOptInCookie = function () {
94 + var value = ccpaSettings.defaultOptInCookieString;
95 + var domain =
96 + '.wordpress.com' === location.hostname.slice( -14 ) ? '.rootDomain' : location.hostname;
97 + cookieLib.setItem( 'usprivacy', value, 365 * 24 * 60 * 60, '/', domain );
98 + };
99 +
100 + var setDefaultOptOutCookie = function () {
101 + var value = ccpaSettings.defaultOptOutCookieString;
102 + var domain =
103 + '.wordpress.com' === location.hostname.slice( -14 ) ? '.rootDomain' : location.hostname;
104 + cookieLib.setItem( 'usprivacy', value, 24 * 60 * 60, '/', domain );
105 + };
106 +
107 + var setDefaultNotApplicableCookie = function () {
108 + var value = '1---';
109 + var domain =
110 + '.wordpress.com' === location.hostname.slice( -14 ) ? '.rootDomain' : location.hostname;
111 + cookieLib.setItem( 'usprivacy', value, 24 * 60 * 60, '/', domain );
112 + };
113 +
114 + var setCcpaAppliesCookie = function ( value ) {
115 + var domain =
116 + '.wordpress.com' === location.hostname.slice( -14 ) ? '.rootDomain' : location.hostname;
117 + cookieLib.setItem( 'ccpa_applies', value, 24 * 60 * 60, '/', domain );
118 + };
119 +
120 + var injectLoadingMessage = function () {
121 + var wrapper = document.createElement( 'div' );
122 + document.body.insertBefore( wrapper, document.body.firstElementChild );
123 + wrapper.outerHTML =
124 + '<div id="ccpa-loading" class="cleanslate ccpa__loading-wrapper">' +
125 + '<div class="ccpa__loading-overlay">' +
126 + '<span class="ccpa__loading-message">' +
127 + ccpaSettings.strings.pleaseWait +
128 + '...</span>' +
129 + '</div>' +
130 + '</div>';
131 + };
132 +
133 + var destroyModal = function () {
134 + var node = document.querySelector( '#ccpa-modal' );
135 +
136 + if ( node ) {
137 + node.parentElement.removeChild( node );
138 + }
139 + };
140 +
141 + var injectModal = function () {
142 + destroyModal();
143 +
144 + injectLoadingMessage();
145 +
146 + var request = new XMLHttpRequest();
147 + request.open(
148 + 'GET',
149 + ccpaSettings.ajaxUrl + '?action=privacy_optout_markup&security=' + ccpaSettings.ajaxNonce,
150 + true
151 + );
152 + request.onreadystatechange = function () {
153 + if ( 4 === this.readyState ) {
154 + if ( 200 === this.status ) {
155 + document.getElementById( 'ccpa-loading' ).remove();
156 + var wrapper = document.createElement( 'div' );
157 + document.body.insertBefore( wrapper, document.body.firstElementChild );
158 + wrapper.outerHTML = this.response;
159 + document.getElementById( 'ccpa-opt-out' ).focus();
160 +
161 + var optOut = document.querySelector( '#ccpa-modal .opt-out' );
162 + optOut.addEventListener( 'click', function ( e ) {
163 + var post = new XMLHttpRequest();
164 + post.open( 'POST', ccpaSettings.ajaxUrl, true );
165 + post.setRequestHeader(
166 + 'Content-Type',
167 + 'application/x-www-form-urlencoded; charset=UTF-8'
168 + );
169 + post.onreadystatechange = function () {
170 + if ( 4 === this.readyState ) {
171 + if ( 200 === this.status ) {
172 + var result = JSON.parse( this.response );
173 +
174 + if ( result && result.success ) {
175 + // Note: Cooke is set in HTTP response from POST, so only need to update the toggle switch state.
176 + if ( result.data ) {
177 + e.target.parentNode.classList.add( 'is-checked' );
178 + e.target.parentNode.parentNode.classList.add( 'is-checked' );
179 + } else {
180 + e.target.parentNode.classList.remove( 'is-checked' );
181 + e.target.parentNode.parentNode.classList.remove( 'is-checked' );
182 + }
183 + }
184 + }
185 + }
186 + };
187 + post.send(
188 + 'action=privacy_optout&optout=' +
189 + e.target.checked +
190 + '&security=' +
191 + ccpaSettings.ajaxNonce
192 + );
193 + } );
194 +
195 + // Set initial toggle status based on cookie data.
196 + var usprivacyCookie = cookieLib.getItem( 'usprivacy' );
197 + var optout = usprivacyCookie && 'Y' === usprivacyCookie[ 2 ];
198 + var toggle = document.querySelector( '#ccpa-modal .opt-out' );
199 +
200 + toggle.checked = optout;
201 +
202 + if ( optout ) {
203 + toggle.parentNode.classList.add( 'is-checked' );
204 + toggle.parentNode.parentNode.classList.add( 'is-checked' );
205 + }
206 +
207 + var buttons = document.querySelectorAll( '#ccpa-modal .components-button' );
208 + Array.prototype.forEach.call( buttons, function ( el ) {
209 + el.addEventListener( 'click', function () {
210 + destroyModal();
211 + } );
212 + } );
213 + }
214 + }
215 + };
216 +
217 + request.send();
218 + };
219 +
220 + var dispatchInitializedEvent = function ( ccpaApplies ) {
221 + // Dispatches a custom event with data indicating if the CCPA applies or not once it has been determined.
222 + // Sites can listen for this event and do additional processing, e.g. showing or hiding additional elements.
223 + var event = document.createEvent( 'CustomEvent' );
224 + event.initCustomEvent( 'wordads-ccpa-initialized', true, false, { ccpaApplies: ccpaApplies } );
225 + document.dispatchEvent( event );
226 + };
227 +
228 + var initialize = function ( ccpaApplies, usprivacyCookie ) {
229 + // Get any Do Not Sell links on the page.
230 + var dnsLinks = document.querySelectorAll( '.ccpa-do-not-sell' );
231 +
232 + // No usprivacy cookie, so we need to set it.
233 + if ( null === usprivacyCookie ) {
234 + if ( ccpaApplies ) {
235 + if ( 0 === dnsLinks.length ) {
236 + // Could not find a Do Not Sell link as required, so default to opt-OUT just to be safe.
237 + setDefaultOptOutCookie();
238 + } else {
239 + // Found a Do Not Sell link, so set default opt-in.
240 + setDefaultOptInCookie();
241 + }
242 + } else {
243 + // CCPA does not apply.
244 + setDefaultNotApplicableCookie();
245 + }
246 + }
247 +
248 + // If CCPA does not apply, and we are not overriding it for admins, then we can stop here.
249 + if ( ! ccpaApplies && 'false' === ccpaSettings.forceApplies ) {
250 + dispatchInitializedEvent( false );
251 + return;
252 + }
253 +
254 + // Displays Do Not Sell links and adds handlers to display the modal when clicked.
255 + Array.prototype.forEach.call( dnsLinks, function ( dnsLink ) {
256 + dnsLink.addEventListener( 'click', function ( e ) {
257 + e.preventDefault();
258 +
259 + if ( ! ccpaSettings.stylesLoaded ) {
260 + // Load wordads-ccpa.min.css.
261 + var ccpaCss = document.createElement( 'link' );
262 + ccpaCss.rel = 'stylesheet';
263 + ccpaCss.type = 'text/css';
264 + ccpaCss.href = ccpaSettings.ccpaCssUrl;
265 + document.getElementsByTagName( 'HEAD' )[ 0 ].appendChild( ccpaCss );
266 +
267 + ccpaSettings.stylesLoaded = true;
268 + }
269 +
270 + injectModal();
271 + } );
272 +
273 + // Make the link visible.
274 + dnsLink.style.display = '';
275 + } );
276 +
277 + // CCPA applies (or we're forcing it to display for admins). Let any listeners know.
278 + dispatchInitializedEvent( true );
279 + };
280 +
281 + // Setup CCPA on DOM loaded.
282 + document.addEventListener( 'DOMContentLoaded', function () {
283 + // Look for usprivacy cookies first.
284 + var usprivacyCookie = cookieLib.getItem( 'usprivacy' );
285 +
286 + // Found a usprivacy cookie.
287 + if ( null !== usprivacyCookie ) {
288 + // CCPA does not apply.
289 + if ( '1---' === usprivacyCookie ) {
290 + initialize( false, usprivacyCookie );
291 + } else {
292 + // CCPA applies.
293 + initialize( true, usprivacyCookie );
294 + }
295 +
296 + // No more processing needed.
297 + return;
298 + }
299 +
300 + // We don't have a usprivacy cookie, so check to see if we have a CCPA applies cookie.
301 + var ccpaCookie = cookieLib.getItem( 'ccpa_applies' );
302 +
303 + // No CCPA applies cookie found, so we'll need to geolocate if this visitor is from applicable US state.
304 + // This needs to happen client side because we do not have region geo data in our $SERVER headers,
305 + // only country data -- therefore we can't vary cache on the region.
306 + if ( null === ccpaCookie ) {
307 + var request = new XMLHttpRequest();
308 + request.open( 'GET', 'https://public-api.wordpress.com/geo/', true );
309 +
310 + request.onreadystatechange = function () {
311 + if ( 4 === this.readyState ) {
312 + if ( 200 === this.status ) {
313 + // Got a geo response. Parse out the region data.
314 + var data = JSON.parse( this.response );
315 + var region = data.region ? data.region.toLowerCase() : '';
316 + var ccpaApplies =
317 + [
318 + 'california',
319 + 'colorado',
320 + 'connecticut',
321 + 'delaware',
322 + 'indiana',
323 + 'iowa',
324 + 'montana',
325 + 'new jersey',
326 + 'oregon',
327 + 'tennessee',
328 + 'texas',
329 + 'utah',
330 + 'virginia',
331 + ].indexOf( region ) > -1;
332 +
333 + // Set CCPA applies cookie. This keeps us from having to make a geo request too frequently.
334 + setCcpaAppliesCookie( ccpaApplies );
335 +
336 + // Perform the rest of the initialization.
337 + initialize( ccpaApplies, null );
338 + } else {
339 + // Geolocation request failed, so default to CCPA applies just to be safe.
340 + setCcpaAppliesCookie( true );
341 +
342 + // Perform the rest of the initialization.
343 + initialize( true, null );
344 + }
345 + }
346 + };
347 +
348 + // Send the geo request.
349 + request.send();
350 + } else {
351 + // We found a CCPA applies cookie. Continue with initialization.
352 + initialize( 'true' === ccpaCookie, null );
353 + }
354 + } );
355 +} )();