Search Education
553 Products Available
An error occurred while processing the template.
The following has evaluated to null or missing:
==> productDetail.urlImage [in template "49724393377863#35335#2373423" at line 52, column 27]
----
Tip: It's the step after the last dot that caused this error, not those before it.
----
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: defaultImageURL = productDetail.urlIm... [in template "49724393377863#35335#2373423" at line 52, column 9]
----
1<#assign
2 defaultImgMap = {
3 "Default": {
4 "image": "/documents/d/guest/default_logo.png",
5 "color": "gray"
6 },
7 "Livestream": {
8 "image": "/documents/d/guest/icon-desktop-svg",
9 "color": "purple"
10 },
11 "Recorded":{
12 "image": "/documents/d/guest/icon-play",
13 "color": "red"
14 },
15 "In Person":{
16 "image": "/documents/d/guest/icon-persons",
17 "color": "green"
18 },
19 "Download": {
20 "image": "/documents/d/guest/icon-download-black",
21 "color": "border-color"
22 },
23 "Self-paced": {
24 "image": "/documents/d/guest/icon-mouse-white",
25 "color": "black"
26 },
27 "Bank subscription": {
28 "image": "/documents/d/guest/icon-bank",
29 "color": "blue"
30 }
31 }
32/>
33
34<#assign commerceContext = renderRequest.getAttribute("COMMERCE_CONTEXT") />
35<#assign account = commerceContext.getAccountEntry() />
36<#assign accountId = account.getAccountEntryId() />
37<#assign chanelId = commerceContext.getCommerceChannelId() />
38
39<div class="product-card-tiles">
40 <#if entries?has_content>
41 <#list entries as curCPCatalogEntry>
42 <#assign
43 cpDefinitionId = curCPCatalogEntry.getCPDefinitionId()
44 productId = curCPCatalogEntry.getCProductId()
45 productName = curCPCatalogEntry.getName()
46 productShortDescription = curCPCatalogEntry.getShortDescription()
47 productDescription = curCPCatalogEntry.getDescription()
48 friendlyURL = cpContentHelper.getFriendlyURL(curCPCatalogEntry, themeDisplay)
49 defaultImageURL = cpContentHelper.getDefaultImageFileURL(accountId, cpDefinitionId)
50 defaultImageFileVersion = cpContentHelper.getCPDefinitionImageFileVersion(cpDefinitionId, request)
51 productDetail = restClient.get("/headless-commerce-delivery-catalog/v1.0/channels/${chanelId}/products/${productId}?accountId=${accountId}&nestedFields=categories,productSpecifications")
52 defaultImageURL = productDetail.urlImage?replace('https://localhost', '')
53 fileEntryIdAttr = 'data-fileentryid="' + defaultImageFileVersion.fileEntryId + '"'
54 specifications = productDetail.productSpecifications
55 customFields = productDetail.customFields
56 productCategories = restClient.get("/headless-commerce-delivery-catalog/v1.0/channels/${chanelId}/products/${productId}/categories?pageSize=75")
57 categories = productCategories.items
58 productsku= restClient.get("/headless-commerce-delivery-catalog/v1.0/channels/${chanelId}/products/${productId}/skus?pageSize=40")
59 erc = productDetail.externalReferenceCode
60 infoObj = {}
61 tags = productDetail.tags
62 featuredSpecificationKeys = ["fit", "weight", "material"]
63 eventTypeName = ""
64 startDate = ""
65 startTime = ""
66 endDate = ""
67 endTime = ""
68 deliverymethodArray = [{"id": "", "value": ""}]
69 locationAddress = ""
70 />
71 <#if categories?has_content>
72 <#list categories as category>
73 <#if category.vocabulary?replace(' ', '') == 'deliverymethod'>
74 <#assign deliverymethodArray += [{"id": category.id, "value": category.name}] />
75 <#else>
76 <#assign infoObj = infoObj + {category.vocabulary?replace(' ', ''): {"id": category.id, "value": category.name}} />
77 </#if>
78 </#list>
79 <#assign infoObj = infoObj + {"deliverymethod": deliverymethodArray} />
80 </#if>
81
82 <#list customFields as customField>
83 <#assign infoObj = infoObj + {(customField.name): customField.customValue.data} />
84 </#list>
85
86 <!-- <div class="d-none">
87 <ul>
88 <li>
89 <#assign sku = "" />
90 <#if cpContentHelper.getDefaultCPSku(curCPCatalogEntry)?has_content>
91 <#assign sku = cpContentHelper.getDefaultCPSku(curCPCatalogEntry).getSku() />
92 </#if>
93 SKU: ${sku}
94 </li>
95 <li>
96 <@liferay_commerce_ui["availability-label"] CPCatalogEntry=curCPCatalogEntry />
97 </li>
98
99 <#if categories?has_content>
100 <#list categories as category>
101 <#if category.vocabulary == 'event type'>
102 <#assign eventTypeName = category.name />
103 <#assign eventTypeId = category.id />
104 </#if>
105 <li>${category.vocabulary}: ${category.name}</li>
106 </#list>
107 </#if>
108 </ul>
109 </div> -->
110
111 <!-- Category Flags -->
112 <#assign
113 isInstitute = "false"
114 isSeminar = "false"
115 isLivestream = "false"
116 isOnlineCourse = "false"
117 isInPerson = "false"
118 isWebinar = "false"
119 isEvent = "false"
120 isPriceTypeFree = "false"
121 isPriceTypeSubs = "false"
122 isPriceTypeMB = "false"
123 start = ""
124 end = ""
125 startDateStr = ""
126 endDateStr = ""
127 startTime = ""
128 endTime = ""
129 year = ""
130 dateDisplay = ""
131 timeDisplay = ""
132 locationDisplay = ""
133 />
134
135 <#if categories?has_content>
136 <#list categories as category>
137 <#if category.vocabulary == 'price type' && category.name == "Free">
138 <#assign isPriceTypeFree = "true" />
139 </#if>
140 <#if category.vocabulary == 'price type' && category.name == "Subscription">
141 <#assign isPriceTypeSubs = "true" />
142 </#if>
143 <#if category.vocabulary == 'price type' && category.name == "Member Benefit">
144 <#assign isPriceTypeMB = "true" />
145 </#if>
146 <#if category.vocabulary == 'delivery method' && category.name == "In Person">
147 <#assign isInPerson = "true" />
148 </#if>
149 <#if category.vocabulary == 'delivery method' && category.name == "Livestream">
150 <#if infoObj["Event End Date"]?has_content>
151 <#assign end = infoObj["Event End Date"]?datetime("yyyy-MM-dd'T'HH:mm:ss'Z'") />
152 <#assign today = .now?datetime> <!-- Today's date without time -->
153 <#assign showLiveStream = end < today />
154 <!-- Ensure 'end' is valid before performing the comparison -->
155 <#if !showLiveStream>
156 <#assign isLivestream = "true" />
157 </#if>
158 </#if>
159 </#if>
160 <#if category.vocabulary == 'event type' && category.name == "Institute">
161 <#assign isInstitute = "true" />
162 </#if>
163 <#if category.vocabulary == 'event type' && category.name == "Seminar">
164 <#assign isSeminar = "true" />
165 </#if>
166 <#if category.vocabulary == 'product type' && category.name == "Webinar">
167 <#assign isWebinar = "true" />
168 </#if>
169 <#if category.vocabulary == 'product type' && category.name == "Online Course">
170 <#assign isOnlineCourse = "true" />
171 </#if>
172 <#if category.vocabulary == 'product type' && category.name == "Event">
173 <#assign isEvent = "true" />
174 </#if>
175 <#if category.vocabulary == 'event type'>
176 <#assign eventTypeName = category.name />
177 </#if>
178 </#list>
179 </#if>
180
181 <div class="card education-card mb-4 asdasd">
182 <div class="card-body">
183 <div class="top-row">
184 <#if infoObj.deliverymethod??>
185 <div class="education-card-categories">
186 <#list infoObj.deliverymethod as format>
187 <#if format.value != "">
188 <#if defaultImgMap[format.value]?has_content>
189 <#if format.value == "Livestream">
190 <#if isLivestream == "true">
191 <span class="category">
192 <img src="${defaultImgMap[format.value]['image']}" alt="" class="category-img bg-${defaultImgMap[format.value]['color']}" />
193 <span class="category-name">${format.value}</span>
194 </span>
195 </#if>
196 <#else>
197 <span class="category">
198 <img src="${defaultImgMap[format.value]['image']}" alt="" class="category-img bg-${defaultImgMap[format.value]['color']}" />
199 <span class="category-name">${format.value}</span>
200 </span>
201 </#if>
202 </#if>
203 </#if>
204 </#list>
205 </div>
206 </#if>
207
208 <#if isInstitute == "true">
209 <div class="certification-info">
210 <span class="certification-info-text">Earn Professional Certification</span>
211 <div class="certification-info-img"><i class="certification-info-img"></i></div>
212 </div>
213 </#if>
214 </div>
215
216 <#assign viewUrl = friendlyURL />
217
218 <#if friendlyURL?contains("/p/")>
219 <#assign viewUrl = ("https://www.icba.org/p/" + friendlyURL?keep_after("/p/")) />
220 </#if>
221
222 <div class="card-title">
223 <h3 class="header-s"><a href="${viewUrl}">${productName}</a></h3>
224 </div>
225
226 <div class="education-card-details">
227 <#if infoObj["Event Start Date"]?has_content>
228 <#assign start = infoObj["Event Start Date"]?datetime("yyyy-MM-dd'T'HH:mm:ss'Z'") />
229 <#assign startDateStr = start?string("MMM d") />
230 <#assign year = start?string("yyyy") />
231 <#assign startTime = start?string("h:mm") + " " + start?string("a")?lower_case />
232 </#if>
233
234 <#if infoObj["Event End Date"]?has_content>
235 <#assign end = infoObj["Event End Date"]?datetime("yyyy-MM-dd'T'HH:mm:ss'Z'") />
236 <#assign endDateStr = end?string("MMM d") />
237 <#assign endTime = end?string("h:mm") + " " + end?string("a")?lower_case />
238 </#if>
239
240 <#if isLivestream == "true" || isInPerson == "true">
241 <#if start?has_content && end?has_content>
242 <#if start?string("yyyyMMdd") == end?string("yyyyMMdd")>
243 <#assign dateDisplay = startDateStr + ", " + year />
244 <#assign timeDisplay = startTime + "–" + endTime />
245 <#else>
246 <#assign dateDisplay = startDateStr + "–" + endDateStr + ", " + year />
247 <#assign timeDisplay = startTime />
248 </#if>
249 </#if>
250
251 </#if>
252 <div class="education-card-location">
253 <#if isWebinar == "true" || isEvent == "true">
254
255 <span class="date">${dateDisplay}</span>
256 <#if isWebinar == "true">
257 <#if isLivestream == "true" || isInPerson == "true">|</#if> <span class="category">Webinar</span>
258 </#if>
259
260 <#if isEvent == "true">
261 <#if isLivestream == "true" || isInPerson == "true">| </#if><span class="category">${eventTypeName}</span>
262 </#if>
263 <span class="time d-none">${timeDisplay}</span>
264 </#if>
265
266 <#if isInPerson == "true">
267 <span class="location">
268 <#if infoObj["Location City"]?has_content>
269 ${infoObj["Location City"]}
270 </#if>
271 <#if infoObj["Location State"]?has_content>
272 , ${infoObj["Location State"]}
273 </#if>
274 </span>
275 </#if>
276 </div>
277
278
279 <div class="education-card-credits">
280 <span class="credits-info"><#if infoObj["CPE Credits"]?has_content>CPE Credits: ${infoObj["CPE Credits"]}</#if></span>
281 </div>
282
283<!---Start ---->
284
285<!-- SKU Area -->
286<#assign productskuJson = jsonFactoryUtil.createJSONObject(productsku)/>
287<#if productskuJson?has_content && productskuJson.has("items")>
288 <#assign itemsArray = productskuJson.getJSONArray("items")/>
289 <#if itemsArray?has_content>
290<#assign priceList = [] />
291
292<#if itemsArray?has_content && itemsArray.length() gt 0>
293 <#list 0..itemsArray.length() - 1 as index>
294 <#assign item = itemsArray.getJSONObject(index) />
295 <#if item?has_content && item.getJSONArray("skuOptions")?has_content>
296 <#assign skuOptions = item.getJSONArray("skuOptions") />
297 <#assign registrationOption = "" />
298 <#assign deliveryMethod = "" />
299 <#assign rowdisplayName = "" />
300
301 <!-- Extract values from skuOptions -->
302 <#list 0..skuOptions.length() - 1 as i>
303 <#assign skuOption = skuOptions.getJSONObject(i) />
304 <#assign key = skuOption.getString("skuOptionKey") />
305 <#assign valueKey = skuOption.getString("skuOptionValueKey") />
306 <#assign valueName = skuOption.getJSONArray("skuOptionValueNames").getString(0) />
307
308 <#if key == "registration-options">
309 <#assign registrationOption = valueKey />
310 <#assign rowdisplayName = valueName />
311 </#if>
312
313 <#if key == "webinar-delivery-method">
314 <#assign deliveryMethod = valueKey />
315 </#if>
316 </#list>
317
318 <!-- Filter: Only show On-demand + desired registration-options -->
319 <#if isWebinar == "true">
320 <#if deliveryMethod == "ondemand" &&
321 (registrationOption == "icba-member" ||
322 registrationOption == "nonmember" ||
323 registrationOption == "unlimited-webinar-pass")>
324
325 <!-- Format price -->
326 <#assign price = item.price.priceFormatted />
327 <!-- Store display name + price in a map -->
328 <#assign priceList = priceList + [{"name": rowdisplayName, "price": price}] />
329 </#if>
330 <#else>
331 <#if registrationOption == "icba-member" ||
332 registrationOption == "nonmember" ||
333 registrationOption == "unlimited-webinar-pass">
334
335 <!-- Format price -->
336 <#assign price = item.price.priceFormatted />
337 <!-- Store display name + price in a map -->
338 <#assign priceList = priceList + [{"name": rowdisplayName, "price": price}] />
339 </#if>
340 </#if>
341 </#if>
342 </#list>
343 </#if>
344 </#if>
345 </#if>
346
347
348<!-- Display the collected name + price -->
349<div class="education-card-price">
350 <#if priceList?has_content>
351
352 <!-- First: Member -->
353 <#list priceList as item>
354 <#if item?has_content && item.name == "ICBA Member">
355 <span class="education-card-price-item member-price">Member: ${item.price}</span>
356 </#if>
357 </#list>
358
359 <!-- Second: Non-Member -->
360 <#list priceList as item>
361 <#if item?has_content && item.name == "Non-Member">
362 <span class="education-card-price-item member-price">${item.name}: ${item.price}</span>
363 </#if>
364 </#list>
365
366 <!-- Third: ICBA Bank Director Program Subscriber -->
367 <#list priceList as item>
368 <#if item?has_content && item.name == "Bank Director Program Subscriber">
369 <span class="education-card-price-item member-price">ICBA Bank Director Program Subscriber: ${item.price}</span>
370 </#if>
371 </#list>
372
373 <!-- Fourth: Unlimited Webinar Pass -->
374 <#list priceList as item>
375 <#if item?has_content && item.name == "Unlimited Webinar Pass">
376 <span class="education-card-price-item member-price">Unlimited Webinar Pass: ${item.price}</span>
377 </#if>
378 </#list>
379
380 </#if>
381</div>
382
383
384<!-----Ends --->
385 <div class="education-card-tags">
386 <#if categories?has_content>
387 <#assign topicCount = 0/>
388 <#assign displayedCategories = []/>
389 <#assign totalTopicCount = 0/>
390
391 <#-- First, count total topic categories -->
392 <#list categories as category>
393 <#if category.vocabulary == 'topic'>
394 <#assign totalTopicCount += 1/>
395 </#if>
396 </#list>
397
398 <#-- Now display first 3 topics -->
399 <#list categories as category>
400 <#if category.vocabulary == 'topic' && topicCount < 3>
401 <#assign taxonomyCategoryId = category.id/>
402 <#assign response = restClient.get("/headless-admin-taxonomy/v1.0/taxonomy-categories/${taxonomyCategoryId}") />
403 <#if response.taxonomyCategoryProperties?has_content>
404 <#assign firstProperty = response.taxonomyCategoryProperties[0] />
405 <#assign categoryValue = firstProperty.value />
406 <span class="tag-pill body-13">${categoryValue}</span>
407 <#else>
408 <span class="tag-pill body-13">${category.name}</span>
409 </#if>
410 <#assign topicCount += 1>
411 </#if>
412 </#list>
413
414 <#-- If more topics exist, show "+X More..." -->
415 <#if (totalTopicCount - topicCount) gt 0>
416 <span class="tag-pill body-13">+${totalTopicCount - topicCount} More...</span>
417 </#if>
418
419 </#if>
420 </div>
421 </div>
422 </div>
423 </div>
424 </#list>
425 </#if>
426</div>
