An error occurred while processing the template.
The following has evaluated to null or missing:
==> cpProductId  [in template "44616#44647#3234203" at line 93, column 92]

----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----

----
FTL stack trace ("~" means nesting-related):
	- Failed at: ${cpProductId}  [in template "44616#44647#3234203" at line 93, column 90]
----
1<#assign 
2commerceContext = renderRequest.getAttribute("COMMERCE_CONTEXT") 
3channelId = commerceContext.getCommerceChannelId() 
4/> 
5<#if cpCatalogEntry?has_content> 
6	<#assign 
7	  cpDefinitionId = cpCatalogEntry.getCPDefinitionId() 
8		cpProductId = cpCatalogEntry.getCProductId() 
9	  productName = cpCatalogEntry.getName() 
10	  productShortDescription = cpCatalogEntry.getShortDescription() 
11	  productDescription = cpCatalogEntry.getDescription() 
12	  productImages = cpContentHelper.getImages(cpDefinitionId, themeDisplay) 
13		productImages = productImages?reverse 
14		cpAttachmentFileEntries = cpContentHelper.getCPMedias(cpDefinitionId, themeDisplay) 
15	/> 
16 
17		<nav class="breadcrumb-navigation" aria-label="breadcrumb"> 
18			<ol class="breadcrumb-list"> 
19				<li class="breadcrumb-item"><a id="level0CategoryCrumb" href='${languageUtil.get(locale, "what-we-offer-products-url")}' class="breadcrumb-link"></a></li> 
20 
21				<li  class="breadcrumb-item"><a id="level1CategoryCrumb" href="#" class="breadcrumb-link"></a></li> 
22				<li class="breadcrumb-item" aria-current="page">${productName}</li> 
23			</ol> 
24		</nav>	 
25	 
26		<div class="category-detail"> 
27			<h1 class="category-title">${productName}</h1> 
28			<p class="category-description">${productShortDescription}</p>         
29		</div>	 
30	 
31		<div class="product-catalog-detail-section"> 
32				<div class="accordion tabs"> 
33					<h2 class="toggle">Product Detail</h2> 
34					<div class="content" tabindex="0"> 
35						<div class="product-detail-content row align-items-lg-start align-items-sm-start align-items-start align-items-md-start flex-lg-row flex-sm-row flex-row flex-md-row"> 
36							<div class="product-detail-slider-wrap col col-lg-5 col-sm-12 col-12 col-md-5"> 
37								<span class="product-detail-image-paging-info visually-hidden" role="status"></span> 
38								<div class="product-detail-slider"> 
39								 <#if productImages?has_content> 
40									<#list productImages as currentImage> 
41										<li class="product-detail-slider-slide"> 
42											 
43											<img class="product-detail-slider-slide-img" src="${currentImage.getURL()}" width="428" height="428" alt="${currentImage.getTitle()}" /> 
44										</li>	 
45									</#list> 
46								 </#if>	   
47								</div> 
48								<div class="product-detail-slider-nav"> 
49								 <#if productImages?has_content> 
50									<#list productImages as currentImage> 
51										<li role="button" class="product-detail-slider-nav-btn"> 
52											<img class="product-detail-slider-slide-img" src="${currentImage.getURL()}" width="65" height="65" alt="${currentImage.getTitle()}" /> 
53										</li>	 
54									</#list> 
55								 </#if>	   
56								 </div> 
57							</div> 
58							<div class="product-detail-content-desc col col-lg-7 col-sm-12 col-12 col-md-7"> 
59								<p>${productDescription}</p> 
60								<a class="btn btn-primary" href="/contact">Contact Sales</a> 
61							</div>	    
62						</div> 
63					</div> 
64 
65						<#assign contentTypeMap = {}/> 
66						<#assign youtubeVideoMap = []/> 
67	 
68					<#if cpAttachmentFileEntries?has_content> 
69						<h2 class="toggle">Resources</h2> 
70						<div  class="content" tabindex="0"> 
71							<div id="resourcesDiv" > 
72							</div> 
73						</div> 
74					</#if>	 
75						<h2 class="toggle">Related Products</h2> 
76						<div class="content" tabindex="0"> 
77							<div id="relatedProductDiv"> 
78	 
79							</div> 
80					 </div> 
81 
82							<h2 id="videoTitle" class="toggle">Videos</h2> 
83							<div class="content" tabindex="0"> 
84								<div id="videoDiv" > 
85									<ul id="videoListSection" class="product-catalog-list-detail"></ul>		 
86								</div> 
87							</div> 		 
88				</div>  
89		</div>	 
90</#if> 
91	<script> 
92 
93	fetch('/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${cpProductId}/categories') 
94  .then(response => response.json()) 
95  .then(data => { 
96		const productCategoryItems = data.items; 
97    if (productCategoryItems && productCategoryItems.length > 0) { 
98			productCategoryItems.forEach(function (productCategoryItem) { 
99				const categoryId = productCategoryItem.id 
100				fetch('/o/c/levelonecategories/?fields=categoryLiferayId,categoryName,categoryURL&filter=categoryLiferayId eq '+categoryId + '&p_auth='+ Liferay.authToken) 
101				.then(response => response.json()) 
102				.then(data => { 
103					const level1Categories = data.items; 
104					if (level1Categories && level1Categories.length > 0) { 
105						level1Categories.forEach(function (level1Category) { 
106							if(level1Category){ 
107								const productCategoryTitle = level1Category.categoryName; 
108								const productCategoryURL = 	level1Category.categoryURL; 
109								const productCategoryFinalURL = 	"/categories/" + productCategoryURL; 
110								document.getElementById('level1CategoryCrumb').setAttribute('href', productCategoryFinalURL); 
111								document.getElementById('level1CategoryCrumb').innerHTML = productCategoryTitle; 
112 
113								const level0Hash = {  
114									"Electricity Meters + Modules":"Measurement + Sensing", 
115									"Gas Meters + Modules":"Measurement + Sensing", 
116									"Sensing + Control":"Measurement + Sensing", 
117									"Thermal Energy Meters + Modules":"Measurement + Sensing", 
118									"Water Meters + Modules":"Measurement + Sensing", 
119									"Grid Management":"Networks + Operations", 
120									"Mobile Meter Reading":"Networks + Operations", 
121									"Network Infrastructure + Management ":"Networks + Operations", 
122									"Operations Management":"Networks + Operations", 
123									"Smart Cities":"Networks + Operations", 
124									"Distributed Energy Management":"Software + Services", 
125									"Energy Forecasting":"Software + Services", 
126									"Meter Data Management + Analytics":"Software + Services", 
127									"Prepayment":"Software + Services", 
128									"Services":"Software + Services" 
129								}; 
130								 
131								const level0CategoryName = level0Hash[productCategoryTitle]; 
132								document.getElementById('level0CategoryCrumb').innerHTML = level0CategoryName; 
133
134						}); 
135						 
136					} else { 
137						console.log('No level1Categories found in the response.'); 
138
139				}) 
140				.catch(error => { 
141					console.error('Error:', error); 
142				}); 
143				 
144			}); 
145 
146    } else { 
147      console.log('No productCategoryItems found in the response.'); 
148
149  }) 
150  .catch(error => { 
151    console.error('Error:', error); 
152  }); 
153	fetch('/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${cpProductId}/attachments?accountId=0&pageSize=50') 
154  .then(response => response.json()) 
155  .then(data => { 
156		const attachmentsMap = new Map(); 
157		const youTubeMap = new Map(); 
158    const productAttachmentItems = data.items.sort((a,b) => (a.title > b.title) ? 1 : ((b.title > a.title) ? -1 : 0)); 
159    if (productAttachmentItems && productAttachmentItems.length > 0) { 
160			productAttachmentItems.forEach(function (productAttachmentItem) { 
161      	const productAttachmentURL = productAttachmentItem.src.split("?")[0]; 
162				const productAttachmentURLStripped = productAttachmentURL.replace("https://itronportal.lxc.liferay.com",""); 
163      	const productAttachmentTitle = productAttachmentItem.title; 
164				const productAttachmentCustomFields = productAttachmentItem.customFields 
165				var productAttachmentContentType = "Article" 
166				var displayAttachment = true; 
167				productAttachmentCustomFields.forEach(function (productAttachmentCustomField) { 
168					const productAttachmentCustomFieldName = productAttachmentCustomField.name 
169					if(productAttachmentCustomFieldName == "ContentType"){ 
170						productAttachmentContentType = productAttachmentCustomField.customValue.data; 
171
172					if(productAttachmentCustomFieldName == "LanguageKey"){ 
173						const productAttachmentLanguageKey = productAttachmentCustomField.customValue.data; 
174						if((productAttachmentLanguageKey != null || productAttachmentLanguageKey != "") && productAttachmentLanguageKey != 'en_US'){ 
175								displayAttachment = false; 
176								if(productAttachmentLanguageKey === '${locale}'){ 
177									displayAttachment = true; 
178
179
180
181				}); 
182 
183						if(productAttachmentContentType == "YouTube Video"){ 
184							const productAttachmentFileEntryId = productAttachmentItem.fileEntryId; 
185							fetch('/o/headless-delivery/v1.0/documents/' + productAttachmentFileEntryId) 
186							.then(response => response.json()) 
187							.then(data => { 
188								const contentCustomFields = data.customFields; 
189								if (contentCustomFields) { 
190									let youTubeURL = ""; 
191									let youTubeCode = ""; 
192									let videoTitle = ""; 
193									contentCustomFields.forEach(function (contentCustomField) { 
194										const contentName = contentCustomField.name 
195										 
196										if(contentName == "VideoUrl"){ 
197											youTubeURL = contentCustomField.customValue.data; 
198											const splitURL = youTubeURL.split("/"); 
199											const splitSize = splitURL.length; 
200											youTubeCode = splitURL[splitSize-1]; 
201
202										if(contentName == "VideoTitle"){ 
203											videoTitle = contentCustomField.customValue.data; 
204
205									}); 
206									let videoListItem = '<li class="product-catalog-list-item-detail"><button class="btn btn-link youtube-modal-link global-youtube-modal-trigger" data-bs-toggle="modal" data-bs-target="#global-youtube-modal" data-modal-url="'+youTubeCode+'" data-modal-title="Itron Optimizer Portfolio Overview"><div class="card-inner"><div class="card-image-div"><img width="279" height="157" loading="lazy" alt="" src="//img.youtube.com/vi/'+youTubeCode+'/maxresdefault.jpg" /><span class="modal-video-btn"><span class="visually-hidden">Play Video</span></span></div><div class="card-body"><h2 class="card-title">'+videoTitle+'</h2></div></div></button></li>'; 
207								  let existingVideoList = document.getElementById('videoListSection').innerHTML; 
208									 
209									document.getElementById('videoListSection').innerHTML = existingVideoList + videoListItem; 
210									showHideVideoSection(); 
211								} else { 
212									console.log('No contentCustomFields found in the response.'); 
213
214							}) 
215							.catch(error => { 
216								console.error('Error:', error); 
217							}); 
218						} else { 
219							if(displayAttachment){ 
220								const listItem = "<li><a target='_blank' href="+ productAttachmentURLStripped+" class='list-link'>"+productAttachmentTitle+"</a></li>"; 
221								let mapValue = attachmentsMap.get(productAttachmentContentType); 
222								let newMapValue = ""; 
223								if(mapValue){ 
224									let newMapValue = mapValue + listItem; 
225									attachmentsMap.set(productAttachmentContentType, newMapValue); 
226								} else { 
227									attachmentsMap.set(productAttachmentContentType, listItem); 
228
229
230
231			}); 
232 
233			let totalList = ''; 
234			let sortedMap = new Map([...attachmentsMap.entries()].sort()); 
235			for (const [key, value] of sortedMap.entries()) { 
236				let ulist = '<h2 class="list-title">' + key + '</h2><ul class="list">' + value + '</ul>' 
237				totalList = totalList + ulist; 
238
239			 
240			document.getElementById('resourcesDiv').innerHTML = totalList; 
241    } else { 
242      console.log('No attachments for selected product.'); 
243
244  }) 
245  .catch(error => { 
246    console.error('Error:', error); 
247  }); 
248		 
249		 
250	fetch('/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${cpProductId}/related-products') 
251  .then(response => response.json()) 
252  .then(data => { 
253		let relatedList = '<ul class="product-catalog-list-detail">'; 
254    const relatedProductItems = data.items; 
255		const totalCount = data.totalCount; 
256		let indexVal = 0; 
257    if (relatedProductItems && relatedProductItems.length > 0) { 
258			relatedProductItems.forEach(function (relatedProductItem) { 
259				const relatedProductId = relatedProductItem.productId; 
260      	fetch('/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/' + relatedProductId) 
261				.then(response => response.json()) 
262				.then(data => { 
263					if (data) { 
264						const relatedProductName = data.name; 
265						const nullData = (relatedProductName == null) 
266						indexVal = indexVal + 1; 
267						if(!nullData){ 
268							const relatedProductImageURL = data.urlImage; 
269							const relatedProductFriendlyURL = data.urls["en_US"]; 
270 
271							if(data.urls['${locale}']){ 
272								const relatedProductFriendlyURL = data.urls['${locale}']; 
273
274							let relatedListItem = '<li class="product-catalog-list-item-detail"><a class="product-catalog-list-item-link" href="/products/' + relatedProductFriendlyURL + '"><div class="card-inner"><div class="card-image-div"><img class="product-catalog-list-item-img" width="227" height="227" loading="lazy" src="'+relatedProductImageURL+'"alt="'+relatedProductName+'" /></div><div class="card-body"><h2 class="card-title card-title__xl">' +relatedProductName + '</h2></div></div></a></li>' 
275							relatedList = relatedList + relatedListItem; 
276
277						if(totalCount == indexVal){ 
278						  relatedList = relatedList + '</ul>'; 
279						  document.getElementById('relatedProductDiv').innerHTML = relatedList; 
280							showHideRelatedProductsSection(); 
281						}       
282					} else { 
283						console.log('No related products found in the response. three'); 
284
285				}) 
286				.catch(error => { 
287					console.error('Error:', error); 
288				}); 
289			}); 
290			 
291       
292    } else { 
293      console.log('No relatedProductItems found in the response Two.'); 
294
295  }) 
296  .catch(error => { 
297    console.error('Error:', error); 
298  }); 
299 
300	</script>