1(function (Drupal) { 2 'use strict'; 3 4 Drupal.behaviors.uclouvain_tarteaucitron = { 5 attach: function (context, settings) { 6 7 let uclouvain_declared_services = []; 8 if (typeof tarteaucitron === 'undefined') return; 9 10 function getDomainFromURL(url) { 11 try { 12 return new URL(url).hostname; 13 } catch { 14 return ''; 15 } 16 } 17 18 function isWhitelisted(domain) { 19 return whitelist.some((rule) => { 20 if (rule.startsWith('*.')) { 21 const base = rule.substring(2); 22 return domain === base || domain.endsWith('.' + base); 23 } 24 return domain === rule; 25 }); 26 } 27 28 function getServiceMatch(url) { 29 const domain = getDomainFromURL(url); 30 31 for (const service of services) { 32 if (service.domains && service.domains.some(d => domain === d || domain.endsWith('.' + d))) { 33 // execute regex of the service on url 34 const match = url.match(service.regex); 35 if (match) { 36 37 service.objectId = null; 38 39 if (match[1] || match[2]) { 40 service.objectId = match[1] || match[2]; 41 } 42 43 return service; 44 } 45 } 46 } 47 return null; 48 } 49 50 const iframes = once('uclouvain-tac-processed', 'iframe', context); 51 52 const whitelist = [ 53 '*.uclouvain.be', 54 '*.ucl.ac.be', 55 'cdn.uclouvain.be', 56 '*.genially.com', 57 'stats.sisg.ucl.ac.be' 58 ]; 59 60 const services = [ 61 { 62 name: 'youtube', 63 label: "Youtube", 64 className: 'youtube_player', 65 domains: ['youtube.com', 'youtu.be', 'youtube-nocookie.com'], 66 regex: /(?:https?:\/\/)?(?:www\.)?(?:youtube(?:-nocookie)?\.com\/(?:watch\?v=|embed\/|v\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/, 67 custom_plugin: false, 68 add_video_id_param: true 69 }, 70 { 71 name: 'vimeo', 72 label: "Vimeo", 73 className: 'vimeo_player', 74 domains: ['vimeo.com'], 75 regex: /vimeo\.com\/(?:video\/)?(\d+)/, 76 custom_plugin: false, 77 add_video_id_param: true 78 79 }, 80 { 81 name: 'dailymotion', 82 label: "Dailymotion", 83 className: 'dailymotion_player', 84 domains: ['dailymotion.com', 'dai.ly'], 85 regex: /dailymotion\.com\/video\/([a-zA-Z0-9]+)|dai\.ly\/([a-zA-Z0-9]+)/, 86 custom_plugin: false, 87 add_video_id_param: true 88 }, 89 { 90 name: 'uclouvain_flip_service', 91 label: "Flip", 92 className: 'flip_player', 93 domains: ['fliphtml5.com', 'online.fliphtml5.com'], 94 regex: /fliphtml5\.com\/([^\/]+)\/([^\/]+)/, 95 custom_plugin: true, 96 type: 'api', 97 add_video_id_param: false 98 }, 99 { 100 name: 'uclouvain_padlet_service', 101 label: "Padlet", 102 className: 'padlet_player', 103 domains: ['padlet.org', 'padlet.pics','padlet.net','padlet.com'], 104 regex: /padlet\.(org|pics|net|com)\/embed\/([a-zA-Z0-9]+)/, 105 custom_plugin: true, 106 type: 'api', 107 add_video_id_param: false 108 }, 109 { 110 name: 'uclouvain_podcastics_service', 111 label: "Podcastics", 112 className: 'podcastics_player', 113 domains: ['podcastics.com'], 114 regex: /podcastics\.com\/playlist\/(\d+)/, 115 custom_plugin: true, 116 type: 'other', 117 add_video_id_param: false 118 }, 119 { 120 name: 'uclouvain_airtable_service', 121 label: "Airtable.com", 122 className: 'airtable_player', 123 domains: ['airtable.com'], 124 regex: /airtable\.com\/embed\/([a-zA-Z0-9]+)/, 125 custom_plugin: true, 126 type: 'api', 127 add_video_id_param: false 128 }, 129 { 130 name: 'uclouvain_powerbi_service', 131 label: "Powerbi.com", 132 className: 'powerbi_player', 133 domains: ['powerbi.com'], 134 regex: /powerbi\.com\/view\?r=([a-zA-Z0-9]+)/, 135 custom_plugin: true, 136 type: "api", 137 add_video_id_param: false 138 }, 139 { 140 name: 'uclouvain_auvio_service', 141 label: "Auvio (RTBF)", 142 className: 'auvio_player', 143 domains: ['rtbf.be'], 144 regex: /www\.rtbf\.be\/auvio\/[^?]*\?id=(\d+)/, 145 custom_plugin: true, 146 type: 'video', 147 add_video_id_param: false 148 }, 149 { 150 name: 'uclouvain_spotify_service', 151 label: "Spotify", 152 className: 'spotify_player', 153 domains: ['spotify.com'], 154 regex: /open\.spotify\.com\/embed\/[a-z]+\/([a-zA-Z0-9]+)/, 155 custom_plugin: true, 156 type: 'video', 157 add_video_id_param: false 158 }, 159 { 160 name: 'uclouvain_flickr_service', 161 label: "Flickr", 162 className: 'flicker_player', 163 domains: ['flickr.com'], 164 regex: /www\.flickr\.com\/photos\/[^\/]+\/sets\/(\d+)/, 165 custom_plugin: true, 166 type: 'other', 167 add_video_id_param: false 168 }, 169 { 170 name: 'uclouvain_xmind_service', 171 label: "xmind", 172 className: 'xmind_player', 173 domains: ['xmind.app'], 174 regex: /xmind\.app\/embed\/([a-zA-Z0-9]+)/, 175 custom_plugin: true, 176 type: 'api', 177 add_video_id_param: false 178 }, 179 { 180 name: 'uclouvain_srf_service', 181 label: "SRF.ch", 182 className: 'srf_player', 183 domains: ['tp.srgssr.ch', 'rts.ch'], 184 regex: /urn=urn:rts:audio:(\d+)/, 185 custom_plugin: true, 186 type: 'video', 187 add_video_id_param: false 188 } 189 ]; 190 191 // Modify twig template is not enough for inclusion. Need to parse all page iframes.
192 iframes.forEach((iframe) => { 193 const src = iframe.src || ''; 194 const domain = getDomainFromURL(src); 195 196 // don't touch the iframe is its domain (from url) is into whitelist. 197 // Matomo debug preview adds an iframe with src = about:blank. 198 if (isWhitelisted(domain) || src === 'about:blank') return; 199 200 const width = iframe.style.width || iframe.getAttribute('data-width') || iframe.getAttribute('width') || '100%'; 201 const height = iframe.style.height || iframe.getAttribute('data-height') || iframe.getAttribute('height') || '100%'; 202 203 let service = getServiceMatch(src); 204 205 // don't touch the iframe if service doesn't exist 206 if (service === null) { 207 service = { 208 className: 'uclouvain_external_player', 209 objectId: null, 210 name : 'uclouvain_external_services', 211 label : 'Contenu externe', 212 add_video_id_param: false 213 } 214 } 215 216 const placeholder = document.createElement('div'); 217 placeholder.setAttribute('class', service.className + ' tarteaucitron-placeholder'); 218 placeholder.setAttribute('data-width', width); 219 placeholder.setAttribute('data-src', src); 220 placeholder.setAttribute('data-height', height); 221 222 placeholder.style.height = height; 223 placeholder.style.width = width; 224 placeholder.id = service.className; 225 226 placeholder.innerHTML = ` 227 <div class="tac_activate tac_activate_${service.className}"> 228 <div class="tac_float"> 229 ${service.label} est bloqué. 230 <button type="button" aria-label="Autoriser ${service.label}" id="${service.name}Allowed" class="tarteaucitronAllow" aria-pressed="true"> 231 <span class="tarteaucitronCheck" aria-hidden="true"></span> Autoriser 232 </button> 233 </div> 234 </div> 235 `; 236 237 if (service.add_video_id_param) { 238 placeholder.setAttribute('data-videoID', service.objectId); 239 } 240 241 iframe.parentNode.replaceChild(placeholder, iframe); 242 }); 243 244 245 /** 246 * Declare a Tarte au cintron service. 247 * @param name 248 * @param label 249 * @param className 250 * @param type 251 */ 252 function declareTarteaucitronService({ name, label,className, type='other' }) { 253 tarteaucitron.services[name] = { 254 key: name, 255 type: type, 256 name: label, 257 description: `Ce plugin bloque les contenus ${label}`, 258 needConsent: true, 259 cookies: [], 260 js: function () { 261 const elements = document.querySelectorAll(`.${className}`); 262 elements.forEach(el => { 263 const src = el.getAttribute('data-src'); 264 const width = el.getAttribute('data-width') || el.style.width || '100%'; 265 const height = el.getAttribute('data-height') || el.style.height || '100%'; 266 267 const iframe = document.createElement('iframe'); 268 iframe.src = src; 269 iframe.className = className; 270 iframe.width = width; 271 iframe.height = height; 272 iframe.allowFullscreen = true; 273 iframe.setAttribute('frameborder', 0); 274 275 el.parentNode.replaceChild(iframe, el); 276 }); 277 }, 278 fallback: function () { 279 const id = className; 280 const iframes = document.querySelectorAll(`iframe.${className}`); 281
282 iframes.forEach((iframe) => { 283 const src = iframe.src; 284 if (!src) return; 285 286 const width = iframe.getAttribute('data-width') || iframe.style.width || '100%'; 287 const height = iframe.getAttribute('data-height') || iframe.style.height || '100%'; 288 289 const placeholder = document.createElement('div'); 290 placeholder.className = `${className} tarteaucitron-placeholder`; 291 placeholder.setAttribute('data-src', src); 292 placeholder.setAttribute('data-width', width); 293 placeholder.setAttribute('data-height', height); 294 placeholder.style = `z-index:98;position:relative;height:${height};width:${width};`; 295 placeholder.setAttribute('aria-label', `Contenu externe désactivé.`); 296 297 placeholder.id = id; 298 placeholder.innerHTML = ` 299 <div class="tac_activate tac_activate_${id}"> 300 <div class="tac_float"> 301 ${label} est bloqué. 302 <button type="button" class="tarteaucitronAllow" id="${name}" onclick="tarteaucitron.userInterface.respond('${name}', true);"> 303 <span class="tarteaucitronCheck" aria-hidden="true"></span> Autoriser ${label} 304 </button> 305 </div> 306 </div> 307 `; 308 309 iframe.parentNode.replaceChild(placeholder, iframe); 310 }); 311 } 312 }; 313 } 314 315 // UCLouvain Matomo - Specific plugin 316 tarteaucitron.services.uclouvain_matomo = { 317 key: 'uclouvain_matomo', 318 type: 'analytic', 319 name: 'UCLouvain Tracking', 320 needConsent: true, 321 cookies: [], 322 js: function () { 323 // @todo is necessary for matomo triggering (Flying Fish) 324 }, 325 fallback: function () { 326 // @todo is necessary for matomo triggering (Flying Fish) 327 } 328 }; 329 330 if (!tarteaucitron.job.includes('uclouvain_matomo')) { 331 tarteaucitron.job.push('uclouvain_matomo'); 332 } 333 334 tarteaucitron.job = tarteaucitron.job || []; 335 services.forEach(function(service, index) { 336 337 if (service.custom_plugin) { 338 declareTarteaucitronService({ 339 name: service.name, 340 label: service.label, 341 className: service.className, 342 type: service.type ? service.type : 'other' 343 }); 344 } 345 346 if (!tarteaucitron.job.includes(service.name)) { 347 tarteaucitron.job.push(service.name); 348 } 349 }); 350 351 /* generic service */ 352 declareTarteaucitronService({ 353 name: 'uclouvain_external_services', 354 label: 'Contenus externes', 355 className: 'uclouvain_external_player' 356 }); 357 358 if (!tarteaucitron.job.includes('uclouvain_external_services')) { 359 tarteaucitron.job.push('uclouvain_external_services'); 360 } 361 362 } 363 }; 364})(Drupal);
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.