1/******************************************************************************* 2 * ADOBE CONFIDENTIAL 3 * __________________ 4 * 5 * Copyright 2016 Adobe Systems Incorporated 6 * All Rights Reserved. 7 * 8 * NOTICE: All information contained herein is, and remains 9 * the property of Adobe Systems Incorporated and its suppliers, 10 * if any. The intellectual and technical concepts contained 11 * herein are proprietary to Adobe Systems Incorporated and its 12 * suppliers and are protected by trade secret or copyright law. 13 * Dissemination of this information or reproduction of this material 14 * is strictly forbidden unless prior written permission is obtained 15 * from Adobe Systems Incorporated. 16 ******************************************************************************/ 17 18if (!window.CQ_Analytics) { 19 window.CQ_Analytics = {}; 20} 21 22(function ($, ns) { 23 24 ns.mboxes = ns.mboxes || []; 25 26 var atjsIntegrator = function ($, ns) { 27 28 /* -- INIT code -- */ 29 30 /** 31 * The regular expression to detect the context path used 32 * on the server using the URL of this script. 33 * @private 34 * @readonly 35 * @type RegExp 36 */ 37 var SCRIPT_URL_REGEXP = /^(?:http|https):\/\/[^/]+(\/.*)\/(?:etc\.clientlibs|etc(\/.*)*\/clientlibs|libs(\/.*)*\/clientlibs|apps(\/.*)*\/clientlibs|etc\/designs).*\.js(\?.*)?$/; 38 39 var instance = {}; 40 41 if (this.constructor === atjsIntegrator.prototype) { 42 throw "This is a singleton!"; 43 } 44 45 if (atjsIntegrator.instance) { 46 return atjsIntegrator.instance; 47 } 48 49 atjsIntegrator.instance = instance; 50 51 var _isDebugMode = window.location.href.indexOf("debug-mbox-calls=1") !== -1; 52 53 function _debug(message) { 54 if (_isDebugMode) { 55 console.log(message); 56 } 57 }; 58 59 var _contextPath = _detectContextPath(); 60 61 function _detectContextPath() { 62 try { 63 if (window.CQURLInfo) { 64 return CQURLInfo.contextPath || ""; 65 } else { 66 var scripts = document.getElementsByTagName("script"); 67 for (var i = 0; i < scripts.length; i++) { 68 var result = SCRIPT_URL_REGEXP.exec(scripts[i].src); 69 if (result) { 70 return result[1]; 71 } 72 } 73 } 74 } catch (e) { 75 } 76 return ""; 77 }; 78 79 /* -- BEGIN PRIVATE AREA -- */ 80 81 /** 82 * Adds a parameter to the specified URL. The parameter name and value 83 * will be URL-endcoded.
84 * 85 * @param url {String} 86 * url The URL 87 * @param name {String} 88 * name The name of the parameter 89 * @param value {String/String[]} 90 * value The value of the parameter 91 * @return {String} The URL with the new parameter 92 */ 93 function _addParameter(url, name, value) { 94 if (value && value instanceof Array) { 95 for (var i = 0; i < value.length; i++) { 96 url = _addParameter(url, name, value[i]); 97 } 98 return url; 99 } 100 var separator = url.indexOf("?") === -1 ? "?" : "&"; 101 var hashIdx = url.indexOf("#"); 102 if (hashIdx < 0) { 103 return url + separator + encodeURIComponent(name) + "=" + encodeURIComponent(value); 104 } else { 105 var hash = url.substring(hashIdx); 106 url = url.substring(0, hashIdx); 107 return url + separator + encodeURIComponent(name) + "=" + encodeURIComponent(value) + hash; 108 } 109 } 110 111 /** 112 * Returns the value of the parameter with the specified name 113 * in the URL. Only the first value will be considered. 114 * Values will be URL-decoded. 115 * @static 116 * @param {String} url The URL 117 * @param {String} name The name of the parameter 118 * @return {String} The value 119 */ 120 function _getParameter(url, name) { 121 var params = _getParameters(url, name); 122 return params != null ? params[0] : null; 123 } 124 125 /** 126 * Returns the values of the parameters with the specified name 127 * in the URL. Values will be URL-decoded. 128 * @static 129 * @param {String} url The URL 130 * @param {String} name The name of the parameter 131 * @return {String[]} The values 132 */ 133 function _getParameters(url, name) { 134 var values = []; 135 var queryIdx = url.indexOf("?"); 136 var hashIdx = url.indexOf("#"); 137 if (!name || queryIdx === -1) { 138 return null; 139 } 140 name = encodeURIComponent(name); 141 if (hashIdx !== -1) { 142 url = url.substring(0, hashIdx); 143 } 144 var query = url.substring(queryIdx + 1); 145 if (query.indexOf(name) === -1) { 146 return null; 147 } 148 var queryPts = query.split("&"); 149 for (var i = 0; i < queryPts.length; i++) { 150 var paramPts = queryPts[i].split("="); 151 if (paramPts[0] === name) { 152 values.push(paramPts.length > 1 ? decodeURIComponent(paramPts[1]) : ""); 153 } 154 } 155 return values.length > 0 ? values : null; 156 } 157 158 /** 159 * Makes sure the specified relative URL starts with the context path 160 * used on the server. If an absolute URL is passed, it will be returned 161 * as-is. 162 * @static 163 * @param {String} url The URL 164 * @return {String} The externalized URL 165 */ 166 function _externalize(url) { 167 try { 168 if (url.indexOf("/") === 0 && _contextPath && url.indexOf(_contextPath + "/") !== 0) { 169 url = _contextPath + url; 170 } 171 } catch (e) { 172 } 173 return url; 174 } 175 176 /** 177 * Requests the specified URL from the server using GET. The request 178 * will be synchronous. 179 * 180 * @param url {String} 181 * url The URL to request 182 * @return {Mixed} The response object or, if the request is 183 * asynchronous, the transaction ID 184 */ 185 function _get(url) { 186 try { 187 var request = $.ajax({ 188 type: "GET", 189 url: _externalize(url), 190 async: false, 191 externalize: false, 192 encodePath: false, 193 hook: false 194 }); 195 196 if (!request) return null; 197 198 return request; 199 } catch (e) { 200 return null; 201 } 202 } 203 204 /** 205 * Appends the default ambit to the specified {@code path}. 206 * The function assumes the brand on the 3rd segment of the path 207 * (i.e /content/campaigns/brand). 208 * 209 * @param {String} path Resource path 210 */ 211 function _appendAmbitSegment(path) { 212 return path.replace(/(\/content\/campaigns\/.*?)\/(.*)/, "$1/master/$2"); 213 } 214 215 function _isAuthorEditor() { 216 var topPath = CQ.shared.HTTP.getPath(top.document.location.href); 217 //support for IE11 218 var userAgent = navigator ? navigator.userAgent.toLowerCase() : "other"; 219 if (userAgent.indexOf('trident/') > 0) { 220 var rv = userAgent.indexOf('rv:');
221 if(parseInt(userAgent.substring(rv + 3, userAgent.indexOf('.', rv)), 10) == '11') 222 return topPath.indexOf("/editor.html/") > -1; 223 } 224 return topPath.includes("/editor.html/"); 225 } 226 227 function _addModeParameter(path) { 228 // if possible, force WCM mode as disabled to prevent edit decorations from being applied 229 var wcmmode = CQ.shared.HTTP.getParameter(document.location.href, 'wcmmode'); 230 if (_isAuthorEditor()) { 231 wcmmode = "disabled"; 232 } 233 if (wcmmode && wcmmode.length > 0) { 234 path = CQ.shared.HTTP.addParameter(path, 'wcmmode', wcmmode); 235 } 236 return path; 237 } 238 239 function _pullContent(path, mboxName) { 240 241 path = _addModeParameter(path); 242 243 var proxyUrl = window.targetGlobalSettings.currentPagePath + ".targetoffer.html" + path; 244 245 // pull in the content 246 var response = _get(proxyUrl); 247 var isOk = (response && response.status && response.status === 200); 248 var hasBody = (response && response.responseText && response.responseText.length > 0); 249 250 var _mboxId = 0; 251 if (isOk && hasBody) { 252 var target = document.getElementById(mboxName); 253 var outputWritten = false; 254 // if a target is found, process 255 if (target) { 256 // empty the target div first. 257 while (target.firstChild) { 258 target.removeChild(target.firstChild); 259 } 260 261 // look for the wrapper div which tracks clicks 262 var childDivs = target.getElementsByTagName('div'); 263 if (childDivs.length === 1) { 264 target = childDivs[0]; 265 } 266 267 var scriptwrapper = document.createElement('div'); 268 scriptwrapper.innerHTML = response.responseText; 269 target.appendChild(scriptwrapper); 270 var scripts = target.getElementsByTagName('script'); 271 for (var i = 0; i < scripts.length; i++) { 272 eval(scripts[i].text); 273 } 274 275 outputWritten = true; 276 277 var parentElement = target.parentElement; 278 if (parentElement) { 279 var event = document.createEvent("CustomEvent"); 280 event.initEvent("target-dom-loaded", true, false); 281 event.mboxName = mboxName; 282 parentElement.dispatchEvent(event); 283 } 284 } 285 286 // CQ-15679 - fallback in case we don't find a target div 287 if (!outputWritten) { 288 document.write(response.responseText); 289 } 290 } 291 } 292 293 function _callMboxUpdate() { 294 for (var i = 0; i < ns.mboxes.length; i++) { 295 var updateArgs = {}; 296 297 if (Array.isArray(ns.mboxes[i].staticParameters)) { 298 ns.mboxes[i].staticParameters.map(function(val,idx){ 299 var parts = val.split("="); 300 if (parts.length === 2) { 301 updateArgs[parts[0]] = parts[1]; 302 } 303 }); 304 } 305 306 for (var j = 0; j < CQ_Analytics.mboxes[i].mappings.length; j++) { 307 var profileprefix = ""; 308 var param = CQ_Analytics.mboxes[i].mappings[j].param; 309 var keypath = '/' + ns.mboxes[i].mappings[j].ccKey.replace('.', '/'); 310 if (ns.mboxes[i].isProfile.indexOf(param) > -1) { 311 /* we should always apply the prefix, to prevent parameter name collisions */ 312 /*if (!param.match(/^profile\..*$/)) {*/ 313 profileprefix = "profile."; 314 /*}*/ 315 } 316 var paramValue = ns.DataProvider.replaceVariables(_getContextVariable(keypath)); 317 updateArgs[profileprefix + param] = typeof paramValue !== "undefined" ? paramValue : ""; 318 } 319 320 if (ns.mboxes[i].includeResolvedSegments && ns.SegmentMgr) { 321 var resolvedSegments = ns.SegmentMgr.getResolved(); 322 if (resolvedSegments.length > 0) { 323 updateArgs["profile._cq_.resolvedSegments"] = "|" + ns.SegmentMgr.getResolved().join("|") + ("|"); 324 } 325 } 326 327 /* space out the first call, which is probably the global mbox, by 100 ms, 328 * to give T&T time to process the profile and use it in the next update calls 329 */ 330 (function (mboxName, args) { 331 setTimeout(function () { 332 CQ_Analytics.TestTarget.updateMboxContent(mboxName, args) 333 }, (i > 0 ? 100 : 0)); 334 })(ns.mboxes[i].name, updateArgs); 335 } 336 } 337 338 function _addMappings(mappingsJsonArray) { 339 for (var idx = 0; idx < mappingsJsonArray.length; idx++) { 340 var mapKey = mappingsJsonArray[idx]["ccKey"]; 341 if (!instance.mappings[mapKey]) { 342 instance.mappings[mapKey] = {}; 343 } 344 } 345 } 346 347 /** 348 * Leverages both ClientContext and ContextHub to fetch the value of a variable 349 * @param keypath 350 */ 351 function _getContextVariable(keypath) { 352 var value = undefined, 353 contextValue = undefined; 354 if (window.ContextHub) { 355 contextValue = ContextHub.get(keypath); 356 } else { 357 contextValue = ns.ClientContext.get(keypath); 358 } 359 if (contextValue) { 360 if (Array.isArray(contextValue)) { 361 value = contextValue.join(","); 362 } else if (typeof contextValue !== "object") { 363 value = contextValue; 364 } 365 } 366 return value; 367 } 368 369 /* Returns an Array of property names used in 370 * CQ_Analytics.mboxes mappings. Returns empty Array if none 371 * found. 372 */ 373 function _getMappedProperties() { 374 var properties = []; 375 if (window.CQ_Analytics 376 && window.CQ_Analytics.TestTarget 377 && window.CQ_Analytics.TestTarget.mappings) { 378 for (var mappedProp in window.CQ_Analytics.TestTarget.mappings) { 379 properties.push(mappedProp); 380 } 381 } 382 return properties; 383 } 384 385 /** 386 * Returns true if the mbox calls are to me made in simulation mode ( WCM preview and edit modes ) 387 */ 388 function _isInSimulationMode() { 389 var layerName = _getAuthorLayerName(); 390 return _isEdit(layerName) || _isPreview(layerName) || _isSimulation(); 391 } 392 393 function _isSimulation() { 394 // use CQ.WCM when available 395 if (typeof CQ !== "undefined") { 396 if (CQ.WCM && CQ.utils && CQ.utils.WCM) { 397 return CQ.WCM.isPreviewMode() || CQ.utils.WCM.isEditMode(); 398 } 399 } 400 // fallback to reading the cookies directly 401 var $COOKIE = (document.cookie || '').split(/;\s*/).reduce(function(re, c) { 402 var tmp = c.match(/([^=]+)=(.*)/); 403 if (tmp) re[tmp[1]] = unescape(tmp[2]); 404 return re; 405 }, {}); 406 return (typeof $COOKIE["wcmmode"] === "undefined" 407 || $COOKIE["wcmmode"] === "preview" 408 || $COOKIE["wcmmode"] === "edit"); 409 } 410 411 function _getAuthorLayerName() { 412 var layer = document.querySelector('[class*="aem-AuthorLayer"]'); 413 return layer ? layer.className.match("aem-AuthorLayer-[^ ]+")[0] : null; 414 } 415 416 function _isPreview(layerName) { 417 return layerName === "aem-AuthorLayer-Preview"; 418 } 419 420 function _isEdit(layerName) { 421 return layerName === "aem-AuthorLayer-Edit"; 422 } 423 424 /** 425 * Forces an mbox update if either of CQ.WCM.isPreviewMode() or CQ.utils.WCM.isEditMode() is true. 426 */ 427 function _forceMboxUpdate() { 428 if (_isInSimulationMode()) { 429 _callMboxUpdate(); 430 } 431 } 432 433 /* --- END PRIVATE AREA -- */ 434 435 /* --- BEGIN PUBLIC AREA -- */ 436 437 instance.registeredCHListeners = {}; 438 439 instance.usedStoresLoaded = false;
440 441 instance.defaults = {}; 442 443 instance.mappings = []; 444 445 instance.maxProfileParams = 200; 446 447 /** 448 * Fetches the resource from provided path and writes the 449 * response to the document or the mbox Element if 450 * 451 * <ul> 452 * <li>response status code is 200</li> 453 * <li>response has a body with length > 0</li> 454 * </ul> 455 * 456 * Uses a synchronous call for requesting the resource. If a WCM mode is defined this 457 * call forces the resource to be rendered with WCM mode disabled. 458 * 459 * @static 460 * @param {String} path Path to document/node to request. 461 * @param {String} mboxName The name of the mbox that issued the request 462 * @param {String} version API version indicator 463 */ 464 instance.pull = function (path, mboxName, version) { 465 466 // if version parameter omitted, assume path without ambit segment 467 if (typeof version === 'undefined') { 468 path = _appendAmbitSegment(path); 469 } 470 471 _pullContent(path, mboxName); 472 }; 473 474 instance.displayOffer = function(mboxName, params) { 475 adobe.target.getOffer({ 476 "mbox": mboxName, 477 "params": params, 478 "success": function (response) { 479 adobe.target.applyOffer({ 480 "mbox": mboxName, 481 "selector": "#" + mboxName, 482 "offer": response 483 }) 484 }, 485 "error": function (status, response) { 486 console.error(status, response); 487 $('#' + mboxName).css('visibility', 'visible'); 488 } 489 }); 490 }; 491 492 /** 493 * Updates the content of an mbox with the offer from Adobe Target 494 * @param mboxName the name of the mbox 495 * @param params the call parameters passed as a JS object - {paramName: paramValue} 496 */ 497 instance.updateMboxContent = function(mboxName, params) { 498 _debug("Updating content for mbox " + mboxName); 499 500 if (_isAuthorEditor()) { 501 // wait for the Author layers to render so we can accurately determine the mode we are in 502 var layerName = _getAuthorLayerName(); 503 if (layerName) { 504 _isEdit(layerName) ? instance.signalDefaultOffer(mboxName) : instance.displayOffer(mboxName, params); 505 } else { 506 setTimeout(function(){instance.updateMboxContent(mboxName, params);}, 100); 507 } 508 } else { 509 instance.displayOffer(mboxName, params); 510 } 511 } 512 513 instance.registerMboxUpdateCalls = function () { 514 if (typeof window.CQ_Analytics !== "undefined" 515 && window.CQ_Analytics.TestTarget.mappings) { 516 _debug("[Target][init] Registering Mbox update calls"); 517 var mappedProperties = _getMappedProperties(); 518 519 if (mappedProperties.length > 0) { 520 instance.registerContextHubListeners(); 521 } else { 522 _callMboxUpdate(); 523 } 524 } 525 }; 526 527 instance.registerContextHubListeners = function () { 528 if (!window.ContextHub) { 529 return; 530 } 531 532 var mappedProperties = _getMappedProperties(); 533 var listenKeys = []; 534 535 for (var mappingIdx = 0; mappingIdx < mappedProperties.length; mappingIdx++) { 536 var mappedProperty = mappedProperties[mappingIdx]; 537 var storeName = mappedProperty.split(".")[0]; 538 var listenKey = "/" + mappedProperty.replace(".", "/"); 539 540 // check if store is in CH 541 var contextHubStore = ContextHub.get(storeName); 542 543 if (contextHubStore 544 && !instance.registeredCHListeners[storeName]) { 545 instance.registeredCHListeners[storeName] = true; 546 listenKeys.push(listenKey); 547 548 _debug("[Target][CH] - Listening for updates on " + listenKey + " CH"); 549 } 550 } 551 552 if (listenKeys.length > 0) { 553 // bind to context hub to listen for property updates 554 ContextHub.bind(listenKeys, 555 function successHandler(data) { 556 _debug("[Target][CH][registerContextHubListeners] All properties available, triggering update!"); 557 _callMboxUpdate(); 558 instance.usedStoresLoaded = true; 559 }, 560 function defaultHandler(data) { 561 _debug("[Target][CH][registerContextHubListeners] Not all properties available, triggering update!"); 562 if (!instance.usedStoresLoaded) { 563 _callMboxUpdate(); 564 } 565 }, 500); 566 } 567 }; 568 569 /**
570 * Adds a new mboxDefinition to the CQ_Analytics.mboxes array 571 * 572 * <p>Removes any mbox definition with the same mbox id prior to adding the passed 573 * mboxDefinition.</p> 574 * 575 * @return {Boolean} true if an mbox was replaced, false otherwise 576 */ 577 instance.addMbox = function (mboxDefinition) { 578 var replaced = false, 579 alreadyDefined = false; 580 581 if (!CQ_Analytics.mboxes) { 582 CQ_Analytics.mboxes = []; 583 } 584 for (var i = 0; i < CQ_Analytics.mboxes.length; i++) { 585 586 var mbox = CQ_Analytics.mboxes[i]; 587 // cleanup existing mbox 588 if (mbox.id === mboxDefinition.id) { 589 CQ_Analytics.mboxes.splice(i, 1); 590 replaced = true; 591 alreadyDefined = mbox.defined; 592 break; 593 } 594 } 595 mboxDefinition.defined = alreadyDefined; 596 ns.mboxes.push(mboxDefinition); 597 598 _addMappings(mboxDefinition.mappings); 599 600 return replaced; 601 }; 602 603 /** 604 * Triggers an update of all the registered mboxes 605 * 606 * <p>Delays the update requests based on the <tt>delay</tt> parameter so that multiple update requests 607 * are clumped together.</p> 608 * 609 * @param delay {Integer} the delay in milliseconds to apply to the reload, defaults to 500 610 */ 611 instance.triggerUpdate = function (delay) { 612 613 if (typeof delay === "undefined") 614 delay = 500; 615 616 if (!instance.reloadRequested) { 617 instance.reloadRequested = true; 618 setTimeout(function () { 619 _forceMboxUpdate(); 620 instance.reloadRequested = false; 621 }, delay); 622 } 623 }; 624 625 /** 626 * Signals that the default offer was received from Target. 627 * This function is called by the Target offer itself. 628 * @param mboxName 629 */ 630 instance.signalDefaultOffer = function(mboxName) { 631 if (typeof instance.defaults[mboxName] === 'undefined') { 632 if (console) { console.log("The default offer path was not found in the internal map for mbox " + mboxName)} 633 return; 634 } 635 636 var defaultContentPath = instance.defaults[mboxName]; 637 638 _pullContent(defaultContentPath, mboxName); 639 $('#'+mboxName).css('visibility', 'visible'); 640 }; 641 642 643 return instance; 644 }; 645 646 ns.TestTarget = atjsIntegrator($, ns); 647 648 // register for mode change event so we can switch to default content in EDIT mode 649 top.$(top.document).on("editor-frame-mode-changed", function() { 650 // This may be called while the page is refreshed so we need to check if we have a valid CQ_Analytics.TestTarget 651 if (CQ_Analytics && CQ_Analytics.TestTarget) 652 CQ_Analytics.TestTarget.triggerUpdate(); 653}); 654 655})($, window.CQ_Analytics);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.