PageSourceSearch

https://otherplans.gallery/lib/archimedes/scripts/archimedes-front…=195f30e&g=7e75de386011cf0d3196d63cbc4cc5b9

js otherplans.gallery collected 2026-09-29 04:13:40 UTC 221,337 bytes, 4,080 lines download raw bytes

1// Core frontend JS for Archimedes sites
2// Copyright (c) 2011 Artlogic Media Limited - http://www.artlogic.net/
3
4
5// prevent firebug errors
6if (!window.console) {
7    window.console = {
8        log: function() {
9            // do nothing...
10        }
11    };
12}
13
14(function($) {
15    
16    // Variable required to determine if the container has been hidden by the protected path.
17    // Due to pageload the container needs to be set to visible again when navigating to another page otherwise the container is always set to hide.
18    var container_hidden = false;
19    var container_els = '#container > *:not(.header-fixed-wrapper, #header, #top_nav_section, .pp-do-not-show)';
20    var allow_campaign_tracking = false;
21    
22    window.protected_paths_login_after = function() {};
23    window.protected_paths_display_form_after = function() {};
24    window.protected_paths_show_content_after = function() {};
25    
26    window.archimedes = {
27
28        // please store all archimedes functions and vars
29        // inside this object...
30
31        archimedes_toolbar: {
32
33            init: function() {
34                $('#archimedes-toolbar-hide a').click(function() {
35                    window.archimedes.archimedes_toolbar.hide();
36                    $('#archimedes-toolbar')
37                        .addClass('archimedes-toolbar-hidden')
38                    ;
39                    return false;
40                });
41                $('#archimedes-toolbar-hitarea')
42                    .mouseover(function() {
43                        $('#archimedes-toolbar-hitarea')
44                            .clearQueue()
45                            .delay(500)
46                            .queue(function () {
47                                window.archimedes.archimedes_toolbar.show();
48                            });
49                        ;
50                    })
51                    .mouseout(function() {
52                        $('#archimedes-toolbar-hitarea').clearQueue();
53                    })
54                    .click(function() {
55                        $('#archimedes-toolbar-hitarea').clearQueue();
56                        window.archimedes.archimedes_toolbar.show();
57                    })
58                ;
59
60                if (g.has_local_storage()) {
61                    if (localStorage.getItem('archimedes-toolbar-display') == 'hidden') {
62                        window.archimedes.archimedes_toolbar.hide(0);
63                    }
64                }
65
66                $('#archimedes-toolbar-editpage a')
67                    .each(function() {
68                        var href = $(this).attr('href');
69                        $(this).click(function() {
70                        	var win_name = $(this).attr('id');
71                            h.open_win({url: href, name: win_name, width: 1000, height: 670, scrollbars: true});
72                            return false;
73                        });
74                    })
75                    .attr('href', '#')
76                    .removeAttr('target')
77                ;
78
79                $('#archimedes-toolbar-inactive')
80                    .click(function() {
81                        return false;
82                    })
83                ;
84            },
85
86            hide: function(speed) {
87                var hideSpeed = 800;
88                if (speed == 0) {
89                    var hideSpeed = 0;
90                }
91                if (g.has_local_storage()) {
92                    localStorage.setItem('archimedes-toolbar-display', 'hidden');
93                }
94                $('#archimedes-toolbar-inactive').css('display', 'block');
95                $('#archimedes-toolbar-container')
96                    .clearQueue()
97                    .fadeTo(hideSpeed, 0, function() {
98                        $(this).css('display', 'none');
99                        $('#archimedes-toolbar-inactive').css('display', 'none');
100                    })
101                ;
102                $('#archimedes-toolbar-faux')
103                    .clearQueue()
104                    .animate({height: 5}, hideSpeed)
105                ;
106                $('#archimedes-toolbar-hitarea')
107                    .clearQueue()
108                    .css('display', 'block')
109                ;
110            },
111
112            show: function() {
113                if (g.has_local_storage()) {
114                    localStorage.setItem('archimedes-toolbar-display', 'visible');
115                }
116                $('#archimedes-toolbar-inactive').css('display', 'block');
117                $('#archimedes-toolbar-container')
118                    .css('display', 'block')
119                    .fadeTo(800, 1, function() {
120                        $('#archimedes-toolbar-inactive').css('display', 'none');
121                    })
122                ;
123                $('#archimedes-toolbar-faux').animate({height: 41}, 500);
124                $('#archimedes-toolbar-hitarea').css('display', 'none');
125            }
126
127        },
128
129        archimedes_core: {
130
131            init: function() {
132                // check for window.core object. If present, run various functions...
133                if (typeof window.artlogic_websites_core != 'undefined') {
134                    window.core = window.artlogic_websites_core;
135                }
136                if (window.core) {
137                    this.rewrite_proxy_dir_urls();
138                    this.no_autocomplete();
139                    this.process_links();
140                }
141                this.protected_paths.init();
142                this.analytics.init();
143                this.cms_preview.init();
144                this.cookie_notification.init();
145                h.accessibility.init();
146            },
147            
148            cms_preview: {
149                
150                init: function() {
151                    this.update_links();
152                },
153                
154                update_links: function(context) {
155                    var context = context && typeof context != 'undefined' ? context : '#container';
156                    
157                    if ($('#cms-frontend-toolbar-preview.cms-frontend-toolbar-preview-on').length && !$('body').hasClass('website-editor-mode')) {
158                        $('a', context).each(function() {
159                            if ($(this).attr('href') && !$(this).hasClass('link-no-preview')) {
160                                if ($(this).attr('href').indexOf('http') < 0 && $(this).attr('href').indexOf('#') < 0 && $(this).attr('href').indexOf('_cmspreview') < 0) {
161                                    $(this).attr('href', $(this).attr('href') + '?_cmspreview=1');
162                                }
163                            }
164                        });
165                    }
166                    if (window.location.search.indexOf('?_preview_uid=') == 0 || window.location.search.indexOf('?vip=') == 0) {
167                        $('a', context).each(function() {
168                            if ($(this).attr('href')) {
169                                if ($(this).attr('href') != '#') {
170                                    if ($(this).attr('href').indexOf('http') < 0 && $(this).attr('href').indexOf(window.location.search) < 0) {
171                                        var updated_url = $(this).attr('href');
172                                        var this_hash = '';
173                                        if ($(this).attr('href').indexOf('#') != -1) {
174                                            var this_hash = '#' + $(this).attr('href').split('#')[1];
175                                            var updated_url = $(this).attr('href').split('#')[0];
176                                        }
177                                        var updated_url = updated_url + window.location.search + this_hash;
178                                        $(this).attr('href', $(this).attr('href') + window.location.search);
179                                    }
180                                }
181                            }
182                        });
183                        $('[data-href]', context).each(function() {
184                            if ($(this).attr('data-href').indexOf('http') < 0 && $(this).attr('data-href').indexOf(window.location.search) < 0) {
185                                $(this).attr('data-href', $(this).attr('data-href') + window.location.search);
186                            }
187                        });
188                    }
189                }
190                
191            },
192
193            process_links: function() {
194
195                // elements with an attribute of 'data-onclick-url' should behave as a link'
196                $('[data-onclick-url]:not([data-onclick-url=""])')
197                    .addClass('cms-clickable')
198                    .unbind('click')
199                    .click(function() {
200                        document.location.href = $(this).attr('data-onclick-url');
201                    })
202                ;
203
204                // external links open in new win (unless they already have a class of 'noPopup' or 'nopopup')
205                $('a').each(function() {
206                    var el = $(this);
207                    var href = el.attr('href');
208                    if (href && href.indexOf('http') == 0) {
209                        $(this).attr('rel', 'noopener');
210                    }
211                    if (href && href.indexOf('http') == 0 && !el.hasClass('noPopup')
212                        && !el.hasClass('nopopup')
213                        && !el.hasClass('image_popup')
214                        && !$('body').hasClass('no_pop_ups')
215                        && !$('body').hasClass('nopopups')
216                        && href.indexOf(window.location.hostname) != 0) {
217                            // Accessibility - let user know the link opens in a new window and is an external site.
218                            
219                            var ariaAttr = el.attr('aria-label');
220                            // Quickfix to prevent ie break
221                            if (typeof ''.startsWith != 'undefined') {
222                                // Check if the link has an existing aria label assigned, and retain it if so.
223                                    if ( !href.startsWith("https://artlogic-res.cloudinary") 
224                                    && typeof ariaAttr !== typeof undefined 
225                                    && ariaAttr !== false 
226                                    && !el.hasClass('external-link-aria-label-added') 
227                                ) {
228                                    aria_label = ariaAttr + " (Link goes to a different website and opens in a new window)."
229                                    el.attr('aria-label', aria_label).addClass('external-link-aria-label-added');
230                                } else if ( !href.startsWith("https://artlogic-res.cloudinary") && !el.hasClass('external-link-aria-label-added') ) {
231                                    var aria_label = el.text().replace(/\s\s+/g, ' ').replace(/"/g, "'") + " (Link goes to a different website and opens in a new window).";
232                                    el.find('img').each(function() {
233                                        if ($(this).attr("alt") && $(this).attr("alt") != '') {
234                                            var alt = $(this).attr("alt");
235                                            aria_label += ' Image: ' + alt + '.'
236                                        }
237                                    });
238                                    el.attr('aria-label', aria_label).addClass('external-link-aria-label-added');
239                                }
240                            }
241                            
242                            
243                            el.attr('target', '_blank').addClass('external');
244                    }
245                });
246
247                // elements with a class of 'behave_as_link' set an onclick value
248                // based on the 'data-behave_as_link_url' attribute (html5 only)
249                $('.behave_as_link').each(function() {
250                    var el = $(this);
251                    if (el.attr('data-behave_as_link_url')) {
252                        el.css('cursor', 'pointer')
253                            .unbind('click')
254                            .click(function() {
255                                document.location.href = el.attr('data-behave_as_link_url');
256                            });
257                    }
258                });
259
260            },
261
262            no_autocomplete: function() {
263
264                $('.no-autocomplete').each(function(){
265
266                    /* Google Chrome fix - autocomplete attr is assigned to each text
267                     * field, rather that the whole form  */
268
269                    $(this).removeAttr('autocomplete');
270                    $(this).find('input[type="text"]').each(function(){
271                        $(this).attr('autocomplete', 'off')
272                    });
273                });
274
275            },
276
277            rewrite_proxy_dir_urls: function() {
278
279                // do we need to rewrite urls to prepend a proxy dirname (e.g.
280                // for mobile site under /m/... etc.
281                if (window.core.top_level_proxy_dirs && !window.core.rewrite_top_level_proxy_dirs_server_side) {
282                    // set proxy_dir to undefined so variable gets cleared on pageload - required for translations
283                    window.archimedes.proxy_dir = undefined;
284                    var t;
285                    for (var i = 0; i < window.core.top_level_proxy_dirs.length; i ++) {
286                        
287                        t = window.core.top_level_proxy_dirs[i];
288                        if (document.location.pathname.indexOf(t) == 0 || document.location.pathname == t.substring(0, t.length - 1)) {
289                            window.archimedes.proxy_dir = t.substring(0, t.length - 1);
290                            break;
291                        }
292                    }
293                    
294                    if (window.archimedes.proxy_dir) {
295                        /* rewrite urls, prepending proxy_dir if
296                         * a) it is not already there
297                         * b) it is not an external link
298                         * c) the link begins with '/'
299                         * d) the tag does NOT have a classname 'no_proxy_dir_rewrite'
300                         */
301                        $('a').each(function() {
302                            var el = $(this);
303                            var href = el.attr('href');
304                            if (href && href.indexOf('http') != 0 &&
305                                href.indexOf('/media/') != 0 &&
306                                href.indexOf('/custom_images/') != 0 &&
307                                href.indexOf('/usr/') != 0 &&
308                                href.indexOf('/cart/') != 0 &&
309                                href.indexOf('/modules/') != 0 &&
310                                href.indexOf('/') == 0 &&
311                                !el.hasClass('no_proxy_dir_rewrite') &&
312                                !href.indexOf(window.archimedes.proxy_dir + '/') == 0) {
313                                    el.attr('href', window.archimedes.proxy_dir + el.attr('href'));
314                            }
315                        });
316                        $('form').each(function() {
317                            var el = $(this);
318                            var href = el.attr('action');
319                            if (href && href.indexOf('action') != 0 &&
320                                href.indexOf('/media/') != 0 &&
321                                href.indexOf('/custom_images/') != 0 &&
322                                href.indexOf('/cart/') != 0 &&
323                                href.indexOf('/modules/') != 0 &&
324                                href.indexOf('/') == 0 &&
325                                !el.hasClass('no_proxy_dir_rewrite') &&
326                                !href.indexOf(window.archimedes.proxy_dir + '/') == 0) {
327                                el.attr('action', window.archimedes.proxy_dir + el.attr('action'));
328                            }
329                        });
330                    }
331
332                }
333                
334            },
335            
336            interactions: {
337                
338                init: function() {
339                    
340                },
341                
342                log: function(interaction_data) {
343                    if (interaction_data && typeof interaction_data != 'undefined') {
344                        
345                        if (typeof interaction_data['website_app_name'] == 'undefined' && typeof $('body').attr('data-site-name') != 'undefined') {
346                            interaction_data['website_app_name'] = $('body').attr('data-site-name');
347                        }
348                        if (typeof interaction_data['site'] == 'undefined' && typeof $('body').attr('data-connected-db-name') != 'undefined') {
349                            interaction_data['site'] = $('body').attr('data-connected-db-name');
350                        }
351                        
352                        if (typeof interaction_data['website_app_name'] != 'undefined' && typeof interaction_data['site'] != 'undefined') {
353                            var name = "contact_uid=";
354                            var cookies = document.cookie;
355                            var ca = cookies.split(';');
356                            var contact_uid = '';
357                            for(var i = 0; i < ca.length; i++) {
358                                var c = ca[i];
359                                while (c.charAt(0) == ' ') {
360                                c = c.substring(1); }
361                                if (c.indexOf(name) == 0) {
362                                    // Found it! So retrieve
363                                    contact_uid = c.substring(name.length, c.length);
364                                } 
365                            }
366                            // Couldn't find one so we need to set one
367                            if (contact_uid == '') {
368                                // Creating a UID - Should work if we don't have a function for this in JS already 
369                                contact_uid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
370                                    var dt = new Date();
371                                    var r = (dt + Math.random()*16)%16 | 0;
372                                    dt = Math.floor(dt/16);
373                                    return (c=='x' ? r :(r&0x3|0x8)).toString(16);
374                                });
375                                var d = new Date();
376                                // One year expiry as stipulated by the ePrivay Directive
377                                d.setTime(d.getTime() + (365 * 24 * 60 * 60 * 1000));
378                                var expires = "expires="+d.toUTCString();
379                                document.cookie = cookies + ";contact_uid=" + contact_uid + ";" + expires + ";";
380                            }
381                            interaction_data['cookie_id'] = contact_uid;
382                            interaction_data['source_url'] = document.location.href; 
383                            interaction_data['table'] = 'integrated_data_capture';
384                            $.ajax({
385                                url: 'https://events.artlogic.net/contact-interactions', type: 'POST',
386                                data: interaction_data,
387                                contentType: 'application/json; charset=utf-8',
388                            }); 
389                        }
390                    }
391                }
392                
393            },
394
395            protected_paths: {
396                init: function() {
397                    this.setup();
398                },
399
400                after_form_show: function() {},
401                after_allow_access: function() {},
402
403                setup: function() {
404                    if ($('#protected_path_login').length) {
405
406                        var urlHash = window.location.hash.substring(1);
407                        // This looks gross because it's not using modern JavaScript syntax to not
408                        // break IE11 compatibility.
409                        var urlHashParams =
410                            urlHash.split('&')
411                                   .map(function(item) { return item.split('=') })
412                                   .filter(function(item) { return item.length == 2 })
413                                   .reduce(function(obj, item) { obj[item[0]] = item[1]; return obj }, {});
414                        // Modern version:
415                        // let urlHashParams = urlHash.split('&')
416                        //                            .map((item) => item.split('='))
417                        //                            .filter((item) => item.length == 2);
418                        // urlHashParams = Object.fromEntries(urlHashParams);
419
420                        var ppl_ak_elem = $('#ppl_ak');
421                        var useArtlogicDatacapture = ppl_ak_elem.length && typeof ppl_ak_elem.attr('data-use_artlogicdatacapture') != 'undefined';
422                        var passwordCacheModeActive = $('input#pp_password_cache_mode').length;
423                        if (passwordCacheModeActive && useArtlogicDatacapture && 'visitor_uid' in urlHashParams) {
424                            window.location.hash = '';
425                            var uid = $('#protected_path_login_form').data('protected-path-uid');
426                            var datacaptureURL = 'https://datacapture.artlogic.net/auth/protected-path/capture/' + uid;
427                            $('#protected_path_login').addClass('protected_path_loading protected_path_verifying_status');
428                            $.ajax({
429                                type: "POST",
430                                data: urlHashParams,
431                                dataType: 'json',
432                                url: datacaptureURL,
433                                success: function(data) {
434                                    if (data.success && data.encryption_key) {
435                                        data['access_token'] = data.encryption_key;
436                                        data['password_cache_mode'] = data.encryption_key;
437                                        data['private_area_access_key'] = data.encryption_key;
438                                        data['email'] = (typeof data.email != 'undefined'? data.email : 'datacapture-auto-entry');
439                                        // data['uid'] = data.viewing_room_uid;
440                                        // data['uid'] = data.uid;
441                                        window.archimedes.archimedes_core.protected_paths.request_access(data);
442                                        return;
443                                    }
444                                },
445                                error: function(data) {
446                                    //if (typeof(data) == 'object' && 'responseJSON' in data && 'error' in data.responseJSON) {
447                                    //    h.alert('<h2>Sorry</h2>'+data.responseJSON.error+'.');
448                                    //} else {
449                                    //    h.alert('<h2>Sorry</h2>An unknown error occurred.');
450                                    //}
451                                    h.alert('<h2>Sorry</h2>We were unable to log you in. Please try again by going back and clicking the original login link. If we have given you a password for this page, you can login directly using the form on this page. Alternatively please contact us for assistance.');
452                                    $('#protected_path_login').removeClass('protected_path_loading protected_path_verifying_status');
453                                    return;
454                                }
455                            });
456                        }
457
458                        $('#protected_path_login #submit a').unbind().click(function() {
459                            $('#protected_path_login_form')[0].submit();
460                            return false;
461                        });
462                        
463                        $('#protected_path_login_form').find('input[type="text"], input[type="email"], input[type="password"]').unbind('keydown.submitform').bind('keydown.submitform', function(event) {
464                            if (event.keyCode === 13) {
465                                $('#protected_path_login_form').submit();
466                                return false;
467                            }
468                        });
469                        
470                        $('.protected_path_frontend_form').not('.initialized').each(function() {
471                            $(this).addClass('initialized');
472                            var instant_access_key = window.location.hash && window.location.hash.indexOf('#access=') > -1 ? window.location.hash.replace('#access=', '') : false;
473                            if (instant_access_key) {
474                                if (instant_access_key.indexOf('&') > -1) {
475                                    instant_access_key = instant_access_key.split('&')[0];
476                                }
477                            }
478                            if (window.location.hash.indexOf('#access=') > -1 && instant_access_key == $('#protected_path_login_form input[name="ppl_ak"]').val()) {
479                                // Allow instant access if the correct access key has been passed
480                                if (window.location.hash.indexOf('&') > -1) {
481                                    window.location.hash = '#' + window.location.hash.split(/&(.+)/)[1];
482                                } else {
483                                    window.location.hash = '';
484                                }
485                                console.log('allow again');
486                                data = {
487                                    'private_area_access_key': $('#protected_path_login_form input[name="ppl_ak"]').val(), 
488                                    'access_token': 'instant_access', 
489                                    'uid': 'instant_access'
490                                }
491                                window.archimedes.archimedes_core.protected_paths.allow_access(data, false, true);
492                            } else if (window.location.hash.indexOf('#verify=') > -1) {
493                                // Verify user if necessary
494                                window.archimedes.archimedes_core.protected_paths.verify_email();
495                            } else {
496                                // Check access status
497                                window.archimedes.archimedes_core.protected_paths.check_status();
498                            }
499                            
500                            // Format the form
501                            $('#protected_path_login_form .protected_path_field_row').each(function() {
502                                if ($('input[type="text"], input[type="email"], input[type="password"]', this).length) {
503                                    if ($('label', this).is(':hidden')) {
504                                        $('input[type="text"], input[type="email"], input[type="password"]', this).attr('placeholder', $('label', this).text());
505                                    }
506                                }
507                            });
508                             
509                            // Add submit events for form
510                            $('#protected_path_login_form').unbind().submit(function() {
511                                form_data = {};
512                                $('input, select, textarea', this).each(function() {
513                                    if ($(this).is(':radio')) {
514                                        var checked_radio = $('input[name="' + $(this).attr('name') + '"]:checked').val();
515                                        if (!checked_radio || typeof checked_radio == 'undefined') {
516                                            var checked_radio = '';
517                                        }
518                                        form_data[$(this).attr('name')] = checked_radio;
519                                    } else if ($(this).is(':checkbox')) {
520                                        var checked = $(this).is(':checked');
521                                        if (checked) {
522                                            form_data[$(this).attr('name')] = '1';
523                                        } else {
524                                            form_data[$(this).attr('name')] = '0';
525                                        }
526                                    } else {
527                                        form_data[$(this).attr('name')] = $(this).val().replace(/\n/g, '<br />');
528                                    }
529                                });
530                                
531                                if ($('input[name="path"]', this).length) {
532                                    if (typeof $('input[name="path"]', this).attr('data-original-url') != 'undefined') {
533                                        form_data['path'] = $('input[name="path"]', this).attr('data-original-url');
534                                    } else {
535                                        form_data['path'] = $('input[name="path"]', this).val();
536                                    }
537                                } else {
538                                    form_data['path'] = window.location.pathname;
539                                }
540                                
541                                if ($('#protected_path_login[data-related-record-page-title-hash]').length) {
542                                    form_data['page_title_hash'] = $('#protected_path_login[data-related-record-page-title-hash]').attr('data-related-record-page-title-hash');
543                                }
544                                
545                                if ($('#protected_path_login[data-related-record-page-subtitle-hash]').length) {
546                                    form_data['page_subtitle_hash'] = $('#protected_path_login[data-related-record-page-subtitle-hash]').attr('data-related-record-page-subtitle-hash');
547                                }
548                                
549                                var email_field_value = $('#pp_email').val();
550                                
551                                // Validate fields
552                                if ($('#protected_path_login_form input:required, #protected_path_login_form input#f_pp_terms_accepted').filter(function() {
553                                    if ($(this).is(':checkbox')) {
554                                        return !$(this).is(':checked');
555                                    } else {
556                                        return !this.value;
557                                    }
558                                }).length > 0) {
559                                    if ($('#protected_path_login_form input#f_pp_terms_accepted').length > 0) {
560                                        
561                                        var message_heading = $('#protected_path_login[data-field-alert-heading]').length ? $('#protected_path_login[data-field-alert-heading]').attr('data-field-alert-heading') : 'Sorry';
562                                        var message_content = $('#protected_path_login[data-field-alert-content]').length ? $('#protected_path_login[data-field-alert-content]').attr('data-field-alert-content') : 'Please fill in all the required fields and agree to the terms and conditions.';
563                                        h.alert('<h2>' + message_heading + '</h2>' + message_content);
564                                        
565                                    } else {
566                                        
567                                        var message_heading = $('#protected_path_login[data-field-alert-heading]').length ? $('#protected_path_login[data-field-alert-heading]').attr('data-field-alert-heading') : 'Sorry';
568                                        var message_content = $('#protected_path_login[data-field-alert-content]').length ? $('#protected_path_login[data-field-alert-content]').attr('data-field-alert-content') : 'Please fill in all the required fields.';
569                                        h.alert('<h2>' + message_heading + '</h2>' + message_content);
570                                        
571                                    }
572                                } else {
573                                    // Validate email
574                                    if ($('#pp_email').val()) {
575                                        if (email_field_value != '' && email_field_value.indexOf('@') > -1 && email_field_value.split('@')[1].indexOf('.') > -1 && email_field_value.indexOf(' ') == -1) {
576                                            window.archimedes.archimedes_core.protected_paths.request_access(form_data);
577                                        } else {
578                                            h.alert('<h2>Sorry</h2>Please ensure you have entered a valid email address.');
579                                        }
580                                    } else {
581                                        // Likely to be the encryption option
582                                        window.archimedes.archimedes_core.protected_paths.request_access(form_data);
583                                    }
584                                }
585                                
586                                return false;
587                            });
588                            
589                            $('.button', this).unbind().click(function() {
590                                $(this).closest('form').submit(); 
591                                return false;
592                            });
593                            
594                            window.addEventListener('load', function() {
595                                window.protected_paths_display_form_after();
596                            });
597                        });
598                    } else {
599                        if (container_hidden) {
600                            $(container_els).attr('aria-hidden', 'false').show();
601                        }
602                    }
603                },
604                
605                verify_email: function(uid) {
606                    var uid = uid && typeof uid != 'undefined' ? uid : (window.location.hash && window.location.hash.indexOf('#verify=') > -1 ? window.location.hash.replace('#verify=', '') : false);
607                    
608                    var data = {
609                        'path': window.location.pathname,
610                        'content_type_data': JSON.stringify(window.archimedes.archimedes_core.gather_page_content(window.protected_paths_html)) // Supply the HTML to this function at it does not already exist on the page.
611                    };
612                    
613                    if (uid) {
614                        $('#protected_path_login').addClass('protected_path_loading protected_path_verifying_status');
615                        $.ajax({
616                            type: "POST",
617                            data: data,
618                            dataType: 'json',
619                            url: '/modules/protected_paths/verify_email/' + uid + '/', // Previously requested from /api/protected_paths/
620                            success: function(data) {
621                                if (data.success) {
622                                    // Success
623                                    window.archimedes.archimedes_core.protected_paths.allow_access(data);
624                                } else {
625                                    // Error
626                                    h.alert('<h2>Sorry</h2>An unknown error occurred.');
627                                    $('#protected_path_login').removeClass('protected_path_loading protected_path_verifying_status');
628                                }
629                            },
630                            error: function(data) {
631                                h.alert('<h2>Sorry</h2>An unknown error occurred.');
632                                $('#protected_path_login').removeClass('protected_path_loading protected_path_verifying_status');
633                            }
634                        });
635                    }
636                },
637
638                after_decrypted_content_added: function() {
639
640                    if(window.galleries) {
641                        window.galleries.init();
642                    }
643                    if(window.feature_panels) {
644                        window.feature_panels.init();
645                    }
646                    if(window.cart) {
647                        window.cart.init();
648                    }
649                    if(window.site) {
650                        window.site.init();
651                    }
652                    if (window.archimedes && typeof window.archimedes != 'undefined') {
653                        window.archimedes.archimedes_core.init();
654                    }
655                    
656                    // $('body').removeClass('protected-path-login-mode fullscreen-slide-dark fullscreen-slide-light type-fullscreen');
657                    // $('#protected_path_login').addClass('protected_path_hide');
658
659                    
660                
661                    // window.protected_paths_login_after();
662                    
663                    $(window).trigger('resize');
664                    $(window).trigger('scroll');
665                    
666                    setTimeout(function() {
667                        $('#protected_path_login').remove();
668                        $('#protected_path_login').removeClass('protected_path_loading protected_path_verifying_status');
669                    }, 1000);
670
671                },
672
673                replace_main_content: function(originalContent, newContent){
674                    
675                    // 's' modifier below not available in FF
676                    // var regex = /(.*)<!--contentstart-->(.*)<!--contentend-->(.*)/gms;
677                    var regex = /([\s\S]*)<!--contentstart-->([\s\S]*)<!--contentend-->([\s\S]*)/gm;
678
679                    var m,
680                    preContent,
681                    mainContent,
682                    postContent;
683
684                    while ((m = regex.exec(originalContent)) !== null) {
685                        // This is necessary to avoid infinite loops with zero-width matches
686                        if (m.index === regex.lastIndex) {
687                            regex.lastIndex++;
688                        }
689                        // The result can be accessed through the `m`-variable.
690                        m.forEach(function (match, groupIndex) {
691                            if (groupIndex == 1) {
692                                preContent = match;
693                            } 
694                            if (groupIndex == 2) {
695                                mainContent = match;
696                            } 
697                            if (groupIndex == 3) {
698                                postContent = match;
699                            } 
700                            // console.log(`Found match, group ${groupIndex}: ${match}`);
701                        });
702                    }
703                    var result = preContent + newContent + postContent;
704                    // console.log(result);
705                    return result;
706
707                },
708                
709                allow_access: function(data, decryptedPageContent, instant) {
710                    var instant = typeof instant != 'undefined' ? instant : false;
711                    if (data && typeof data != 'undefined') {
712                        if (g.has_local_storage_test() && typeof localStorage != 'undefined') {
713                            
714                            // Check for existing data
715                            if (localStorage.getItem('protected_paths_access')) {
716                                // If protected_paths storage already exists
717                                var protected_paths_data = JSON.parse(localStorage.getItem('protected_paths_access'));
718                            } else {
719                                var protected_paths_data = {};
720                            }
721                            
722                            // Set new data
723                            protected_paths_data[$('#protected_path_login_form').attr('data-protected-path-id')] = {
724                                'access_token': data.access_token,
725                                'private_area_access_key': data.private_area_access_key,
726                                'uid': data.uid
727                            }
728
729                            // Decrypt data...
730                            var cacheDataExists = false;
731                            if ($('#cachedata').length || decryptedPageContent) {
732                                cacheDataExists = true;
733                                if (!decryptedPageContent) {
734                                    decryptedPageContent = '';
735                                    key = data.private_area_access_key;
736                                    var decrypted_txt = this.decrypt('#cachedata', key);
737
738                                    if (decrypted_txt.length && decrypted_txt.indexOf('protected page access allowed') > 0) {
739                                        //update
740                                        decryptedPageContent = decrypted_txt;
741                                    }
742                                } 
743
744                                //update window.protected_paths_html
745                                console.log('protected path - set window.protected_paths_html 2');
746                                window.protected_paths_html = this.replace_main_content(window.protected_paths_html, decryptedPageContent);
747                            }
748                            
749                            // Set in local storage
750                            if(!$('.templated-gallery-site-demo').length && window.location.search.indexOf('_cmspreview_protectedpath_datacapture') == -1 && window.location.search.indexOf('_cmspreview_protectedpath_password') == -1) {
751                                localStorage.setItem('protected_paths_access', JSON.stringify(protected_paths_data));
752                            }
753
754                            if (window.gtag) {
755                                gtag('event', 'protected_paths_final_page_viewed', {});
756                            }
757                            
758                            if (window.ga) {
759                                ga('send', {
760                                  'hitType': 'event',
761                                  'eventCategory': 'Protected Paths',
762                                  'eventAction': 'Final page viewed',
763                                  'eventLabel': window.location.pathname
764                                });
765                                ga('tracker2.send', {
766                                  'hitType': 'event',
767                                  'eventCategory': 'Protected Paths',
768                                  'eventAction': 'Final page viewed',
769                                  'eventLabel': window.location.pathname
770                                });
771                            }
772                        }
773                        
774                        var window_hash = (typeof window.location.hash != 'undefined' ? window.location.hash : '');
775                        
776                        history.replaceState({}, null, window.location.pathname + window.location.search + window_hash);
777                        
778                        
779                        setTimeout(function() {
780                            if (window.protected_paths_html && typeof window.protected_paths_html != 'undefined') {
781                                console.log('protected path - replace content with html');
782                                
783                                $('#container').replaceWith(window.protected_paths_html);
784                                $(container_els).attr('aria-hidden', 'false').show();
785                                $('#container').show();
786                                
787                                var protection_type_id = typeof $('input[name="protection_type_id"]').val() != 'undefined' ? $('input[name="protection_type_id"]').val() : '';
788                                
789                                window.protected_paths_show_content_after(protection_type_id
789);
790                                window.protected_paths_login_after(protection_type_id);
791                                
792                                if(window.galleries) {
793                                    window.galleries.init();
794                                }
795                                if(window.feature_panels) {
796                                    window.feature_panels.init();
797                                }
798                                if(window.cart) {
799                                    window.cart.init();
800                                }
801                                if(window.site) {
802                                    window.site.init();
803                                }
804                                if (window.archimedes && typeof window.archimedes != 'undefined') {
805                                    window.archimedes.archimedes_core.init();
806                                }
807                                
808                                $('body').removeClass('protected-path-login-mode fullscreen-slide-dark fullscreen-slide-light type-fullscreen');
809                                $('#protected_path_login').addClass('protected_path_hide');
810                                
811                                $(window).trigger('resize');
812                                $(window).trigger('scroll');
813                                
814                                setTimeout(function() {
815                                    $('#protected_path_login').remove();
816                                    $('#protected_path_login').removeClass('protected_path_loading protected_path_verifying_status');
817                                }, (instant ? 0 : 1000));
818                            } else {
819                                console.log('protected path - reload - no html');
820                                location.reload();
821                            }
822                            console.log('protected path - reset window.protected_paths_html');
823                            window.protected_paths_html = false;
824                        }, (instant ? 0 : 1000));
825                    }
826                },
827                
828                check_status: function() {
829                    var protected_path_data = localStorage.getItem('protected_paths_access') ? JSON.parse(localStorage.getItem('protected_paths_access')) : false;
830                    var check_type = 'localstorage'; // Options: 'localstorage', 'backend'
831                    if (protected_path_data) {    
832                        if (typeof protected_path_data[$('#protected_path_login_form').attr('data-protected-path-id')] != 'undefined') {
833                            
834                            var access_token = protected_path_data[$('#protected_path_login_form').attr('data-protected-path-id')]['access_token'];
835                            var private_area_access_key = protected_path_data[$('#protected_path_login_form').attr('data-protected-path-id')]['private_area_access_key'];
836                            var uid = protected_path_data[$('#protected_path_login_form').attr('data-protected-path-id')]['uid'];
837                            
838                            if (check_type == 'localstorage') {
839                                // Check private area access key against localstorage 
840                                if (private_area_access_key == $('#protected_path_login_form input[name="ppl_ak"]').val()) {
841                                    $(container_els).attr('aria-hidden', 'false').show();
842                                    $('#container').show();
843                                    $('body').removeClass('protected-path-login-mode fullscreen-slide-dark fullscreen-slide-light type-fullscreen');
844                                    $(window).trigger('resize');
845                                    $(window).trigger('scroll');
846                                    $('#protected_path_login').remove();
847                                    
848                                    if ($('#cachedata').length) {
849                                        // Fetch CryptoJS script
850                                        if(typeof window.CryptoJS == 'undefined') {
851                                            console.log('Fetching CryptoJS script')
852                                            $.ajax({
853                                                url: '/lib/js/crypto-js/crypto-js.js',
854                                                async: false,
855                                                dataType: "script"
856                                            });
857                                        }
858                                        var cacheData = $('#cachedata').val();
859                                        key = private_area_access_key;
860                                        // Decode the base64 cacheData so we can separate iv and crypt text.
861                                        var rawData = atob(cacheData);
862                                        var iv = rawData.slice(0,16);
863                                        var crypttext = rawData.slice(16);
864                                        // Decrypt...
865                                        var plaintextArray = CryptoJS.AES.decrypt(
866                                            { ciphertext: CryptoJS.enc.Latin1.parse(crypttext) },
867                                            CryptoJS.enc.Latin1.parse(key),
868                                            { iv: CryptoJS.enc.Latin1.parse(iv) }
869                                        );
870                                        var decrypted_txt = CryptoJS.enc.Utf8.stringify(plaintextArray);
871                                        console.log(decrypted_txt)
872                                        if (decrypted_txt.length) {
873                                            // debugger;
874                                            $('#cachedata').replaceWith(decrypted_txt);
875                                            this.after_decrypted_content_added();
876                                        }
877                                    }
878
879                                    if (window.gtag) {
880                                        gtag('event', 'protected_paths_final_page_viewed', {});
881                                    }
882                                    
883                                    if (window.ga) {
884                                        ga('send', {
885                                          'hitType': 'event',
886                                          'eventCategory': 'Protected Paths',
887                                          'eventAction': 'Final page viewed',
888                                          'eventLabel': window.location.pathname
889                                        });
890                                        ga('tracker2.send', {
891                                          'hitType': 'event',
892                                          'eventCategory': 'Protected Paths',
893                                          'eventAction': 'Final page viewed',
894                                          'eventLabel': window.location.pathname
895                                        });
896                                    }
897                                    
898                                } else {
899                                    window.archimedes.archimedes_core.protected_paths.hide_content();
900                                }
901                            } else {
902                                // Check user's access token in the backend
903
904                                var data = {
905                                    'access_token': access_token,
906                                    'path': window.location.pathname  
907                                };
908                                
909                                if (uid) {
910                                    $('#protected_path_login').addClass('protected_path_loading protected_path_checking_status');
911                                    $.ajax({
912                                        type: "POST",
913                                        data: data,
914                                        dataType: 'json',
915                                        url: '/modules/protected_paths/check_status/' + uid + '/', // Previously requested from /api/protected_paths/
916                                        success: function(data) {
917                                            if (data.success) {
918                                                // Success
919                                                setTimeout(function() {
920                                                    $(container_els).attr('aria-hidden', 'false').show();
921                                                    $('#container').show();
922                                                    $('body').removeClass('protected-path-login-mode fullscreen-slide-dark fullscreen-slide-light type-fullscreen');
923                                                    $('#protected_path_login').addClass('protected_path_hide');
924                                                
925                                                    $(window).trigger('resize');
926                                                    $(window).trigger('scroll');
927
928                                                    if (window.gtag) {
929                                                        gtag('event', 'protected_paths_final_page_viewed', {});
930                                                    }
931                                                    
932                                                    if (window.ga) {
933                                                        ga('send', {
934                                                          'hitType': 'event',
935                                                          'eventCategory': 'Protected Paths',
936                                                          'eventAction': 'Final page viewed',
937                                                          'eventLabel': window.location.pathname
938                                                        });
939                                                        ga('tracker2.send', {
940                                                          'hitType': 'event',
941                                                          'eventCategory': 'Protected Paths',
942                                                          'eventAction': 'Final page viewed',
943                                                          'eventLabel': window.location.pathname
944                                                        });
945                                                    }
946                                    
947                                                    setTimeout(function() {
948                                                        $('#protected_path_login').remove();
949                                                        $('#protected_path_login').removeClass('protected_path_loading protected_path_checking_status');
950                                                    }, 1000);
951                                                }, 1000);
952                                            } else {
953                                                // Error
954                                                $('#protected_path_login').removeClass('protected_path_loading protected_path_checking_status');
955                                            }
956                                        },
957                                        error: function(data) {
958                                            h.alert('<h2>Sorry</h2>An unknown error occurred.');
959                                            $('#protected_path_login').removeClass('protected_path_loading protected_path_checking_status');
960                                        }
961                                    });
962                                }
963                            }
964                        } else {
965                            window.archimedes.archimedes_core.protected_paths.hide_content();
966                        }
967                    } else {
968                        window.archimedes.archimedes_core.protected_paths.hide_content();
969                    }
970                },
971
972                hide_content: function() {
973                    
974                    $(container_els).attr('aria-hidden', 'true').hide();
975                    $('body').addClass('protected-path-login-mode type-fullscreen');
976                    if ($('#protected_path_login').hasClass('protected_path_frontend_form_has_background')) {
977                        $('body').addClass('fullscreen-slide-dark');
978                    } else {
979                        $('body').addClass('fullscreen-slide-light');
980                    }
981                    container_hidden = true;
982
983                    if (window.gtag) {
984                        gtag('event', 'protected_paths_login_data_capture_form_shown', {});
985                    }
986                    
987                    if (window.ga) {
988                        ga('send', {
989                          'hitType': 'event',
990                          'eventCategory': 'Protected Paths',
991                          'eventAction': 'Login / Data Capture form shown',
992                          'eventLabel': window.location.pathname
993                        });
994                        ga('tracker2.send', {
995                          'hitType': 'event',
996                          'eventCategory': 'Protected Paths',
997                          'eventAction': 'Login / Data Capture form shown',
998                          'eventLabel': window.location.pathname
999                        });
1000                    }
1001                },
1002
1003                decrypt: function(cacheDataElement, salt, callback) {
1004
1005                    if (!$(cacheDataElement).val()) {
1006                        console.error('Decrypt() requires an input field with the value attr containing the encrypted content string.')
1007                        return;
1008                    }
1009
1010                    var result = {};
1011                    var cacheData = $(cacheDataElement).val();
1012                    
1013                    // Decode the base64 cacheData so we can separate iv and crypt text.
1014                    var rawData = atob(cacheData);
1015                    var iv = rawData.slice(0,16);
1016                    var crypttext = rawData.slice(16);
1017                    
1018                    // Decrypt...
1019                    var plaintextArray = CryptoJS.AES.decrypt(
1020                        { ciphertext: CryptoJS.enc.Latin1.parse(crypttext) },
1021                        CryptoJS.enc.Latin1.parse(salt),
1022                        { iv: CryptoJS.enc.Latin1.parse(iv) }
1023                    );
1024                    
1025                    try {
1026                        var decryptedContent = CryptoJS.enc.Utf8.stringify(plaintextArray);
1027                    } catch (e) {
1028                        var decryptedContent = ''
1029                    }
1030                    
1031                    if (!callback) {
1032                        return decryptedContent;
1033                    } else if (typeof callback == 'function') {
1034                        return callback(cacheDataElement, salt, (decryptedContent || ''));
1035                    }
1036                            
1037                },
1038
1039                decrypt_frontend_callback: function(cacheDataElement, salt, decryptedContent) {
1040
1041                    // if it has the magic comment update the site
1042                    if (decryptedContent.indexOf('protected page access allowed') > 0) {
1043                        setTimeout(function() {
1044                            $('#protected_path_login').removeClass('protected_path_loading');
1045                            
1046                            // if (data.success) {
1047                                // Success
1048                                //h.alert('<h2>Thank you</h2>We have sent you an email with a link to access this page.');
1049                                
1050                                // window.archimedes.archimedes_core.analytics.track_campaigns.save_form_data($('#protected_path_login_form'));
1051                                
1052                                $('input, select, textarea', '#protected_path_login_form').each(function() {
1053                                    $(this).val(''); 
1054                                });
1055                               
1055 
1056                                // data.access_token, 'private_area_access_key': data.private_area_access_key, 'uid': data.uid
1057                                data = {};
1058                                data.access_token = salt;
1059                                data.private_area_access_key = salt;
1060                                data.uid = $(cacheDataElement).data('id');
1061
1062                                // Used by window.archimedes.archimedes_core.protected_paths.allow_access...
1063                                console.log('protected path - set window.protected_paths_html 1');
1064                                window.protected_paths_html = decrypted_txt;
1065                                
1066                                // if (data.instant_access && typeof data.instant_access != 'undefined') {
1067                                    $('#protected_path_login').addClass('protected_path_loading protected_path_verifying_status');
1068                                    window.archimedes.archimedes_core.protected_paths.allow_access(data);
1069                                // } else {
1070                                    // $('#protected_path_login').addClass('protected_path_check_email');
1071                                // }
1072                                
1073                            
1074                        }, 1000);
1075                    } else {
1076                        // else reset the form...
1077                        setTimeout(function() {
1078                            $('#protected_path_login').removeClass('protected_path_loading');
1079                            var message_heading = $('#protected_path_login[data-password-alert-heading]').length ? $('#protected_path_login[data-password-alert-heading]').attr('data-password-alert-heading') : 'Sorry';
1080                            var message_content = $('#protected_path_login[data-password-alert-content]').length ? $('#protected_path_login[data-password-alert-content]').attr('data-password-alert-content') : 'Your password is incorrect, please try again.';
1081                            h.alert('<h2>' + message_heading + '</h2>' + message_content);
1082                        }, 1000);
1083                    }
1084                    
1085
1086                },
1087                
1088                request_access: function(form_data) {
1089                    $('#protected_path_login').addClass('protected_path_loading');
1090                    var data = form_data;
1091                    // debugger;
1092                    data['content_type_data'] = JSON.stringify(window.archimedes.archimedes_core.gather_page_content(window.protected_paths_html));
1093                    
1094                    if (window.location.search != '') {
1095                        if (window.location.search.indexOf('_cmspreview_live_protectedpath=1') > -1) {
1096                            data['_cmspreview_live_protectedpath'] = '1';
1097                        }
1098                        if (window.location.search.indexOf('_cmspreview_protectedpath_datacapture=1') > -1) {
1099                            data['_cmspreview_protectedpath_datacapture'] = '1';
1100                        }
1101                        if (window.location.search.indexOf('_cmspreview_protectedpath_password=1') > -1) {
1102                            data['_cmspreview_protectedpath_password'] = '1';
1103                        }
1104                    }
1105                    
1106                    // Function globals
1107                    data_capture_active = Boolean($('input#pp_email').length);
1108                    password_protection_active = Boolean($('#cachedata').length && $('input#pp_password_cache_mode').length);
1109                    decryptContentGlobal = '';
1110                    
1111                    if ($('#cachedata').hasClass('frontend-mode')) {
1112                        // ***********************
1113                        // ***********************
1114                        // NEW: FRONTEND MODE
1115                        // ***********************
1116                        // ***********************
1117                        
1118                        // Kill switch active
1119                        if ($('.admin_protected_paths_encryption_kill_switch').length) {
1120                            // check password
1121                            
1122                        }
1123                        
1124                        /* Build the key...
1125                            get the path( make sure it has / at the end)
1126                         */
1127                        var path = $('#cachedata').data('path') || window.location.pathname || '';
1128                        if(!path.endsWith('/'))
1129                            path = path + '/';
1130                        
1131                        // get the refresh uid
1132                        var refreshUID = $('#cachedata').data('id');
1133                        
1134                        // get the password from the form data
1135                        var pw = data.password_cache_mode || '';
1136
1137                        // combine
1138                        var combined_data = [];
1139                        combined_data.push(path);
1140                        combined_data.push(refreshUID);
1141                        combined_data.push(pw);
1142                        var combined_data = btoa(JSON.stringify(combined_data).replace(/,/g,', ')); //base64 encode the json stringified array; 
1143                        
1144                        // Hash
1145                        var hash = CryptoJS.MD5(CryptoJS.enc.Latin1.parse(combined_data));
1146                        var key = hash.toString(CryptoJS.enc.Hex)
1147
1148                        // Setup fields to send to allow access function
1149                        data.uid = refreshUID;
1150                        data.private_area_access_key = key;
1151                        data.access_token = key;
1152                        
1153                        var pass_success = false;
1154
1155                        // 
1156                        // ATTEMPT TO DECRYPT
1157                        //
1158                        if ($('#cachedata').length) {
1159                            // var decryptedContent = this.decrypt('#cachedata', key, this.decrypt_frontend_callback);
1160                            var decryptedContent = this.decrypt('#cachedata', key);
1161
1162                            if (decryptedContent.indexOf('protected page access allowed') > 0) {
1163                                
1164                                // success
1165                                data.key = key;
1166                                if (!data_capture_active) {
1167                                    // Access given if email is not required
1168                                    window.archimedes.archimedes_core.protected_paths.allow_access(data, decryptedContent);
1169                                } else {
1170                                    // window.archimedes.archimedes_core.protected_paths.allow_access(data, decryptedContent);
1171                                    decryptContentGlobal = decryptedContent;
1172                                    pass_success = true;
1173                                }
1174
1175                                if (window.gtag) {
1176                                    gtag('event', 'protected_paths_login_data_capture_form', {
1177                                        'success': true,
1178                                    });
1179                                }
1180                                
1181                                if (window.ga) {
1182                                    ga('send', {
1183                                      'hitType': 'event',
1184                                      'eventCategory': 'Protected Paths',
1185                                      'eventAction': 'Login / Data Capture form completed succesfully',
1186                                      'eventLabel': window.location.pathname
1187                                    });
1188                                    ga('tracker2.send', {
1189                                      'hitType': 'event',
1190                                      'eventCategory': 'Protected Paths',
1191                                      'eventAction': 'Login / Data Capture form completed succesfully',
1192                                      'eventLabel': window.location.pathname
1193                                    });
1194                                }
1195                                    
1196                            } else {
1197                                
1198                                // fail
1199                                setTimeout(function() {
1200                                    $('#protected_path_login').removeClass('protected_path_loading');
1201                                    var message_heading = $('#protected_path_login[data-password-alert-heading]').length ? $('#protected_path_login[data-password-alert-heading]').attr('data-password-alert-heading') : 'Sorry';
1202                                    var message_content = $('#protected_path_login[data-password-alert-content]').length ? $('#protected_path_login[data-password-alert-content]').attr('data-password-alert-content') : 'Your password is incorrect, please try again.';
1203                                    h.alert('<h2>' + message_heading + '</h2>' + message_content);
1204                                }, 1000);
1205
1206                                if (window.gtag) {
1207                                    gtag('event', 'protected_paths_login_data_capture_form', {
1208                                        'success': false,
1209                                    });
1210                                }
1211                                
1212                                if (window.ga) {
1213                                    ga('send', {
1214                                      'hitType': 'event',
1215                                      'eventCategory': 'Protected Paths',
1216                                      'eventAction': 'Login / Data Capture form password verification failed',
1217                                      'eventLabel': window.location.pathname
1218                                    });
1219                                    ga('tracker2.send', {
1220                                      'hitType': 'event',
1221                                      'eventCategory': 'Protected Paths',
1222                                      'eventAction': 'Login / Data Capture form password verification failed',
1223                                      'eventLabel': window.location.pathname
1224                                    });
1225                                }
1226                                
1227                                // Stop the process going any further
1228                                return;
1229                            }
1230
1231                        }
1232                    } 
1233
1234                    /* Previously this was an else block */
1235                    if (data_capture_active) {
1236                        // Back end mode.
1237                        
1238                        if ($('#protected_path_login').hasClass('protected_path_log_interactions')) {
1239                            var related_record_id = typeof $('#protected_path_login').attr('data-related-record-id') != 'undefined' ? $('#protected_path_login').attr('data-related-record-id') : '';
1240                            var related_record_type = typeof $('#protected_path_login').attr('data-related-record-type') != 'undefined' ? $('#protected_path_login').attr('data-related-record-type') : '';
1241                            var interaction_data = {
1242                                'type': 'Data capture signup',
1243                                'viewing_room_id': related_record_type == 'viewing_rooms' && related_record_id ? related_record_id : '',
1244                                'viewing_room_label': related_record_type == 'viewing_rooms' && related_record_id ? (typeof form_data.page_title != 'undefined' ? form_data.page_title : '') : '',
1245                                'firstname': typeof form_data.first_name != 'undefined' ? form_data.first_name : '',
1246                                'lastname': typeof form_data.last_name != 'undefined' ? form_data.last_name : '',
1247                                'organisation': typeof form_data.organisation != 'undefined' ? form_data.organisation : '',
1248                                'country': typeof form_data.country != 'undefined' ? form_data.country : '',
1249                                'telephone': typeof form_data.phone != 'undefined' ? form_data.phone : ''
1250                            };
1251                            window.archimedes.archimedes_core.interactions.log(interaction_data);
1252                        }
1253                            
1254                        if ($('#protected_path_login').hasClass('protected_path_fallback_mode_enabled')) {
1255                            var appname = $('#protected_path_login').attr('data-appname') && typeof $('#protected_path_login').attr('data-appname') != 'undefined' ? $('#protected_path_login').attr('data-appname') : 'unknown';
1256                            $.ajax({
1257                                type: "POST",
1258                                data: data,
1259                                dataType: 'json',
1260                                url: 'https://datastore.artlogic.net/' + appname + '/datacapture',
1261                                success: function(data) {
1262                                    console.log('PP datastore success');
1263                                },
1264                                error: function(data) {
1265                                    console.log('PP datastore failed');
1266                                }
1267                            });
1268                            
1269                            protected_path_fallback_mode_timeout = setTimeout(function() {
1270                                console.log('PP fallback entry mode');
1271                                data = {
1272                                    'private_area_access_key': $('#protected_path_login_form input[name="ppl_ak"]').val(), 
1273                                    'access_token': 'instant_access', 
1274                                    'uid': 'instant_access'
1275                                }
1276                                window.archimedes.archimedes_core.analytics.track_campaigns.save_form_data($('#protected_path_login_form'));
1277                                
1278                                // Don't call this if it's already been called 
1278in password cache
1279                                if (!$('input#pp_password_cache_mode').length && !password_protection_active) {
1280                                    window.archimedes.archimedes_core.protected_paths.allow_access(data, false, true);
1281                                }
1282                                
1283                                if (typeof protected_path_xhr != 'undefined') {
1284                                    protected_path_xhr.abort();
1285                                }
1286                                if (typeof protected_path_xhr_success_timeout != 'undefined') {
1287                                    clearTimeout(protected_path_xhr_success_timeout);
1288                                }
1289
1290                                if (window.gtag) {
1291                                    gtag('event', 'protected_paths_login_data_capture_form', {
1292                                        'success': true,
1293                                    });
1294                                    gtag('event', 'protected_paths_login_data_capture_form_fallback_entry', {});
1295                                }
1296                                
1297                                if (window.ga) {
1298                                    ga('send', {
1299                                      'hitType': 'event',
1300                                      'eventCategory': 'Protected Paths',
1301                                      'eventAction': 'Login / Data Capture form completed succesfully',
1302                                      'eventLabel': window.location.pathname
1303                                    });
1304                                    ga('send', {
1305                                      'hitType': 'event',
1306                                      'eventCategory': 'Protected Paths',
1307                                      'eventAction': 'Login / Data Capture form - fallback entry mode',
1308                                      'eventLabel': window.location.pathname
1309                                    });
1310                                    ga('tracker2.send', {
1311                                      'hitType': 'event',
1312                                      'eventCategory': 'Protected Paths',
1313                                      'eventAction': 'Login / Data Capture form completed succesfully',
1314                                      'eventLabel': window.location.pathname
1315                                    });
1316                                    ga('tracker2.send', {
1317                                      'hitType': 'event',
1318                                      'eventCategory': 'Protected Paths',
1319                                      'eventAction': 'Login / Data Capture form - fallback entry mode',
1320                                      'eventLabel': window.location.pathname
1321                                    });
1322                                }
1323                            }, 10000, data);
1324                        }
1325
1326                        var ppl_ak_elem = document.querySelector('#ppl_ak');
1327                        var use_artlogicdatacapture = ppl_ak_elem && ppl_ak_elem.getAttribute('data-use_artlogicdatacapture') == 'true';
1328                        if (use_artlogicdatacapture) {
1329                            var site_name = ppl_ak_elem.getAttribute('data-site_name') || '';
1330                            var protected_path_xhr_url = 'https://datacapture.artlogic.net/'+site_name+'/website-visitor-data';
1331                        } else {
1332                            var protected_path_xhr_url = '/modules/protected_paths/request_access/'; // Previously requested from /api/protected_paths/
1333                        }
1334                        protected_path_xhr = $.ajax({
1335                            type: "POST",
1336                            data: data,
1337                            dataType: 'json',
1338                            url: protected_path_xhr_url,
1339                            success: function(data) {
1340                                if (typeof protected_path_fallback_mode_timeout != 'undefined') {
1341                                    clearTimeout(protected_path_fallback_mode_timeout);
1342                                }
1343                                if (typeof protected_path_xhr_success_timeout != 'undefined') {
1344                                    clearTimeout(protected_path_xhr_success_timeout);
1345                                }
1346                                protected_path_xhr_success_timeout = setTimeout(function() {
1347                                    
1348                                    $('#protected_path_login').removeClass('protected_path_loading');
1349                                    
1350                                    if (data.success) {
1351                                        // Success
1352                                        //h.alert('<h2>Thank you</h2>We have sent you an email with a link to access this page.');
1353                                        window.archimedes.archimedes_core.analytics.track_campaigns.save_form_data($('#protected_path_login_form'));
1354                                        
1355                                        $('input, select, textarea', '#protected_path_login_form').not(':hidden').each(function() {
1356                                            $(this).val(''); 
1357                                        });
1358
1359                                        if (use_artlogicdatacapture) {
1360                                            data.instant_access = true;
1361                                            data.private_area_access_key = ppl_ak_elem.value;
1362                                        }
1363
1364                                        console.log(data);
1365
1366                                        if (data.instant_access && typeof data.instant_access != 'undefined') {
1367                                            
1368                                            if(!$('input#pp_password_cache_mode').length && !password_protection_active) {
1369                                                /*
1370                                                    BLOCK ACCESS IF PASSWORD FIELD IS ACTIVE. 
1371                                                    Because the handler for the password field has already
1372                                                        called the allow_access function.
1373                                                */
1374                                                console.log('allow accesss')
1375                                                $('#protected_path_login').addClass('protected_path_loading protected_path_verifying_status');
1376                                                window.archimedes.archimedes_core.protected_paths.allow_access(data);
1377                                            }
1378
1379                                        } else {
1380                                            $('#protected_path_login').addClass('protected_path_check_email');
1381                                        }
1382
1383                                        if (window.gtag) {
1384                                            gtag('event', 'protected_paths_login_data_capture_form', {
1385                                                'success': true,
1386                                            });
1387                                        }
1388                                        
1389                                        if (window.ga) {
1390                                            ga('send', {
1391                                              'hitType': 'event',
1392                                              'eventCategory': 'Protected Paths',
1393                                              'eventAction': 'Login / Data Capture form completed succesfully',
1394                                              'eventLabel': window.location.pathname
1395                                            });
1396                                            ga('tracker2.send', {
1397                                              'hitType': 'event',
1398                                              'eventCategory': 'Protected Paths',
1399                                              'eventAction': 'Login / Data Capture form completed succesfully',
1400                                              'eventLabel': window.location.pathname
1401                                            });
1402                                        }
1403                                        
1404                                    } else {
1405                                        // Error
1406                                        
1407                                        // Mute error if password warning is already showing
1408                                        if (!$('input#pp_password_cache_mode').length || ($('input#pp_password_cache_mode').length && pass_success)) {
1409                                            h.alert('<h2>Sorry</h2>Please fill in all the required fields and ensure your email address is valid.');
1410                                        }
1411
1412                                        if (window.gtag) {
1413                                            gtag('event', 'protected_paths_login_data_capture_form', {
1414                                                'success': false,
1415                                            });
1416                                        }
1417                                        
1418                                        if (window.ga) {
1419                                            ga('send', {
1420                                              'hitType': 'event',
1421                                              'eventCategory': 'Protected Paths',
1422                                              'eventAction': 'Login / Data Capture form verification failed',
1423                                              'eventLabel': window.location.pathname
1424                                            });
1425                                            ga('tracker2.send', {
1426                                              'hitType': 'event',
1427                                              'eventCategory': 'Protected Paths',
1428                                              'eventAction': 'Login / Data Capture form verification failed',
1429                                              'eventLabel': window.location.pathname
1430                                            });
1431                                        }
1432                                        
1433                                    }
1434                                }, 500);
1435                            },
1436                            error: function(data) {
1437                                if (typeof protected_path_fallback_mode_timeout == 'undefined') {
1438                                    h.alert('<h2>Sorry</h2>An unknown error occurred.');
1439                                }
1440                                $('#protected_path_login').removeClass('protected_path_loading');
1441                            }
1442                        });
1443
1444                        if (password_protection_active && data_capture_active && pass_success) {
1445                            // Run the password version of allow access. So access is dependant on the correct password,
1446                            //     not a valid email address.
1447                            console.info('DC and PW')
1448                            window.archimedes.archimedes_core.protected_paths.allow_access(data, decryptContentGlobal);
1449                        }
1450
1451                    }
1452                    
1453                    
1454                },
1455
1456                decrypt_content: function(cacheData) {
1457                    
1458                    // Decrypt page content if it's there...
1459                    if (localStorage.getItem('protected_paths_access')) {
1460                        // If protected_paths storage already exists
1461                        var protected_paths_data = JSON.parse(localStorage.getItem('protected_paths_access'));
1462                    }
1463                    key = protected_paths_data.private_area_access_key;
1464                    console.log(protected_paths_data)
1465                    console.log(cacheData)
1466                    console.log(key)
1467                    
1468                    // Decode the base64 cacheData so we can separate iv and crypt text.
1469                    var rawData = atob(cacheData);
1470                    var iv = rawData.slice(0,16);
1471                    var crypttext = rawData.slice(16);
1472                    
1473                    // Decrypt...
1474                    var plaintextArray = CryptoJS.AES.decrypt(
1475                        { ciphertext: CryptoJS.enc.Latin1.parse(crypttext) },
1476                        CryptoJS.enc.Latin1.parse(key),
1477                        { iv: CryptoJS.enc.Latin1.parse(iv) }
1478                    );
1479                    var decrypted_txt = CryptoJS.enc.Utf8.stringify(plaintextArray);
1480                    return decrypted_txt
1481                    
1482                }
1483                
1484            },
1485            
1486            gather_page_content: function(html) {
1487                // Gather all tags marked with 'data-ct-*' and form them into an object to be used throughout the core
1488                // This should be used in protected paths, so we can track the individual privateviews UIDs
1489                // Attributes format: data-ct-name="" data-ct-type="" data-ct-value=""
1490                
1491                // If html has been supplied, gather from the supplied html. Otherwise gather from the content of the page (default).
1492                if (html && typeof html != 'undefined') {
1493                    var context = $.parseHTML(html);
1494                } else {
1495                    var context = 'body';
1496                }
1497                
1498                content_tracking_data = [];
1499                $('*[data-ct-name]', context).each(function() {
1500                    var name = $(this).attr('data-ct-name') && typeof $(this).attr('data-ct-name') != 'undefined' ? $(this).attr('data-ct-name') : '';
1501                    var type = $(this).attr('data-ct-type') && typeof $(this).attr('data-ct-type') != 'undefined' ? $(this).attr('data-ct-type') : '';
1502                    var value = $(this).attr('data-ct-value') && typeof $(this).attr('data-ct-value') != 'undefined' ? $(this).attr('data-ct-value') : '';
1503                    var element_tracking_object = {
1504                        'content_name': name, 
1505                        'content_type': type, 
1506                        'content_value': value
1507                    };
1508                    content_tracking_data.push(element_tracking_object);
1509                });
1510                return content_tracking_data;
1511            },
1512
1513            analytics: {
1514
1515                init: function() {
1516                    this.track_campaigns.init();
1517
1518                    /* Google Analytics tracking */
1519                    if (window._gaq || window.ga || window.gtag) {
1520                        $('a.analytics, .analytics a').unbind('click.analytics-anchors').bind('click.analytics-anchors',function(e) {
1521                            if ($(this).attr('target') != '_blank') {
1522                                e.preventDefault();
1523                            }
1524                            var href = $(this).attr('href');
1525                            var target = $(this).attr('target');
1526                            var path = href.replace(location.protocol + '//' + location.host, '');
1527                            var eventLabel = $(this).text().trim() || 'user action on ' + path;
1528
1529
1530                            if (window.gtag){
1531                                // Modern Google Analytics 4 (https://developers.google.com/tag-platform/gtagjs/reference#event)
1532                                gtag('event', 'site_link_click', {
1533                                    'event_category': 'link_engagement',                                    
1534                                    'event_label': eventLabel,
1535                                    'link_url': path
1536                                });
1537                            } else if (window.ga) {
1538                                // Universal analytics
1539                                // Also corrected legacy ga.js to send an EVENT and not a pageview
1540                                ga('send', 'event', {
1541                                    eventCategory: 'link_engagement',
1542                                    eventAction: 'site_link_click',
1543                                    eventLabel: eventLabel
1544                                });
1545                                // Send tracker2 pageview if a secondary tracker has been installed
1546                                ga('tracker2.send', 'event', {
1547                                    eventCategory: 'link_engagement',
1548                                    eventAction: 'site_link_click',
1549                                    eventLabel: eventLabel
1550                                });
1551                                ga('artlogic_tracker.send', 'event', {
1552                                    eventCategory: 'link_engagement',
1553                                    eventAction: 'site_link_click',
1554                                    eventLabel: eventLabel
1555                                });
1556                            } else if (window._gaq) {
1557                                // Legacy analytics
1558                                _gaq.push(['_trackPageview', path]);
1559                            }
1560                            if (target != '_blank') {
1561                                setTimeout(function() {
1562                                    location = e.href;
1563                                    document.location.href = href;
1564                                }, 200);
1565                            }
1566                        });
1567
1568                        // Track all documents automatically in Google Analytics
1569                        if ($('body').hasClass('analytics-track-documents') || $('body').hasClass('analytics-track-all-links')) {
1570                            $('a').each(function() {
1571                                // Dont track this link if it is already being tracked using the .analytics class
1572                                if (!$(this).hasClass('analytics') && !$(this).parent().hasClass('analytics')) {
1573                                    // Check that this link is a document
1574                                    var element_href = $(this).attr('href');
1575                                    if (element_href) {
1576                                        if (element_href.substr(-4)[0] == '.' || element_href.substr(-5)[0] == '.' || element_href.substr(-6)[0] == '.') {
1577                                            $(this).unbind('click.analytics-track-documents-and-links').bind('click.analytics-track-documents-and-links', function() {
1578                                                if (window._gaq || window.ga) {
1579                                                    if ($(this).attr('href')) {
1580                                                        var href = $(this).attr('href');
1581                                                        var path = href.replace(location.protocol + '//' + location.host, '');
1582                                                        if (window.ga) {
1583                                                            // Universal analytics
1584                                                            ga('send', 'pageview', path);
1585                                                        } else if (window._gaq) {
1586                                                            // Legacy analytics
1587                                                            _gaq.push(['_trackPageview', path]);
1588                                                        }
1589                                                    }
1590                                                    if (!$(this).hasClass('external') && !$(this).attr('target') == '_blank') {
1591                                                        setTimeout(function() {
1592                                                            document.location.href = href;
1593                                                        }, 200);
1594                                                    }
1595                                                }
1596                                            });
1597                                        }
1598                                    }
1599                                }
1600                            });
1601                        }
1602
1603                        // Track all external links automatically in Google Analytics
1604                        if ($('body').hasClass('analytics-track-all-links')) {
1605                            $('a').each(function() {
1606                                // Dont track this link if it is already being tracked using the .analytics class
1607                                if (!$(this).hasClass('analytics') && !$(this).parent().hasClass('analytics')) {
1608                                    // Check that this link is a document
1609                                    var element_href = $(this).attr('href');
1610                                    if (element_href) {
1611                                        if (element_href.substr(0,7) == 'http://' || element_href.substr(0,8) == 'https://') {
1612                                            $(this).click(function() {
1613                                                if (window._gaq || window.ga) {
1614                                                    var href = $(this).attr('href');
1615                                                    var path = href;
1616                                                    if (window.ga) {
1617                                                        // Universal analytics
1618                                                        ga('send', 'event', {
1619                                                          'eventCategory': 'External link click',
1620                                                          'eventAction': path
1621                                                        });
1622                                                        ga('tracker2.send', 'event', {
1623                                                          'eventCategory': 'External link click',
1624                                                          'eventAction': path
1625                                                        });
1626                                                    } else if (window._gaq) {
1627                                                        // Legacy analytics
1628                                                        _gaq.push(['_trackPageview', path]);
1629                                                    }
1630                                                }
1631                                            });
1632                                        }
1633                                    }
1634                                }
1635                            });
1636                        }
1637                    }
1638
1639                },
1640                facebook_pixels: {
1641                    
1642                    init: function() {
1643                        
1644                    },
1645                    
1646                    grant_consent: function(consent) {
1647                        if(typeof fbq !== 'undefined'){
1648                            fbq('consent', 'grant');
1649                        }
1650                    },
1651                    revoke_consent: function(consent) {
1652                        if(typeof fbq !== 'undefined'){
1653                            fbq('consent', 'revoke');
1654                        }
1655                    }
1656                },
1657                
1658                track_campaigns: {
1659
1660                    init: function() {
1661                        
1662                        var cookie_management_preferences_enabled = false;
1663                        var marketing_consent_given = false;
1664                        var cookie_notification_mode = $('#cookie_notification').attr('data-mode');
1665                        
1666                        // In some places we use #custom_cookie_notification instead of #cookie_notification where we want to differentiate from core code (ArtlogicAspect)
1667                        if (typeof cookie_notification_mode == 'undefined' && $('#custom_cookie_notification').length) {
1668                            cookie_notification_mode = $('#custom_cookie_notification').attr('data-mode');
1669                        }
1670                        
1671                        if (cookie_notification_mode == 'consent' && window.archimedes && window.archimedes.archimedes_core && typeof window.archimedes.archimedes_core.cookie_notification.get_cookie_preference === "function") {
1672                            cookie_management_preferences_enabled = true;
1673                            marketing_consent_given = window.archimedes.archimedes_core.cookie_notification.get_cookie_preference('marketing');
1674                        } else if (cookie_notification_mode == 'consent' && window.site && window.site.cookie_notification && typeof window.site.cookie_notification.get_cookie_preference === "function") {
1675                            cookie_management_preferences_enabled = true;
1676                            marketing_consent_given = window.site.cookie_notification.get_cookie_preference('marketing');
1677                        }
1678
1679                        if (cookie_management_preferences_enabled) {
1680                            if (marketing_consent_given) {
1681                                allow_campaign_tracking = true;
1682                            } else {
1683                                allow_campaign_tracking = false;
1684                            }
1685                        } else {
1686                            // If no cookie management options are available on the site, we allow tracking by default.
1687                            allow_campaign_tracking = true;
1688                        }
1689                        
1690                        if (allow_campaign_tracking) {
1691                            this.track();
1692                            this.set_forms();
1693                        }
1694                    },
1695
1696                    track: function() {
1697
1698
1699                        // Track campaign information in localstorage so it can be used elsewhere on the site
1700
1701                        if (allow_campaign_tracking && g.has_local_storage_test() && typeof localStorage != 'undefined') {
1702                                
1703                            var visit_expired = false;
1704                            if (localStorage.getItem('campaign_tracking_info_timestamp')) {
1705                                var campaign_tracking_minutes_active = (parseInt(Date.now() - localStorage.getItem('campaign_tracking_info_timestamp')) / (1000*60)); //milliseconds to days
1706                                var expiry_minutes = 60;
1707                                if (campaign_tracking_minutes_active > expiry_minutes) {
1708                                    var visit_expired = true
1709                                }
1710                            }
1711                            
1712                            // Set current time as timestamp
1713                            localStorage.setItem('campaign_tracking_info_timestamp', new Date().getTime());
1714                            
1715                            var new_visitor = (localStorage.getItem('campaign_tracking_info') ? false : true);
1716                            var google_campaign_exists = false;
1717                            var search_string = window.location.search;
1718                            var landing_page = window.location.href.split('?')[0];
1719                            var internal_referrer = false;
1720                            
1721                            var referrer_url = 'Unknown';   
1722                            if (typeof document.referrer != 'undefined' && typeof document.location.protocol != 'undefined' && typeof document.location.hostname != 'undefined') {
1723                                if (document.referrer) {
1724                                    if (document.referrer.indexOf(document.location.protocol + '//' + document.location.hostname) < 0) {
1725                                        var referrer_url = document.referrer;
1726                                    } else {
1727                                        var referrer_url = 'Internal';
1728                                        var internal_referrer = true;
1729                                    }
1730                                } else if (document.referrer == '') {
1731                                    var referrer_url = 'Organic';
1732                                } else {
1733                                    var referrer_url = 'Unknown';   
1734                                }
1735                            }
1736
1737                            if (search_string && typeof search_string != 'undefined') {
1738                                if (search_string.indexOf('utm_campaign') > -1) {
1739                                    var google_campaign_exists = true;
1740                                }
1741                            }
1742
1743                            var track_new_visit = (google_campaign_exists || new_visitor || (visit_expired && !internal_referrer));
1744
1745                            if (track_new_visit) {
1746                                // Visits are tracked and/or reset if there are google campaign params or if this is a new visit
1747                                
1748                                var campaign_info_existing = localStorage.getItem('campaign_tracking_info');
1749                                if (campaign_info_existing && typeof campaign_info_existing != 'undefined') {
1750                                    // Always retain previous data apart from keys which need to be reset
1751                                    var campaign_info = JSON.parse(campaign_info_existing);
1752                                    campaign_info['landing_page'] = '';
1753                                    campaign_info['referrer_url'] = '';
1754                                } else {
1755                                    var campaign_info = {};
1756                                }
1757                                
1758                                // Set landing page and referrer
1759                                campaign_info['landing_page'] = landing_page;
1760                                campaign_info['referrer_url'] = referrer_url;
1761                                
1762                                if (google_campaign_exists) {
1763                                    var params = search_string.replace('?', '').split('&');
1764                                    
1765                                    campaign_info['utm_source'] = '';
1766                                    campaign_info['utm_medium'] = '';
1767                                    campaign_info['utm_campaign'] = '';
1768                                    campaign_info['utm_term'] = '';
1769                                    campaign_info['utm_content'] = '';
1770                                    
1771                                    // Store each of the params
1772                                    $.each(params, function(i, param) {
1773                                        var param_split = param.split('=');
1774                                        var param_name = param_split[0];
1775                                        var param_value = param_split[1];
1776                                        campaign_info[param_name] = param_value;
1777                                    });
1778                                }
1779                                // Set the info in localStorage
1780                                localStorage.setItem('campaign_tracking_info', JSON.stringify(campaign_info));
1781                            }
1782                        }
1783                               
1784                    },
1785
1786                    get_user_data: function() {
1787
1788                        // Get information already set for this user
1789
1790                        if (g.has_local_storage_test() && typeof localStorage != 'undefined') {
1791                            var tracking_info = localStorage.getItem('campaign_tracking_info');
1792                            if (tracking_info) {
1793                                var tracking_info_map = JSON.parse(tracking_info);
1794                                if (typeof tracking_info_map.user_data != 'undefined') {
1795                                    return tracking_info_map.user_data;
1796                                } else {
1797                                    return {};
1798                                }
1799                            } else {
1800                                return {};
1801                            }
1802                        }
1803                    },
1804
1805                    save_form_data: function(form_instance) {
1806
1807                        // Grab user data from form
1808
1809                        var form_instance = form_instance && typeof form_instance != 'undefined' ? form_instance : false;
1810
1811                        if (allow_campaign_tracking && g.has_local_storage_test() && typeof localStorage != 'undefined' && form_instance) {
1812
1813                            // Find existing tracking data
1814                            var tracking_info = localStorage.getItem('campaign_tracking_info');
1815                            if (!tracking_info) {
1816                                var tracking_info = '{}';
1817                            }
1818                            var tracking_info_map = JSON.parse(tracking_info);
1819
1820                            // Create user data key if it doesn't exist
1821                            if (!tracking_info_map.user_data || typeof tracking_info_map.user_data == 'undefined') {
1822                                tracking_info_map['user_data'] = {};
1823                            }
1824
1825                            // Find information in the form
1826                            var field_aliases = {
1827                                'f_name': 'name',
1828                                'f_first_name': 'first_name',
1829                                'f_last_name': 'last_name',
1830                                'f_email': 'email',
1831                                'f_phone': 'phone',
1832                                'cart_user_email': 'email',
1833                                'cart_user_phone': 'phone',
1834                                'customer_email': 'email',
1835                                'customer_phone': 'phone',
1836                                'customer_first_name': 'first_name',
1837                                'customer_last_name': 'last_name'
1838                            };
1839
1840                            var form_data_map = {};
1841                            var fieldnames_to_find = ['name', 'first_name', 'last_name', 'email', 'phone', 'phone_code'];
1842                            $('input, select', form_instance).each(function() {
1843                                var fieldname = $(this).attr('name');
1844                                if (fieldnames_to_find.indexOf(fieldname) > -1) {
1845                                    // Add to the tracked data
1846                                    tracking_info_map['user_data'][fieldname] = $(this).val();
1847                                } else if (field_aliases[fieldname] && typeof field_aliases[fieldname] != 'undefined') {
1848                                    var corrected_fieldname = field_aliases[fieldname];
1849                                    tracking_info_map['user_data'][corrected_fieldname] = $(this).val();
1850                                }
1851                            });
1852
1853                            // Set in local storage
1854                            localStorage.setItem('campaign_tracking_info', JSON.stringify(tracking_info_map));
1855
1856                        }
1857                               
1858                    },
1859
1860                    set_forms: function() {
1861
1862                        // Add tracking data to forms
1863                        if (allow_campaign_tracking && g.has_local_storage_test() && typeof localStorage != 'undefined') {
1864                            var tracking_info = localStorage.getItem('campaign_tracking_info');
1865                            if (tracking_info) {
1866
1867                                var field_aliases = {
1868                                    'name': ['f_name'],
1869                                    'first_name': ['f_first_name'],
1870                                    'last_name': ['f_last_name'],
1871                                    'email': ['f_email', 'cart_user_email', 'customer_email'],
1872                                    'phone': ['f_phone', 'cart_user_phone', 'customer_phone'],
1873                                };
1874
1875                                $('form').each(function() {
1876                                    var campaign_tracking_enabled = $(this).hasClass('campaign_tracking') || $(this).hasClass('campaign_tracking_autofill') || $(this).find('input[name="campaign_tracking_data"]').length || $(this).find('input[name="tracking"]').length;
1877                                    if (campaign_tracking_enabled) {
1878                                        var form_instance = $(this);
1879                                        var campaign_tracking_autofill = $(this).hasClass('campaign_tracking_autofill');
1880                                        if (!$('input[name="campaign_tracking_data"]', this).length) {
1881                                            $(this).append('<input type="hidden" name="campaign_tracking_data" />');
1882                                        }
1883                                        $('input[name="campaign_tracking_data"]', this).val(tracking_info); 
1884                                        if (campaign_tracking_autofill) {
1885                                            $.each(window.archimedes.archimedes_core.analytics.track_campaigns.get_user_data(), function(name, value) {
1886                                                if (value && value != 'Newsletter signup') {
1887                                                    var fieldnames_to_find_filter_list = '[name="' + name + '"]';
1888    
1889                                                    var fieldname_aliases = field_aliases[name];
1890                                                    if (fieldname_aliases && typeof fieldname_aliases != 'undefined') {
1891                                                        for (var i = 0; i < fieldname_aliases.length; i++) {
1892                                                            var fieldnames_to_find_filter_list = fieldnames_to_find_filter_list + ', ' + '[name="' + fieldname_aliases[i] + '"]';
1893                                                        }
1894                                                    }
1895                                                    $('input, select', form_instance).not('.form_field_prefilled').not('.campaign_tracking_do_not_process').filter(fieldnames_to_find_filter_list).val(value).trigger('change').addClass('campaign_tracking_autofilled active');
1896                                                }   
1897                                            });
1898                                        }
1899                                    }
1900                                });
1901                            }
1902                        }
1903
1904                    }
1905                    
1906                }
1907            },
1908
1909            google_analytics: function(page){
1910                // this is a placeholder function that gets defined in /pylons0962/galleries/templates/site/includes/misc/analytics_tracking_code.mako
1911            },
1912
1913            cookie_notification: {
1914
1915                focus_on_banner: function () {
1916                    // Accessibility — focus the first interactive element in the cookie banner
1917                    // Only if no popup is currently active (popup takes priority)
1918                    if (!$('body').hasClass('feature_popup_active')) {
1919                        var firstBtn = document.querySelector('#cookie_notification a[role="button"], #cookie_notification button');
1920                        if (firstBtn) firstBtn.focus({preventScroll: true});
1921                    }
1922                },
1923
1924                init: function () {
1925                    var cookie_settings = {
1926                        time_to_wait: 2000
1927                    };
1928                    var custom_cookie_settings = $('#cookie_notification').data('cookie-notification-settings') || {};
1929                    $.extend(cookie_settings, custom_cookie_settings);
1930
1931                    $('#cookie_notification').each(function () {
1932                        var mode = 'default';
1933                        if ($(this).attr('data-mode')){
1934                            mode = $(this).attr('data-mode');
1935                        }
1936                        if(mode == 'consent'){
1937                            var has_preferences = window.archimedes.archimedes_core.cookie_notification.get_cookie_preferences();
1938                            if(!has_preferences){
1939                                // Display the cookie notification after a set amount of time
1940                                setTimeout(function () {
1941                                    $('#cookie_notification').addClass('active');
1942                                    window.archimedes.archimedes_core.cookie_notification.focus_on_banner();
1943                                }, cookie_settings.time_to_wait)
1944
1945                                // Accept btn event listener
1946                                $('#cookie_notification_accept').click(
1947                                    window.archimedes.archimedes_core.cookie_notification.cookie_consent_accept_btn_callback
1948                                );
1949
1950                                // Reject btn event listener
1951                                $('#cookie_notification_reject').click(
1952                                    window.archimedes.archimedes_core.cookie_notification.cookie_consent_reject_btn_callback
1953                                );
1954
1955                            }
1956                
1957                            else {
1958                                $(this).hide();
1959                            }
1960                            
1961                            window.archimedes.archimedes_core.cookie_notification.manage_cookies_popup.init();
1962                            
1963                        } else {
1964                            var notification_cookie = h.getCookie('cookie_notification_dismissed');
1965                            if (!notification_cookie) {
1966                                // Display the cookie notification after a set amount of time
1967                                setTimeout(function () {
1968                                    $('#cookie_notification').addClass('active');
1969                                    window.archimedes.archimedes_core.cookie_notification.focus_on_banner();
1970                                }, cookie_settings.time_to_wait)
1971
1972                                // Accept btn event listener
1973                                $('#cookie_notification_accept').click(
1974                                    window.archimedes.archimedes_core.cookie_notification.cookie_notification_accept_btn_callback
1975                                );
1976
1977                            }
1978                            else {
1979                                $(this).hide();
1980                            }
1981                        }
1982                        
1983                    });
1984                    
1985                    // Run any scripts which have permission to run
1986                    var marketing_consent_given = window.archimedes.archimedes_core.cookie_notification.get_cookie_preference('marketing');
1987                    var statistics_consent_given = window.archimedes.archimedes_core.cookie_notification.get_cookie_preference('statistics');
1988                    var functional_consent_given = window.archimedes.archimedes_core.cookie_notification.get_cookie_preference('functionality');
1989                    if (functional_consent_given) {
1990                        if (typeof window.archimedes_callback != 'undefined' && typeof window.archimedes_callback.additional_scripts_functional != 'undefined') {
1991                            window.archimedes_callback.additional_scripts_functional();
1992                        }
1993                    }
1994                    if (marketing_consent_given) {
1995                        window.archimedes.archimedes_core.analytics.facebook_pixels.grant_consent();
1996                        if (typeof window.archimedes_callback != 'undefined' && typeof window.archimedes_callback.additional_scripts_marketing != 'undefined') {
1997                            window.archimedes_callback.additional_scripts_marketing();
1998                        }
1999                    }
2000                    if (statistics_consent_given) {
2001                        if (typeof window.archimedes_callback != 'undefined' && typeof window.archimedes_callback.additional_scripts_statistics != 'undefined') {
2002                            window.archimedes_callback.additional_scripts_statistics();
2003                        }
2004                    }
2005                    
2006                },
2007
2008                cookie_notification_accept_btn_callback: function (e) {
2009                    h.setCookie('cookie_notification_dismissed', '1', 365);
2010                    /* Display:none the banner once it's transformed off the screen 
2011                        - do this instead of $.hide because it's janky */
2012                    $('#cookie_notification').bind('transitionend', function (te) {
2013                        if (te.originalEvent.propertyName == 'transform') {
2014                            $('#cookie_notification').hide();
2015                        }
2016                    });
2017                    $('#cookie_notification').removeClass('active');
2018                    e.preventDefault();
2019                },
2020
2021                cookie_consent_accept_btn_callback: function (e) {
2022                    
2023                    // Set all cookie consent options to true as they have accepted all
2024                    var cookie_consent = {
2025                        'essential': true,
2026                        'functionality': true,
2027                        'statistics': true,
2028                        'marketing': true
2029                    }
2030                    
2031                    // h.setCookie('cookie_consent', JSON.stringify(cookie_consent), 365);
2032                    window.archimedes.archimedes_core.cookie_notification.set_cookie_preferences(
2033                    cookie_consent
2034                    );
2035                    window.archimedes.archimedes_core.cookie_notification.manage_cookies_popup.after_submit_callback();
2036                    window.archimedes.archimedes_core.cookie_notification.hide_cookie_notification_banner();
2037                    e.preventDefault();
2038                },
2039
2040                cookie_consent_reject_btn_callback: function (e) {
2041                    
2042                    // Set all cookie consent options to true as they have accepted all
2043                    var cookie_consent = {
2044                        'essential': true,
2045                        'functionality': false,
2046                        'statistics': false,
2047                        'marketing': false
2048                    }
2049                    
2050                    window.archimedes.archimedes_core.cookie_notification.set_cookie_preferences(
2051                    cookie_consent
2052                    );
2053                    window.archimedes.archimedes_core.cookie_notification.manage_cookies_popup.after_submit_callback();
2054                    window.archimedes.archimedes_core.cookie_notification.hide_cookie_notification_banner();
2055                    e.preventDefault();
2056                },
2057                
2058                hide_cookie_notification_banner: function (e) {
2059                    
2060                    /* Display:none the banner once it's transformed off the screen 
2061                        - do this instead of $.hide because it's janky */
2062                    $('#cookie_notification').bind('transitionend', function (te) {
2063                        if (te.originalEvent.propertyName == 'transform') {
2064                            $('#cookie_notification').hide();
2065                        }
2066                    });
2067                    $('#cookie_notification').removeClass('active');
2068                    
2069                },
2070
2071                set_cookie_preferences: function (cookie_preferences) {
2072
2073                    // delete all existing cookies because we can't guess which cookies no longer have consent
2074                    // disable this as it empties the the store basket if you click it and causes errors on the checkout
2075                    // window.archimedes.archimedes_core.cookie_notification.delete_all_cookies();
2076                    
2077                    if (g.has_local_storage()) {
2078                        today = new Date();
2079                        today = today.toISOString().substr(0, 10);
2080                        cookie_preferences['date'] = today;
2081                        localStorage.setItem(
2082                            "cookie_preferences",
2083                            JSON.stringify(cookie_preferences)
2084                        );
2085                        if (typeof window.google_analytics_init == 'function') {
2086                            window.google_analytics_init(location.pathname + window.location.search);
2087                        }
2088                        if (typeof window.google_tag_manager_init == 'function') {
2089                           
2089 window.google_tag_manager_init();
2090                        }
2091                        window.archimedes.archimedes_core.analytics.track_campaigns.init();
2092                        
2093                        var marketing_consent_given = window.archimedes.archimedes_core.cookie_notification.get_cookie_preference('marketing');
2094                        var statistics_consent_given = window.archimedes.archimedes_core.cookie_notification.get_cookie_preference('statistics');
2095                        var functional_consent_given = window.archimedes.archimedes_core.cookie_notification.get_cookie_preference('functionality');
2096                        
2097                        if (functional_consent_given) {
2098                            if (typeof window.archimedes_callback != 'undefined' && typeof window.archimedes_callback.additional_scripts_functional != 'undefined') {
2099                                window.archimedes_callback.additional_scripts_functional();
2100                            }
2101                        }
2102                        if (marketing_consent_given) {
2103                            window.archimedes.archimedes_core.analytics.facebook_pixels.grant_consent();
2104                            if (typeof window.archimedes_callback != 'undefined' && typeof window.archimedes_callback.additional_scripts_marketing != 'undefined') {
2105                                window.archimedes_callback.additional_scripts_marketing();
2106                            }
2107                        }
2108                        if (statistics_consent_given) {
2109                            if (typeof window.archimedes_callback != 'undefined' && typeof window.archimedes_callback.additional_scripts_statistics != 'undefined') {
2110                                window.archimedes_callback.additional_scripts_statistics();
2111                            }
2112                        }
2113                        
2114                    }
2115                },
2116
2117                get_cookie_preferences: function(){
2118                    result = false;
2119                    if(g.has_local_storage()){
2120                        var cookie_preferences = localStorage.getItem('cookie_preferences') || "";
2121                        if (cookie_preferences) {
2122                            cookie_preferences = JSON.parse(cookie_preferences);
2123                            if (cookie_preferences.date) {
2124                                var expires_on = new Date(cookie_preferences.date);
2125                                expires_on.setDate(expires_on.getDate() + 365);
2126                                // console.log('cookie preferences expire on', expires_on.toISOString());
2127                                var valid = expires_on > new Date();
2128                                if (valid){
2129                                    result = cookie_preferences;
2130                                }
2131                            }
2132                        }
2133                    }
2134                    return result;
2135                },
2136
2137                get_cookie_preference: function (category) {
2138                    result = false;
2139                    try {
2140                        var cookie_preferences = localStorage.getItem('cookie_preferences') || "";
2141                        if (cookie_preferences) {
2142                            cookie_preferences = JSON.parse(cookie_preferences);
2143                            if (cookie_preferences.date) {
2144                                var expires_on = new Date(cookie_preferences.date);
2145                                expires_on.setDate(expires_on.getDate() + 365);
2146                                // console.log('cookie preferences expire on', expires_on.toISOString());
2147                                var valid = expires_on > new Date();
2148                                if (valid && cookie_preferences.hasOwnProperty(category) && cookie_preferences[category]) {
2149                                    result = true;
2150                                }
2151                            }
2152                        }
2153                    }
2154                    catch(e) {
2155                        console.warn('get_cookie_preference() failed');
2156                        return result;
2157                    }
2158                    //console.log('cookie preference', category, result);
2159                    return result;
2160                },
2161
2162                delete_all_cookies: function(){
2163                    var cookies = document.cookie.split("; ");
2164                    for (var c = 0; c < cookies.length; c++) {
2165                        var d = window.location.hostname.split(".");
2166                        // iterate through all domain variations
2167                        while (d.length > 0) {
2168                            console.log(encodeURIComponent(cookies[c].split(";")[0].split("=")[0]) + '=; expires=Thu, 01-Jan-1970 00:00:01 GMT; domain=' + d.join('.') + '; path=/');
2169                            document.cookie = encodeURIComponent(cookies[c].split(";")[0].split("=")[0]) + '=; expires=Thu, 01-Jan-1970 00:00:01 GMT; domain=' + d.join('.') + '; path=/';
2170                            d.shift();
2171                        }
2172                        // delete cookies with no domain set
2173                        document.cookie = encodeURIComponent(cookies[c].split(";")[0].split("=")[0]) + '=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/';
2174                    }
2175                },
2176
2177                manage_cookies_popup: {
2178
2179                    init: function(){
2180                        $('#cookie_notification_preferences a, .cookie_notification_preferences a').unbind('click.manage_cookies_open')
2181                            .click('manage_cookies_open', function () {
2182                                // For accessibility - tracks which element to refocus on
2183                                try {
2184                                    h.accessibility.global_variables.element_to_refocus_to = $(this);
2185                                } catch(error) {
2186                                    console.error(error);
2187                                }
2188                                window.archimedes.archimedes_core.cookie_notification.manage_cookies_popup.open();
2189                            });
2190
2191                        $('#manage_cookie_preferences_close_popup_link a, #manage_cookie_preferences_popup_overlay')
2192                            .unbind()
2193                            .click(function () {
2194                                window.archimedes.archimedes_core.cookie_notification.manage_cookies_popup.close(true);
2195                                return false;
2196                            })
2197                            ;
2198
2199
2200                        $('#cookie_preferences_form').submit(function(e){
2201                            e.preventDefault();
2202                            var cookie_consent = {};
2203                            $('input', this).not(':input[type=button], :input[type=submit], :input[type=reset]').each(function(){
2204                                cookie_consent[$(this).attr('name')] = $(this).is(':checked') ? true : false;
2205                            });
2206                            window.archimedes.archimedes_core.cookie_notification.set_cookie_preferences(cookie_consent);
2207                            window.archimedes.archimedes_core.cookie_notification.manage_cookies_popup.after_submit_callback();
2208                            window.archimedes.archimedes_core.cookie_notification.manage_cookies_popup.close(false);
2209                        }); 
2210
2211                        $('#cookie_preferences_form_submit').click(function(e){
2212                            e.preventDefault();
2213                            $('#cookie_preferences_form').submit();
2214                        });
2215
2216                        var cookie_preferences = window.archimedes.archimedes_core.cookie_notification.get_cookie_preferences();
2217                        if(cookie_preferences){
2218                            for(var category in cookie_preferences){
2219                                if(cookie_preferences[category]){
2220                                    $('#cookie_preferences_form input[name="'+category+'"]').attr('checked', true);
2221                                }
2222                            }
2223                        }
2224
2225                    },
2226
2227                    open: function(){
2228                        $('body').addClass('manage_cookie_preferences_popup_active');
2229                        $('#cookie_notification').removeClass('active');
2230                        setTimeout(function () {
2231                            $('body').addClass('manage_cookie_preferences_popup_visible');
2232                            h.accessibility.on_popup_opening($('#manage_cookie_preferences_popup_box'), false, '#manage_cookie_preferences_close_popup_link a');
2233                            window.archimedes.archimedes_core.cookie_notification.manage_cookies_popup.after_popup();
2234                        }, 50);
2235                    },
2236
2237                    close: function(reopen_notification){
2238                        $('body').removeClass('manage_cookie_preferences_popup_visible');
2239                        setTimeout(function () {
2240                            $('body').removeClass('manage_cookie_preferences_popup_active');
2241                            if(reopen_notification){
2242                                $('#cookie_notification').addClass('active');
2243                            }
2244                            else {
2245                                window.archimedes.archimedes_core.cookie_notification.hide_cookie_notification_banner();
2246                            }
2247                        }, 400);
2248
2249                        h.accessibility.on_popup_closing();
2250                    },
2251
2252                    after_popup: function(){
2253
2254                    },
2255
2256                    after_submit_callback: function(){
2257
2258                    },
2259
2260                }
2261            },
2262
2263        }
2264
2265    };
2266
2267    window.h = window.helpers = {
2268        
2269        format_number: {
2270            
2271            prep: function(v) {
2272                // remove commas...
2273                return h.replace_all(v, ',', '');
2274            },
2275
2276            trim_zeros: function(v) {
2277                v = this.prep(v);
2278                return (v) ? parseFloat(v) : 0;
2279            },
2280
2281            decimal: function(v, params) {
2282                var blank_zero = (params.blank_zero) ? true : false;
2283                var drop_trailing_zeros = (params.drop_trailing_zeros) ? true : false;
2284                v = this.prep(v);
2285                v = this.trim_zeros(v).toFixed(2);
2286                if (blank_zero && v == 0) {
2287                    v = '';
2288                }
2289                if (v.toString().indexOf('.') > -1) {
2290                    var delim = ',';
2291                    var vsplit = v.toString().split('.');
2292                    var pre = vsplit[0].split('');
2293                    var post = vsplit[1];
2294                    pre.reverse();
2295                    var m = 3, seg = [], segs = [], rev, new_v;
2296                    for (var i = 0; i < pre.length; i ++) {
2297                        seg[seg.length] = pre[i];
2298                        if (seg.length == 3) {
2299                            segs[segs.length] = seg.join("");
2300                            seg = [];
2301                        }
2302                    }
2303                    if (seg.length > 0) {
2304                        segs[segs.length] = seg.join("");
2305                    }
2306                    rev = segs.join(delim).split('');
2307                    rev.reverse();
2308                    pre = rev.join('');
2309                    if (!pre) {
2310                        pre = 0;
2311                    }
2312                    if (post == '00' && drop_trailing_zeros) {
2313                        new_v = pre;
2314                    } else {
2315                        new_v = pre + '.' + post;
2316                    }
2317                    v = new_v;
2318                }
2319                return v;
2320            }
2321
2322
2323        },
2324
2325        is_int: function(value) {
2326            if((parseFloat(value) == parseInt(value)) && !isNaN(value)){
2327                return true;
2328            } else {
2329                return false;
2330            }
2331        },
2332
2333        element_exists: function(sel) {
2334            // given a jquery selector, return true if the element exists, e.g.:
2335            // h.element_exists('.foo');
2336            // h.element_exists('#bar');
2337            return $(sel).length > 0;
2338        },
2339
2340        create_html_select: function(params) {
2341            // note: this helper function is used by the new
2342            // invoice screen
2343            if (!params) { return; }
2344            var html_rows = [];
2345            html_rows[html_rows.length] = '<select id="' + params.el_id +
2346                '" name="' + params.el_name + '">';
2347            var sel, id;
2348            if (params.allow_empty) {
2349                sel = (!params.selected_value) ? ' selected' : '';
2350                html_rows[html_rows.length] = '<option value=""' + sel +
2351                '></option>';
2352            }
2353            for (var i = 0; i < params.option_ids_in_order.length; i ++) {
2354                id = params.option_ids_in_order[i];
2355                sel = false;
2356                if (params.selected_value) {
2357                    if (params.options[id] == params.selected_value) {
2358                        sel = true;
2359                    }
2360                }
2361                html_rows[html_rows.length] = '<option value="' + id + '"' +
2362                    ((sel) ? ' selected' : '') + '>' + params.options[id] +
2363                    '</option>';
2364            }
2365            html_rows[html_rows.length] = '</select>';
2366            return html_rows.join("");
2367        },
2368
2369        open_win: function(params) {
2370            // a quick syntax for a new pop-up window:
2371            // h.open_win({url: 'url/', params: {'hello': 'world'}, width: 350, height: 350, scrollbars: true})
2372            if (!params) return false;
2373            if (!params.params) params.params = {};
2374            params.url = h.construct_url(params.url, params.params);
2375            delete params.params;
2376            fl_win(params);
2377        },
2378
2379        construct_url: function(url, params) {
2380            // construct a GET url from a given url, plus (optionally)
2381            // an object contining parameters to be sent, e.g.
2382            // this.construct_url('/path_to_file/', {'id': 1, 'name': 'a value'})
2383            // -> '/path_to_file/?id=1&name=a%20value'
2384            // NOTE: this is the jquery equivaluent of prototype's Object.toQueryString() function
2385            params_str = (params) ? jQuery.param(params) : null;
2386            var sep = (url.indexOf("?") > -1) ? "&" : "?";
2387            return this.concat_ws(sep, url, params_str);
2388        },
2389
2390        concat_ws: function() {
2391            // this is an improvement on the fl_concatWS
2392            // function in /flgui/scripts/main.js
2393            var out = [];
2394            if (!arguments || arguments.length == 0) return "";
2395            var sep = arguments[0]
2396            for (var i = 1; i < arguments.length; i ++) {
2397                if (arguments[i]) {
2398                    out[out.length] = arguments[i].toString();
2399                }
2400            }
2401            return out.join(sep);
2402        },
2403
2404        decode_html_entities: function(t) {
2405            // converts an html entity (e.g. the html entity representing
2406            // the Euro symbol) to the real character. The results are cached
2407            // for speedy retrieval.
2408            // Based on a free script found here (thanks due to the author):
2409            // http://javascript.internet.com/snippets/convert-html-entities.html
2410            if (!this.html_entities_map) {
2411                this.html_entities_map = {};
2412            }
2413            if (this.html_entities_map[t]) {
2414                return this.html_entities_map[t];
2415            }
2416            var el=document.createElement("textarea");
2417            el.innerHTML = t.replace(/\</g,"&lt;").replace(/\>/g,"&gt;");
2418            this.html_entities_map[t] = el.value;
2419            return this.html_entities_map[t];
2420        },
2421
2422        in_array: function(value, array) {
2423            /*
2424             * Peter is not crazed for the jQuery $.inArray syntax which returns
2425             * the index of the found item, or -1 if it is not in the array.
2426             * Sometimes we just want to know True or False if an item is in
2427             * an array. This function uses the jQuery function to do just that:
2428             * h.in_array(value, array) -> true or false
2429             */
2430            if ($.inArray(value, array) > -1) {
2431                return true;
2432            } else {
2433                return false;
2434            }
2435        },
2436
2437        remove_leading: function(value, match_string) {
2438            while(value.indexOf(match_string) == 0) {
2439                value = ('___start___' + value).replace('___start___' + match_string, '')
2440            }
2441            return value;
2442        },
2443
2444        display_date: function(input_date, short_month_name) {
2445            /*
2446                Display a date in SQL format to our preferred format:
2447                >>> display_date('2009-10-22');
2448                'October 22, 2009'
2449                >>> display_date('2009-10-22', true); // short month name
2450                'Oct 22, 2009'
2451            */
2452            if (!input_date || input_date == "" || parseInt(input_date) == 0) {
2453                return "";
2454            } else {
2455                var t="";
2456                var dt = "";
2457                var tm = "";
2458                if (short_month_name) {
2459                    var month_names=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
2460                } else {
2461                    var month_names=["January","February","March","April","May","June","July","August","September","October","November","December"];
2462                }
2463                var input_array = input_date.split(" ")[0].split("-");
2464                var time_segment = '';
2465                if (input_date.indexOf(':') > -1) {
2466                    time_segment = input_date.split(' ')[1];
2467                }
2468                if (time_segment == '00:00:00') {
2469                    time_segment = '';
2470                }
2471                var y = parseInt(input_array[0], 10);
2472                /*
2473                    Note about the above syntax (javacript bug): parseInt("09") returns 0 not
2474                    9, and parseInt("08") returns 0 not 8, whereas all other numbers are returned
2475                    correctly. This is because parseInt incorrectly thinks they are invalid octal
2476                    numbers. The workaround is to force parseInt to base 10, eg. parseInt("09", 10)
2477                    returns 9. See article here:
2478                    http://www.breakingpar.com/bkp/home.nsf/0/87256B280015193F87256C85006A6604
2479                */
2480                var m = (parseInt(input_array[1], 10)>0) ? month_names[input_array[1]-1] : "";
2481                var d = parseInt(input_array[2], 10);
2482                t += (m != "") ? m+" " : "";
2483                t += (d>0) ? d+", " : "";
2484                t += y;
2485                return this.concat_ws(', ', t, time_segment);
2486            }
2487        },
2488
2489        add_warning_if_greater_than_today: function() {
2490            // if the variable h.today exists (set it if you need to), and any field exists on
2491            // the page with a class of 'gt_today', check dates against today, and add a class
2492            // of 'warning'.
2493            var today = this.today;
2494            if (today) {
2495                $('.gt_today').each(function() {
2496                    var v = $(this).val();
2497                    if (v == 0 || v == '' || v == '0000-00-00') {
2498                         $(this).removeClass('tick');
2499                        $(this).removeClass('warning');
2500                    } else if (v < today) {
2501                         $(this).removeClass('tick');
2502                        $(this).addClass('warning');
2503                    } else {
2504                        $(this).removeClass('warning');
2505                        $(this).addClass('tick');
2506                    }
2507                });
2508            }
2509        },
2510
2511        convert_to_single_line: function(t) {
2512            t = h.replace_all(t, '<br>', ', ');
2513            t = h.replace_all(t, '<br/>', ', ');
2514            t = h.replace_all(t, '<br />', ', ');
2515            t = h.clean_whitespace(t);
2516            t = h.trim(t);
2517            var tags_to_keep = ['b', 'i', 'u', 'em', 'strong']
2518            for (var i = 0; i < tags_to_keep.length; i ++) {
2519                t = h.replace_all(t, '<' + tags_to_keep[i] + '>', '___' + tags_to_keep[i] + '___');
2520                t = h.replace_all(t, '</' + tags_to_keep[i] + '>', '___/' + tags_to_keep[i] + '___');
2521            };
2522            t = t.replace(/<.+?>/g, '');
2523            for (var i = 0; i < tags_to_keep.length; i ++) {
2524                t = h.replace_all(t, '___' + tags_to_keep[i] + '___', '<' + tags_to_keep[i] + '>');
2525                t = h.replace_all(t, '___/' + tags_to_keep[i] + '___', '</' + tags_to_keep[i] + '>');
2526            };
2527            t = t.replace(/(, )+/g, ', ');
2528            return t;
2529        },
2530
2531        trim: function(t) {
2532            // trim a string
2533            if (!t) {
2534                t = '';
2535            }
2536            t = t.toString();
2537            return t.replace(/^\s+|\s+$/g, '');
2538        },
2539
2540        clean_whitespace: function(t) {
2541            // clean whitespace from a string, replacing with a single space
2542            if (!t) {
2543                t = '';
2544            }
2545            t = t.toString();
2546            return t.replace(/\s+/g, ' ');
2547        },
2548
2549        list_items_to_array: function(txt, return_integers) {
2550            // converts a line-separated list of items into an array,
2551            // removing empty values
2552            items = this.convert_line_returns(txt).split("\n"); // convert line returns
2553            var out = [];
2554            var item;
2555            for (var i = 0; i < items.length; i ++) {
2556                item = this.trim(items[i]);
2557                if (item) {
2558                    if (return_integers) {
2559                        item = parseInt(item);
2560                    }
2561                    out[out.length] = item;
2562                }
2563            };
2564            return out;
2565        },
2566
2567        encode_breaks: function(t, c) { // text, break character
2568            if (!t) {
2569                t = '';
2570            }
2571            if (!c) c = '<br />';
2572            t = this.replace_all(t, "\r\n", c);
2573            t = this.replace_all(t, "\r", c);
2574            t = this.replace_all(t, "\n", c);
2575            return t;
2576        },
2577
2578        encode_breaks_to_commas: function(t) {
2579            return this.encode_breaks(t, ", ");
2580        },
2581
2582        escape_quotes: function(t) {
2583            // converts double-quotes, returning a string suitable
2584            // for putting in the 'value' attribute of an html
2585            // input field
2586            return this.replace_all(t, '"', '&#34;');
2587        },
2588
2589        convert_line_returns: function(txt, c) {
2590            if (!c) c = "\n";
2591            return this.encode_breaks(txt, c);
2592        },
2593
2594        replace_all: function(t, r, w) { // text, replace, with
2595            if (!t) {
2596                t = '';
2597            }
2598            return t.toString().split(r).join(w);
2599        },
2600
2601        parse_int: function(v) {
2602            v = parseInt(v, 10); // see note above relating to parseInt
2603            if (isNaN(v)) {
2604                v = 0;
2605            }
2606            return v;
2607        },
2608
2609        parse_float: function(v) {
2610            v = parseFloat(v, 10); // see note above relating to parseInt
2611            if (isNaN(v)) {
2612                v = 0.0;
2613            }
2614            return v;
2615        },
2616
2617        copy_obj: function(obj) {
2618            var out = {};
2619            for (var k in obj) {
2620                out[k] = obj[k];
2621            }
2622            return out;
2623        },
2624
2625        decode_json: function(t) {
2626            if (typeof t === 'object') return t;
2627            return JSON.parse(t);
2628        },
2629
2630        alert: function(msg, options) {
2631            if (msg.indexOf('<') == -1) {
2632                msg = '<div class="aol-default-prompt-text">' + msg + '</div>';
2633            }
2634            if (!options) {
2635                options = {
2636                    buttons: {Ok: true}
2637                };
2638            }
2639            if (!options.zIndex) options.zIndex = 100000; // Maximum value
2640            $.prompt(msg.toString(), options);
2641            this.set_promp_style();
2642        },
2643
2644        notify: function(msg, milliseconds) {
2645            // like alert, but dismisses automatically after the
2646            // specified number of milliseconds
2647            if (msg.indexOf('<') == -1) {
2648                msg = '<div class="aol-default-prompt-text">' + msg + '</div>';
2649            }
2650            if (!milliseconds) {
2651                milliseconds = 2500;
2652            }
2653            var dialog = $.prompt(msg.toString(),{
2654                buttons: {Ok: true},
2655                timeout: milliseconds
2656            });
2657            this.set_promp_style();
2658        },
2659
2660        prompt: function(msg, params) {
2661            if (msg.indexOf('<') == -1) {
2662                msg = '<div class="aol-default-prompt-text">' + msg + '</div>';
2663            }
2664            $.prompt(msg, params);
2665            this.set_promp_style();
2666        },
2667
2668        set_promp_style: function() {
2669            $('.aolpromptbuttons button')
2670                .wrapInner('<span></span>');
2671            $('.aolpromptbuttons')
2672                .append('<div class="clear"></div>');
2673        },
2674
2675        proxy_url: function(url) {
2676            // rewrites a url so that it includes the 'proxy dir' if it is being
2677            // used. A proxy dir is a top level directory used for a specific
2678            // purpose, e.g. '/m/' = mobile, '/a/' = accessible, etc...
2679            if (!window.archimedes.proxy_dir) {
2680                return url;
2681            } else {
2682                var prefix = window.archimedes.proxy_dir;
2683                if (url.indexOf(prefix + '/') != 0 &&
2684                    url.indexOf('http') != 0 &&
2685                    url.indexOf('/') == 0) {
2686                    return prefix + url;
2687                } else {
2688                    return url;
2689                }
2690            }
2691        },
2692
2693        // Cookie functions <[email protected]>
2694        setCookie: function(c_name, value, expiredays, expireseconds) {
2695            var exdate=new Date();
2696            if (expiredays) {
2697                exdate.setDate(exdate.getDate()+expiredays);
2698            } else if (expireseconds) {
2699                exdate.setSeconds(exdate.getSeconds() + expireseconds);
2700            }
2701            document.cookie=c_name+ "=" +escape(value)+
2702            ((expiredays==null) ? "" : ";expires="+exdate.toGMTString())+";path=/";
2703        },
2704
2705        getCookie: function(c_name) {
2706            if (document.cookie.length>0){
2707              c_start=document.cookie.indexOf(c_name + "=");
2708              if (c_start!=-1){
2709                c_start=c_start + c_name.length+1;
2710                c_end=document.cookie.indexOf(";",c_start);
2711                if (c_end==-1) c_end=document.cookie.length;
2712                return unescape(document.cookie.substring(c_start,c_end));
2713                }
2714              }
2715            return "";
2716        },
2717
2718        deleteCookie: function(c_name){
2719            // currently can only delete first-party cookies (where cookie domain is equal to site hostname)
2720            if (h.getCookie(c_name)) {
2721                var domain = location.hostname;
2722                var domain = location.hostname;
2723                document.cookie =
2724                    c_name + "=" + ";path=/;domain="+domain+";expires=Thu, 01 Jan 1970 00:00:01 GMT";
2725                // now delete the version without a subdomain
2726                domain = domain.split('.');
2727                domain.shift();
2728                domain = domain.join('.');
2729                document.cookie =
2730                    c_name + "=" + ";path=/;domain="+domain+";expires=Thu, 01 Jan 1970 00:00:01 GMT";
2731            }
2732        },
2733
2734        // Javascript redirection (with optional delay)
2735        // <[email protected]>
2736        redirect: function(url, options) {
2737            var options = $.extend({
2738                delay: 0
2739            }, options || {});
2740
2741            setTimeout(function() {
2742                window.location = url;
2743            }, options.delay);
2744        },
2745
2746        ajax_login: function(path, username, password) {
2747            $.post(path, {
2748                username: username,
2749                password: password
2750            }, function(data) {
2751                if (data && data.logged_in) {
2752                    try {
2753                        window.location.reload(true);
2754                    } catch (e) {
2755                        // Backward compatability
2756                        window.location.href = window.location.pathname;
2757                    }
2758                } else if (data.error) {
2759                    h.alert(data.error);
2760                } else {
2761                    h.alert('There was a problem trying to log you in. Please refresh the page and try again.');
2762                }
2763            }, 'json');
2764        },
2765        
2766        accessibility: {
2767            
2768            global_variables: {
2769                element_to_refocus_to_after_close: '',
2770                popup_layer_tracking: [],
2771                element_to_refocus_to: '',
2772            },
2773            
2774            init: function() {
2775                h.accessibility.click_and_tab_detection();
2776                h.accessibility.misc();
2777                h.accessibility.check_for_core_accessibility_stylesheet();
2778                h.accessibility.axe_automatic_audit();
2779                h.accessibility.show_image_alt_tags();
2780            },
2781            
2782            on_popup_opening: function(trap_element, first_focusable_element_override, close_button_element) {
2783                
2784                try {
2785                    
2786                    if (!$("body").hasClass('accessibility-functions-disabled')) {
2787                
2788                        h.accessibility.popupLayerTracker("opening", trap_element);
2789                        
2790                        // if an artwork popup is already open
2791                        if ($("body").hasClass("roomview-active")) {
2792                            h.accessibility.focus_untrap();
2793                        } else if ($("body").hasClass("page-popup-visible")) {
2794                            h.accessibility.focus_untrap();
2795                        }
2796            
2797                        var $focus_override_element = $(trap_element).find("[data-first-focus-override]");
2798  
2799                        // check if the trap_element has a data attribute which is a custom override (useful for bespoke sites)
2800                        if (trap_element && $focus_override_element.length && $focus_override_element.attr('data-first-focus-override')) {
2801                            first_focusable_element_override = $focus_override_element.attr('data-first-focus-override');
2802                        }
2803                        
2804                        h.accessibility.focus_trap(trap_element, first_focusable_element_override, close_button_element);
2805                        
2806                        if (close_button_element) {
2807                            h.accessibility.closeWithEscapeKey(close_button_element, trap_element);
2808                        }
2809                        
2810                        var first_element_to_focus = first_focusable_element_override;
2811                        if (first_element_to_focus && $(first_element_to_focus).length) {
2812                            $(first_element_to_focus)[0].focus({preventScroll:true});
2813                            first_element_to_focus = '';
2814                        } else if ($('.focustrap-first').length) {
2815                            $('.focustrap-first')[0].focus({preventScroll:true});
2816                        }
2817                        // Fix for iOS - set non popup content to aria hidden when a popup is opened.
2818                        // Due to the main navigation being inside #container - we do not set the aria-hidden here, but instead within the click event
2819                        if (trap_element && !$('#container').find(trap_element).length) {
2820                            $('#container > *:not(#cookie_notification):not(#manage_cookie_preferences_popup_container)').attr('aria-hidden', 'true');
2821                        }
2822                        
2823                        // If more than one popup opens, hide the non active ones from the screen reader
2824                        if ($('[data-popup-layer]').length) {
2825                            $('[data-popup-layer]').each(function() {
2826                                if ($(this).attr('data-popup-layer') != h.accessibility.popupLayerTracker("length")) {
2827                                    $(this).attr('aria-hidden', 'true');
2828                                }
2829                            });
2830                        }
2831                        
2832                    }
2833                    
2834                } catch(error) {
2835                    console.error(error);
2836                }
2837                
2838            },
2839            
2840            on_popup_closing: function(focus_after) {
2841                
2842                try {
2843                    
2844                    if (!$("body").hasClass('accessibility-functions-disabled')) {
2845                
2846                        h.accessibility.popupLayerTracker("closing");
2847                        
2848                        h.accessibility.focus_untrap(focus_after);
2849                        
2850                        // if an artwork popup is already open
2851                        if ($("body").hasClass("roomview-active")) {
2852                            h.accessibility.focus_trap('.roomview-close', false, '.roomview-close');
2853                        } else if ($( ".fancybox-inner #contact_form" ).length || $( "body.manage_cookie_preferences_popup_active .fancybox-overlay .fancybox-opened" ).length) {
2854                            h.accessibility.focus_trap('.fancybox-opened .fancybox-skin');
2855                        } else if ($("body").hasClass("page-popup-visible")) {
2856                            h.accessibility.focus_trap('#popup_box .inner', false, '#popup_container .close');
2857                        }
2858                        
2859                        // When closing a popup, make sure the new active popup is visible to screen readers
2860                        if ($('[data-popup-layer]').length) {
2861                            $('[data-popup-layer]').each(function() {
2862                                if ($(this).attr('data-popup-layer') 
2862== h.accessibility.popupLayerTracker("length")) {
2863                                    $(this).attr('aria-hidden', 'false');
2864                                }
2865                            });
2866                        }
2867                        
2868                        // Fix for iOS - set non popup content back to readable by screan readers if no popups are open
2869                        if (!h.accessibility.popupLayerTracker("length")) {
2870                            $('#container > *:not(#cookie_notification):not(#manage_cookie_preferences_popup_container)').attr('aria-hidden', 'false');
2871                        }
2872                        
2873                        var $element_to_refocus_to_after_close = h.accessibility.global_variables.element_to_refocus_to_after_close
2874                        
2875                        // You can add a data atrribute of 'data-focus-after' to the default refocus element to change the focus. This is useful
2876                        // when you want to refocus on a different element on an item within a grid/list.
2877                        if ($element_to_refocus_to_after_close && $element_to_refocus_to_after_close.length && $element_to_refocus_to_after_close.attr("data-focus-after") && typeof $element_to_refocus_to_after_close.attr("data-focus-after") !== 'undefined' ) {
2878                            var new_focus_element = $element_to_refocus_to_after_close.attr("data-focus-after");
2879                            if ($(new_focus_element).length) $(new_focus_element)[0].focus({preventScroll:true});
2880                        } else if ($element_to_refocus_to_after_close && $element_to_refocus_to_after_close.length && !focus_after) {
2881                            $element_to_refocus_to_after_close[0].focus({preventScroll:true});
2882                        }
2883                    }
2884                
2885                } catch(error) {
2886                    console.error(error);
2887                }
2888                
2889            },
2890            
2891            focus_trap: function(trap_element, first_focusable_element_override, close_button_element) {
2892                
2893                if (!$("body").hasClass('accessibility-functions-disabled')) {
2894                
2895                    // Trap the focus/tab key to a specific element. This is useful for popups and navigations
2896                    
2897                    if (  typeof trap_element != 'undefined' && trap_element.length ) {
2898                        
2899                        var focusable_types = 'iframe:visible:not(.fancybox-iframe):not(.focustrap-ignore), ' +
2900                                              'button:visible:not([disabled]):not(.focustrap-ignore), ' +
2901                                              '[href]:visible:not([tabindex="-1"]):not([disabled]):not(.focustrap-ignore):not(link), ' +
2902                                              'input:visible:not([type=hidden]):not(.focustrap-ignore), ' +
2903                                              'select:visible:not(.focustrap-ignore), ' +
2904                                              'textarea:visible:not(.focustrap-ignore), ' +
2905                                              '[tabindex]:visible:not([tabindex="-1"]):not(.focustrap-ignore):not(link), ' +
2906                                              '.focusable_element_override:visible:not(.focustrap-ignore)'
2907                        
2908                        if (first_focusable_element_override) {
2909                            $(first_focusable_element_override).addClass('focusable_element_override');
2910                        }
2911                        
2912                        $(focusable_types).each(function () {
2913                            $(this).addClass('focustrap-focusable');
2914                            if (typeof $(this).attr('tabindex') !== 'undefined' && (!$(this).hasClass("focustrap-disabled-outside"))) {
2915                               var tabindex = $(this).attr('tabindex');
2916                               $(this).attr('data-tabindex', tabindex);
2917                            }
2918                        });
2919                        
2920                        //supply your wrapper
2921                        var $elem = $(trap_element).first();
2922                        
2923                        if ($elem && typeof $elem !== "undefined") {
2924                            
2925                            $elem.addClass('focustrap-wrapper');
2926                            
2927                            //detect the focusable items in your wrapper
2928                            var focusable = Array.prototype.slice.call($(focusable_types, $elem[0]));
2929                            
2930                            $(focusable).not('.focustrap-ignore').addClass('focustrap-item').attr('tabindex', 0);
2931                            
2932                            //detect the focusable items outside your wrapper and kill them
2933                            $('body').find(focusable_types).not('.focustrap-item').addClass('focustrap-disabled-outside');
2934                            
2935                            // There is a weird empty iframe that gets included with google maps which is tababble - this removes it.
2936                            $('#map_basic iframe').addClass('focustrap-disabled-outside');
2937                            
2938                            if ($('.focustrap-disabled-outside').attr('tabindex') != -1) {
2939                                $('.focustrap-disabled-outside').attr('tabindex', -1);
2940                            }
2941                            
2942                            // Check to see if the cookie banner is active
2943                            // If it is we need to include it in the tab order for when a popup is open so its accessible by keyboard
2944                            var cookie_banner_active = false;
2945                            var cookie_banner_focusable_elements = [];
2946                            var cookie_preferences = window.archimedes.archimedes_core.cookie_notification.get_cookie_preferences();
2947                            
2948                            if ($('#cookie_notification').length && !cookie_preferences && !$('body').hasClass('manage_cookie_preferences_popup_visible')) {
2949                                
2950                                // re-enable tabbing for focusable items in the cookie banner
2951                                $('#cookie_notification .focustrap-disabled-outside').not('.focustrap-ignore').attr('tabindex', 0);
2952                                
2953                                cookie_banner_active = true;
2954                                cookie_banner_focusable_elements = $('#cookie_notification').find(focusable_types);
2955                                
2956                                if (cookie_banner_focusable_elements.length) {
2957                                    var cookie_banner_first_focusable_element = cookie_banner_focusable_elements[0];
2958                                    var cookie_banner_last_focusable_element = cookie_banner_focusable_elements[cookie_banner_focusable_elements.length - 1]
2959                                    
2960                                    // If you tab on the last focusable element in the cookie banner, focus on the first focusable element on the popup
2961                                    $(cookie_banner_last_focusable_element).addClass('cb-focustrap-last').bind('keydown.focusLastCB', function (e) {
2962                                        var keyCode = e.which;
2963                                        if ($("this:focus") && (keyCode == 9 && !e.shiftKey)) {
2964                                            e.preventDefault();
2965                                            $(firstAnchor).focus();
2966                                        }
2967                                    });
2968                                    
2969                                    // If you tab + shift on the first focusable element in the cookie banner, focus on the last focusable element on the popup
2970                                    $(cookie_banner_first_focusable_element).addClass('cb-focustrap-first').bind('keydown.focusFirstCB', function (e) {
2971                                        var keyCode = e.which;
2972                                        if ($("this:focus") && (keyCode == 9 && e.shiftKey)) {
2973                                            e.preventDefault();
2974                                            $(lastAnchor).focus();
2975                                        }
2976                                    });
2977                                }
2978                            }
2979                            
2980                            //Ensure tab looping in your wrapper
2981                            var firstAnchor = focusable[0];
2982                            var lastAnchor = focusable[focusable.length - 1]
2983                            
2984                            $(lastAnchor).addClass('focustrap-last').bind('keydown.focusLast', function (e) {
2985                                var keyCode = e.which;
2986                                if ($("this:focus") && (keyCode == 9 && !e.shiftKey)) {
2987                                    e.preventDefault();
2988                                    if (cookie_banner_active && cookie_banner_focusable_elements.length && !window.archimedes.archimedes_core.cookie_notification.get_cookie_preferences() && $('#cookie_notification').hasClass('active')) {
2989                                        // If the cookie banner is active focus on first focusable element in the cookie banner
2990                                        $(cookie_banner_first_focusable_element).focus();
2991                                    } else {
2992                                        $(firstAnchor).focus();
2993                                    }
2994                                }
2995                            });
2996                            $(firstAnchor).addClass('focustrap-first').bind('keydown.focusFirst', function (e) {
2997                                var keyCode = e.which;
2998                                if ($("this:focus") && (keyCode == 9 && e.shiftKey)) {
2999                                    e.preventDefault();
3000                                    if (cookie_banner_active && cookie_banner_focusable_elements.length && !window.archimedes.archimedes_core.cookie_notification.get_cookie_preferences() && $('#cookie_notification').hasClass('active')) {
3001                                        // If the cookie banner is active focus on last focusable element in the cookie banner
3002                                        $(cookie_banner_last_focusable_element).focus();
3003                                    } else {
3004                                        $(lastAnchor).focus();
3005                                    }
3006                                }
3007                            });
3008                            // If a first focusable element hasn't been specified, set focus to .focustrap-first element
3009                            if (!first_focusable_element_override && typeof first_focusable_element_override !== 'undefined' && $('.focustrap-first').length) {
3010                                $('.focustrap-first')[0].focus({preventScroll:true});
3011                            }
3012                        } else {
3013                            console.log('Accessibility error: element not found during focus trapping');
3014                        }
3015                    }
3016                }
3017                
3018            },
3019            
3020            focus_untrap: function(focus_after) {
3021                
3022                if (!$("body").hasClass('accessibility-functions-disabled')) {
3023                
3024                    var $focusAfter = $(focus_after);
3025                    
3026                    // Remove keydown listeners used for tab looping inside wrapper
3027                    $('.focustrap-first').unbind("keydown.focusFirst");
3028                    $('.focustrap-last').unbind("keydown.focusLast");
3029                    $('.cb-focustrap-first').unbind("keydown.focusFirstCB");
3030                    $('.cb-focustrap-last').unbind("keydown.focusLirstCB");
3031                    
3032                    $('.focustrap-focusable').each(function () {
3033                        if (typeof $(this).attr('data-tabindex') !== 'undefined' && !$(this).attr("data-disabled")) {
3034                            $(this).attr("tabindex", $(this).attr('data-tabindex'));
3035                        } else if ($(this).attr("data-disabled")) {
3036                            $(this).attr("tabindex", -1);
3037                        } else {
3038                            $(this).removeAttr("tabindex");
3039                        }
3040                    });
3041                    
3042                    $('.focustrap-item').removeClass('focustrap-item');
3043                    $('.focustrap-focusable').removeClass('focustrap-focusable focustrap-disabled-outside');
3044                    $('.focustrap-wrapper').removeClass('focustrap-wrapper');
3045                    $('.focustrap-first').removeClass('focustrap-first');
3046                    $('.focustrap-last').removeClass('focustrap-last');
3047                    if ( typeof $focusAfter != 'undefined' && $focusAfter.length ) {
3048                        $focusAfter.focus();
3049                    }
3050                    
3051                }
3052            
3053            },
3054            
3055            closeWithEscapeKey: function(close_button_element, focus_trap_element) {
3056                // This function allows you to close navigations and popups using the escape key
3057                // close_button_element should be a clickable element which will close the popup e.g a close button.
3058                
3059                if (!$("body").hasClass('accessibility-functions-disabled')) {
3060                
3061                    function escapeFunc(event) {
3062                        if (event.keyCode === 27 && (focus_trap_element ? (h.accessibility.is_current_popup(focus_trap_element)) : true) && !$('body').hasClass('share-dropdown-active')) {
3063                            event.preventDefault();
3064                            $(close_button_element).click();
3065                        }
3066                    }
3067                    
3068                    if (!$(focus_trap_element).hasClass('esc-key-listener-added')) {
3069                        $(document).bind('keydown.escKeyDown', escapeFunc);
3070                        $(focus_trap_element).addClass('esc-key-listener-added');
3071                    }
3072                
3073                }
3074            },
3075            
3076            popupLayerTracker: function(action, popup_element) {
3077                
3078                if (!$("body").hasClass('accessibility-functions-disabled')) {
3079                
3080                    // variable to shorten the calls so that the code is more readable
3081                    var accessibility = h.accessibility.global_variables;
3082                    
3083                    if (action == "length") {
3084                        return accessibility.popup_layer_tracking.length;
3085                    } else if (action == "opening") {
3086                        var $popupElement = $(popup_element);
3087                        if (accessibility.element_to_refocus_to) {
3088                            accessibility.popup_layer_tracking.push([$popupElement, accessibility.element_to_refocus_to]);
3089                        } else {
3090                            accessibility.popup_layer_tracking.push([$popupElement]);
3091                        }
3092                        $popupElement.first().attr("data-popup-layer", accessibility.popup_layer_tracking.length);
3093                    } else if (action == "closing") {
3094                        var $popupElement = accessibility.popup_layer_tracking[accessibility.popup_layer_tracking.length - 1];
3095                        if (accessibility.popup_layer_tracking[accessibility.popup_layer_tracking.length - 1].length > 1) {
3096                            accessibility.element_to_refocus_to_after_close = accessibility.popup_layer_tracking[accessibility.popup_layer_tracking.length - 1][1];
3097                        } else {
3098                            accessibility.element_to_refocus_to_after_close = false;
3099                        }
3100                        $popupElement[0].removeAttr("data-popup-layer");
3101                        accessibility.popup_layer_tracking.pop();
3102                    }
3103                }
3104                
3105            },
3106            
3107            click_and_tab_detection: function() {
3108                
3109                if (!$("body").hasClass('accessibility-functions-disabled')) {
3110                    try {
3111                        
3112                        // Listen for tab key being pressed which adds the 'tabbing-detected' class initiating accessibility features.
3113                        if (!$('body').hasClass('tabbing-listener-added')) {
3114                            $(document).unbind("keyup.tabListener");
3115                            $(document).bind("keyup.tabListener", function(e) {
3116                                if ( e.which == 9 && !e.target.form && !$('body').hasClass('tabbing-detected') ) {
3117                                    $('body').addClass('tabbing-detected');
3118                                    $('body').removeClass('clicking-detected');
3119                                    console.log('Accessibility mode enabled');
3120                                } else if ( e.which == 9 && e.target.form && !$('body').hasClass('form-tabbing-detected') ) {
3121                                    $('body').addClass('form-tabbing-detected');
3122                                    $('body').removeClass('clicking-detected');
3123                                }
3124                            });
3125                            $('body').addClass('tabbing-listener-added');
3126                        }
3127                        
3128                        // Listen for clicks which indicates mouse use and turns off accessibility features.
3129                        if (!$('body').hasClass('clicking-listener-added')) {
3130                            $(document).unbind("mouseup.clickListener");
3131                            $(document).bind("mouseup.clickListener", function(e) {
3132                                // e.hasOwnProperty('originalEvent') is used to check if it was clicked by an actual user or triggered programmatically e.g by a .click()
3133                                if (!$('body').hasClass('clicking-detected') && e.hasOwnProperty('originalEvent')) {
3134                                    if ($('body').hasClass('tabbing-detected')) {
3135                                        console.log('Accessibility mode disabled');
3136                                    }
3137                                    $('body').addClass('clicking-detected');
3138                                    $('body').removeClass('tabbing-detected form-tabbing-detected');
3139                                }
3140                            });
3141                            $('body').addClass('clicking-listener-added');
3142                        }
3143                        
3144                    } catch (e) {
3145                        console.log('Accessibility tabbing/clicking listeners failed to be added. This site may be using an older version of jQuery.')
3146                    }
3147                }
3148            },
3149            
3150            is_current_popup: function(focus_trap_element) {
3151                
3152                // This function returns true or false depending if the popup you supply it is the top level popup or not
3153                // The focus_trap_element you pass to the function must be the same as the trap_element supplied to the on_popup_opening function
3154                
3155                if (h.accessibility.popupLayerTracker("length") == $(focus_trap_element).attr("data-popup-layer")) {
3156                    return true;
3157                } else {
3158                    return false;
3159                }
3160            },
3161            
3162            misc: function() {
3163                // Pause the slideshow if any items within the slideshow receive focus
3164                if (!$('#ig_slideshow').hasClass('focus-listeners-added')) {
3165                    if (typeof $('body').focusin != 'undefined') {
3166                        $('#ig_slideshow .item a').focusin(function(){
3167                            $('#ig_slideshow').cycle('pause');
3168                        });
3169                    }
3170                    $('#ig_slideshow').addClass('focus-listeners-added');
3171                }
3172            },
3173            
3174            check_for_core_accessibility_stylesheet: function() {
3175                
3176                // This function checks if the core accessibility style sheet has been included, and then includes it if not.
3177                
3178                if (!$('body').hasClass('core-accessibility-stylesheet-checked')) {
3179                    
3180                    $('body').append('<div class="accessibility-stylesheet-exists-test"></div>');
3181                    
3182                    if ($(".accessibility-stylesheet-exists-test").css('font-family') != "accessibilitystylesheetexists") {
3183                        $('<link rel="stylesheet" href="/lib/archimedes/styles/accessibility.css" />').insertBefore('head style:first');
3184                    }
3185                    
3186                    $('.accessibility-stylesheet-exists-test').remove();
3187                    $('body').addClass('core-accessibility-stylesheet-checked');
3188                }
3189            },
3190            
3191            axe_automatic_audit: function() {
3192                
3193                //todo
3194                
3195                // Fix issues created when upgrading to latest axe core version
3196                // escape html in any of the descriptions (test on a page with a video)
3197                // Change issue count so it only shows violations and add a seperate count for items which need review
3198                // make panel not overlay page but instead resizes the window
3199                // FUTURE - Remove best practice rules from results?
3200                // FUTURE -  if high contrast mode is made, remove contrasts from audit?
3201                
3202                
3203                $(".run-axe-audit-button").unbind().click(function(){
3204                    
3205                    $(".axe-results-panel").removeClass("hide");
3206                    
3207                    // For accessibility - tracks which element to refocus on
3208                    try {
3209                        h.accessibility.global_variables.element_to_refocus_to = $(this);
3210                    } catch(error) {
3211                        console.error(error);
3212                    }
3213                    
3214                    // clear any previous audit results
3215                    $(".axe-dynamic-panel-container").empty();
3216                    
3217                    $("body").addClass('axe-panel-visible');
3218                    
3219                    $(".axe-panel-close-button").unbind().click(function(){
3220                        $("body").removeClass('axe-panel-visible');
3221                        setTimeout(function(){ $(".axe-results-panel").addClass("hide"); }, 400);
3222                        h.accessibility.on_popup_closing();
3223                    });
3224                    
3225                    window.scrollTo(0, 0);
3226                    
3227                    // Force header to show so that it can be tested
3228                    $(".layout-fixed-header #header").css({"transform": "none"});
3229                    // Hide nav which has the audit run button temporarily as it threw errors in the audit
3230                    $("#cms-frontend-toolbar .cms-frontend-toolbar-item ul").css({"display": "none"});
3231                    
3232                    axe.run({exclude: [['#cms-frontend-toolbar']]}, function (err, results) {
3233                        
3234                        if (err) {
3235                            $(".axe-dynamic-panel-container").append("<div class='axe-error-message'>Oops! Something went wrong! If you continue experiencing this issue please contact support at [email protected]</div>");
3236                            throw err;
3237                        }
3238                        
3239                        var number_of_incomplete_tests = 0;
3240                        var number_of_violations = 0;
3241                        var total_issues = 0;
3242
3243                        results.incomplete.forEach(function (issue) {
3244                            if (issue.nodes.length && typeof issue.error == 'undefined') {
3245                                number_of_incomplete_tests += issue.nodes.length;
3246                            }
3247                        });
3248                        
3249                        results.violations.forEach(function (issue) {
3250                            if (issue.nodes.length && typeof issue.error == 'undefined') {
3251                                number_of_violations += issue.nodes.length;
3252                            }
3253                        });
3254                        
3255                        total_issues = number_of_violations + number_of_incomplete_tests;
3256                        if  (total_issues >= 1) {
3257                            if (total_issues == 1) {
3258                                var issues_label = " issue"
3259                            } else if (total_issues >= 1) {
3260                                var issues_label = " issues"
3261                            }
3262                            
3263                            $(".axe-dynamic-panel-container").append("<div class='axe-total-container'><div class='axe-total-violations'>" + total_issues + issues_label + " found</div><button class='re-run-axe-audit-button'>Re-run audit</button></div>");
3264                            
3265                        } else if (total_issues == 0) {
3266                            $(".axe-dynamic-panel-container").append("<div class='axe-total-container'><div class='axe-total-violations no-violations'>No accessibility issues were found</div><button class='re-run-axe-audit-button'>Re-run audit</button></div><div class='manual-test-warning'>Please ensure that you also carry out all necessary manual accessibility testing as this audit can only check a 
3266small and limited amount of issues.</div>");
3267                        }
3268                        
3269                        // If there are violations - add them to the audit panel
3270                        if (results.violations && results.violations.length >= 1) {
3271                            
3272                            $(".axe-dynamic-panel-container").append("<h3>Violations</h3><div class='axe-violations-list'></div>")
3273                            
3274                            results.violations.forEach(function (issue, index) {
3275                                
3276                                var violation_items = ""
3277                                
3278                                issue.nodes.forEach(function (node) {
3279                                    
3280                                    violation_items += "<div class='violation-element-location-header'><h4>Element location</h4></div>"
3281                                    node.target.forEach(function (target) {
3282                                        violation_items += "<div class='violation-element-location'>" + target + "</div>"
3283                                    });
3284                                    
3285                                    if (node.html && node.html.length) {
3286                                        violation_items += "<h4>Element source</h4><div class='violation-element-source'><pre class='prettyprint lang-html'><xmp>" + node.html + "</xmp></pre></div>"
3287                                    }
3288                                    
3289                                    if (node.failureSummary && node.failureSummary.length) {
3290                                        var summary_split_by_line_returns = node.failureSummary.split("\n");
3291                                        violation_items += "<div class='failure-summary'>"
3292                                        summary_split_by_line_returns.forEach(function (line, index) {
3293                                            if (index == 0 || line == "Fix any of the following:") {
3294                                                if (index != 0) {
3295                                                    violation_items += "</ul>"
3296                                                }
3297                                                violation_items += "<h4>" + line + "</h4><ul class='failure-summary-list'>"
3298                                            } else if (line && line.length) {
3299                                                violation_items += "<li class='failure-summary-item'>" + line + "</li>"
3300                                            }
3301                                        });
3302                                        violation_items += "</ul></div>"
3303                                    }
3304                                    
3305                                });
3306                                    
3307                                $(".axe-violations-list").append(
3308                                    "<button class='violation-item' aria-expanded='false' aria-controls='violation-" + issue.id + "'><span class='description'>" + issue.help + "</span><span class='count' aria-label='. Number of issues is " + issue.nodes.length + "'>" + issue.nodes.length + "</span></button>\
3309                                    <div id='violation-" + issue.id + "' class='accordion-panel' hidden>" +
3310                                    
3311                                        "<div class='issue-impact'>Impact: <span class='issue-impact-status " + issue.impact + "'>" + issue.impact + "</span></div>" +
3312                                        
3313                                        "<div class='issue-description'>" +
3314                                            "<h4>Issue description</h4>" +
3315                                            "<a href=" + issue.helpUrl + " target='_blank'>" +
3316                                                issue.description +
3317                                            "</a>" +
3318                                        "</div>" +
3319                                        
3320                                        violation_items +
3321                                        
3322                                    "</div>"
3323                                )
3324                            });
3325                            
3326                        }
3327                        
3328                        // If there are issues which need reviewing - add them to the audit panel
3329                        if ( results.incomplete && results.incomplete.length >= 1 ) {
3330                            
3331                            if (results.incomplete.length > 1 || results.incomplete.length == 1 && typeof results.incomplete[0].error == 'undefined') {
3332                                $(".axe-dynamic-panel-container").append("<h3>Needs review</h3><div class='axe-needs-reviewing-list'></div>")
3333                            }
3334                            
3335                            results.incomplete.forEach(function (issue, index) {
3336                                
3337                                if (typeof issue.error == 'undefined') {
3338                                
3339                                    var review_items = ""
3340                                
3341                                    issue.nodes.forEach(function (node) {
3342                                        
3343                                        review_items += "<div class='review-element-location-header'><h4>Element location</h4></div>"
3344                                        node.target.forEach(function (target) {
3345                                            review_items += "<div class='review-element-location'>" + target + "</div>"
3346                                        });
3347                                        
3348                                        if (node.html && node.html.length) {
3349                                            review_items += "<h4>Element source</h4><div class='review-element-source'><pre class='prettyprint lang-html'><xmp>" + node.html + "</xmp></pre></div>"
3350                                        }
3351                                        
3352                                        if (node.any && node.any[0] && node.any[0].message) {
3353                                            review_items += "<h4>This issue needs reviewing because:</h4><div class='review-message'>" + node.any[0].message + "</div>"
3354                                        }
3355                                        
3356                                    });
3357                                    
3358                                    $(".axe-needs-reviewing-list").append(
3359                                        "<button class='needs-reviewing-item' aria-expanded='false' aria-controls='review-" + issue.id + "'><span class='description'>" + issue.help + "</span><span class='count' aria-label='. Number of issues is " + issue.nodes.length + "'>" + issue.nodes.length + "</span></button>" +
3360                                        "<div id='review-" + issue.id + "' class='accordion-panel' hidden>" +
3361                                        
3362                                            "<div class='issue-impact'>Impact: <span class='issue-impact-status " + issue.impact + "'>" + issue.impact + "</span></div>" +
3363                                            
3364                                            "<div class='issue-description'>" +
3365                                                "<h4>Issue description</h4>" +
3366                                                "<a href=" + issue.helpUrl + " target='_blank'>" +
3367                                                    issue.description +
3368                                                "</a>" +
3369                                            "</div>" +
3370                                            
3371                                            review_items +
3372                                            
3373                                        "</div>"
3374                                    )
3375                                }
3376                                
3377                            });
3378                        }
3379                        
3380                        // Highlight syntax for element source code snippet https://github.com/google/code-prettify
3381                        PR.prettyPrint();
3382                        
3383                        // click event to open and close accordians
3384                        $(".axe-violations-list button.violation-item, .axe-needs-reviewing-list button.needs-reviewing-item").unbind().click(function(){
3385                            var accordian_panel_id = $(this).attr("aria-controls");
3386                            
3387                            // add or remove active class to accordian button
3388                            $(".axe-violations-list button:not([aria-controls=" + accordian_panel_id + "])").removeClass("active");
3389                            $(".axe-needs-reviewing-list button:not([aria-controls=" + accordian_panel_id + "])").removeClass("active");
3390                            $(this).toggleClass("active");
3391                            if ($(this).attr("aria-expanded") == "true") {
3392                                $(this).attr("aria-expanded", false)
3393                            } else {
3394                                $(this).attr("aria-expanded", true)
3395                            }
3396                            
3397                            // hide or show violations accordian panel depending on if its active or not
3398                            $(".axe-violations-list .accordion-panel:not(#" + accordian_panel_id + ")").attr("hidden", true);
3399                            if ($(".axe-violations-list #" + accordian_panel_id).attr("hidden")) {
3400                                $(".axe-violations-list #" + accordian_panel_id).removeAttr("hidden");
3401                            } else {
3402                                $(".axe-violations-list #" + accordian_panel_id).attr("hidden", true);
3403                            }
3404                            
3405                             // hide or show "needs review" accordian panel depending on if its active or not
3406                            $(".axe-needs-reviewing-list .accordion-panel:not(#" + accordian_panel_id + ")").attr("hidden", true);
3407                            if ($(".axe-needs-reviewing-list #" + accordian_panel_id).attr("hidden")) {
3408                                $(".axe-needs-reviewing-list #" + accordian_panel_id).removeAttr("hidden");
3409                            } else {
3410                                $(".axe-needs-reviewing-list #" + accordian_panel_id).attr("hidden", true);
3411                            }
3412                        });
3413                        
3414                        // Highlighter button - highlights elements with issues
3415                        var focus_area
3416                        var original_tab_index
3417                        $(".violation-element-location, .review-element-location").unbind().hover(function(){
3418                            focus_area = $(this).text();
3419                            original_tab_index = $(focus_area).attr('tabindex');
3420                            $(focus_area).attr('tabindex', "-1").focus().css({"outline": "dashed"});
3421                        }, function(){
3422                            $(focus_area).css("outline", "").attr('tabindex', original_tab_index);
3423                        });
3424                        
3425                        
3426                        // click event for button to re-run audit
3427                        $(".re-run-axe-audit-button").unbind().click(function(){
3428                            $("#cms-frontend-toolbar-buttons .run-axe-audit-button").trigger( "click" );
3429                        });
3430                        
3431                        h.accessibility.on_popup_opening('.axe-results-panel', false, '.axe-panel-close-button');
3432                        
3433                        // console.log(results);
3434                    });
3435                    // Allow header to be hidden again once audit is completed
3436                    $(".layout-fixed-header #header").css({"transform": ""});
3437                    // Show nav again which has the audit run button in it
3438                    $("#cms-frontend-toolbar .cms-frontend-toolbar-item ul").css({"display": ""});
3439                    
3440                    
3441                });
3442            
3443            },
3444            
3445            role_button: function() {
3446                // Add required tabindex and keyboard events for elements which have role="button" set
3447                
3448                // Remove any existing roleButton event listers to prevent duplications
3449                $('a[role="button"], span[role="button"], div[role="button"]').unbind("
3449keydown.roleButton");
3450                
3451                $('a[role="button"]')
3452                    .bind('keydown.roleButton', function (e) {
3453                        var keyCode = e.which;
3454                        if (keyCode == 32) {
3455                            e.preventDefault();
3456                            $(this).click();
3457                        }
3458                    })
3459                    
3460                $('span[role="button"], div[role="button"]')
3461                    .bind('keydown.roleButton', function (e) {
3462                        var keyCode = e.which;
3463                        if (keyCode == 32 || keyCode == 13) {
3464                            e.preventDefault();
3465                            $(this).click();
3466                        }
3467                    })
3468                    .attr('tabindex', 0)
3469                    
3470            },
3471            
3472            show_image_alt_tags: function() {
3473                
3474                if ($('body').hasClass('show-image-alt-tags')) {
3475                    $("img:not(.alt-tag-checked):not(.zoomImg)").each(function(){
3476                        var alt_tag = $(this).get(0).alt;
3477                        var message = '';
3478                        var style = '';
3479                        
3480                        if (!$(this).attr('alt') && typeof $(this).attr('alt') == 'undefined') {
3481                            style = 'background-color:#D10000;color:white;';
3482                            message = '<strong>Warning! This image has no alt tag.</strong>';
3483                        } else if (!$(this).attr('alt') && alt_tag == '') {
3484                            style += 'background-color:orange;color:black;';
3485                            message = '<strong>Needs review:</strong> Image has an alt tag but it is empty.';
3486                        } else {
3487                            style += 'background-color:green;color:white;';
3488                            message = alt_tag;
3489                        }
3490                        $(this).addClass('alt-tag-checked').after('<div class="alt-tag-notification" style="' + style +'">' + message + '</div>');
3491                        
3492                    });
3493                }
3494                
3495            },
3496            
3497            focus_without_scroll: function(element_to_focus_on, custom_scroll_element) {
3498                
3499                // This function prevents the page or element from scrolling when the given element is focused upon
3500                // custom_scroll_element - pass a custom element which you want to prevent scrolling on rather than the whole document
3501                
3502                // Example use on an artwork detail popup - h.accessibility.focus_without_scroll($(this), '#popup_content');
3503                
3504                if (element_to_focus_on && typeof element_to_focus_on !== "undefined") {
3505                    var $scroll_element = $(document);
3506                    if (custom_scroll_element && typeof custom_scroll_element !== "undefined" && $(custom_scroll_element).length) {
3507                        $scroll_element = $(custom_scroll_element);
3508                    }
3509                    
3510                    var scroll_top_before_focus = $scroll_element.scrollTop();
3511                    $(element_to_focus_on).focus();
3512                    $scroll_element.scrollTop(scroll_top_before_focus);
3513                }
3514                
3515            }
3516            
3517        }
3518
3519    };
3520
3521    window.g = {
3522
3523        reload: function() {
3524            window.document.location.href = window.document.location.href.split('#')[0];
3525        },
3526
3527        has_local_storage: function() {
3528
3529            var hasLocalStorage = false;
3530            try {
3531                if (localStorage) {
3532                    hasLocalStorage = true;
3533                }
3534            } catch(e) {
3535                // no localStorage!
3536            }
3537            return hasLocalStorage;
3538        },
3539
3540        has_local_storage_test: function() {
3541
3542            hasLocalStorage = false;
3543            // Older iOS Safari, in Private Browsing Mode, *looks* like it supports localStorage but all calls to setItem
3544            // throw QuotaExceededError. We're going to detect this and just silently drop any calls to setItem
3545            // to avoid the entire page breaking, without having to do a check at each usage of Storage.
3546            if (typeof localStorage === 'object') {
3547                try {
3548                    localStorage.setItem('localStorage', 1);
3549                    localStorage.removeItem('localStorage');
3550                    hasLocalStorage = true;
3551                } catch (e) {
3552                    // no localStorage!
3553                }
3554            }
3555            return hasLocalStorage;
3556        }
3557
3558    };
3559
3560
3561    // Define some core jquery plugins (please ask Peter if you want to add more)
3562    // Note that these plugins are defined separately in back-end js helper files
3563
3564    $.fn.extend({
3565
3566        floatCenter: function() {
3567            /*
3568             * $.floatCenter()
3569             *
3570             * Center an object in the window and bring it to the top. See
3571             * also $.toWin()
3572             *
3573             * Author: Peter Chater
3574             * Date: May 21, 2010
3575             */
3576            $(this).show();
3577            var scrollTop = $('html,body').scrollTop(); // see comments on http://api.jquery.com/scrollTop/
3578            var l = ($(window).width() - $(this).width()) / 2;
3579            var t = (($(window).height() - $(this).height()) / 2.5) + scrollTop;
3580            $(this).css({'left': l + 'px', 'top': t + 'px'}
3580).toTop();
3581            return this;
3582        },
3583
3584        centered: function() {
3585            /*
3586             * $('#my_element').centered()
3587             *
3588             * Center an object within its parent element
3589             *
3590             * Author: Peter Chater
3591             * Date: Aug 2, 2010
3592             */
3593            $(this).show().css({'margin-left': '0px', 'float': 'left'});
3594            var w = $(this).width() + 1; // allow 1 extra pixel to allow for rounding
3595            $(this).css({
3596                'width': w + 'px',
3597                'margin-left': 'auto',
3598                'margin-right': 'auto',
3599                'float': 'none'
3600            });
3601            return this;
3602        },
3603
3604        toWin: function() {
3605            /*
3606             * $.toWin()
3607             * 
3608             * Turns an element into a faux 'window' in the page,
3609             * with a simple close box. Any element may be used - it will
3610             * be detached from its current position on the DOM and displayed
3611             * absolutely positioned.
3612             *
3613             * Note that the css for this is currently not fully backwards-
3614             * compatible it uses custom drop-shadow and rounded-corners
3615             * available in contemporary browsers. It should default to a
3616             * rectangle in older browsers.
3617             *
3618             * Usage:
3619             *
3620             * $('#my_element').toWin();
3621             *
3622             * Note that the element should initially be set to display none.
3623             *
3624             * To cancel the window, a button or link should be placed inside
3625             * the element whose click action will be:
3626             *
3627             * $('#my_element').cancelWin();
3628             *
3629             * Author: Peter Chater
3630             * Date: May 24, 2010
3631             * Requires: TopZIndex (see below)
3632             */
3633            window.archimedes.current_towin_el = this;
3634            $(this)
3635                .setMask()
3636                .addClass('archimedes_towin')
3637                .css({'position': 'absolute'})
3638                .floatCenter()
3639                .fadeTo('fast', 1.0)
3640                .setCloseBox();
3641            return this;
3642        },
3643
3644        cancelWin: function() {
3645            /*
3646             * $.cancelWin()
3647             *
3648             * Remove a 'window' created with the $.toWin() method.
3649             *
3650             * Note that this method should be called on the element
3651             * that was turned into a 'win', e.g.:
3652             *
3653             * $('#my_element').cancelWin();
3654             *
3655             * ...or simply on the 'body' element:
3656             *
3657             * $('body').cancelWin();
3658             *
3659             * Author: Peter Chater
3660             * Date: May 24, 2010
3661             */
3662            $('#archimedes_mask_el').fadeOut();
3663            $('#archimedes_closebox_el').fadeOut();
3664            $(window.archimedes.current_towin_el).fadeOut();
3665            return this;
3666        },
3667
3668        closeWin: function() {
3669            // synonymous with $.cancelWin() above...
3670            $(this).cancelWin();
3671            return this;
3672        },
3673
3674        setCloseBox: function() {
3675            /*
3676             * $.setCloseBox()
3677             *
3678             * Give an object a closebox, and a handler function so that it
3679             * knows what to do...
3680             *
3681             * You can remove it again with:
3682             * $('#archimedes_closebox_el').hide();
3683             *
3684             * Author: Peter Chater
3685             * Date: May 24, 2010
3686             * Requires: TopZIndex (see below)
3687             */
3688            var l = $(this).offset().left + $(this).width() + 40;
3689            var t = $(this).offset().top - 10;
3690            if (!h.element_exists('#archimedes_closebox_el')) {
3691                $('body').append('<div id="archimedes_closebox_el"></div>');
3692            }
3693            $('#archimedes_closebox_el')
3694                .css({
3695                    'position': 'absolute',
3696                    'left': l + 'px',
3697                    'top': t + 'px',
3698                    'width': '30px',
3699                    'height': '30px',
3700                    'background': 'transparent url(/lib/archimedes/images/archimedes_closebox.png) top left no-repeat'
3701                    })
3702                .toTop()
3703                .show('fast')
3704                .click(function() {
3705                    $('body').cancelWin();
3706                });
3707            return this;
3708        },
3709
3710        setMask: function() {
3711            /*
3712             * $.setMask()
3713             *
3714             * Sets a mask element behind the current element. Used in
3715             * conjunction with toWin() above...
3716             *
3717             * Author: Peter Chater
3718             * Date: May 24, 2010
3719             * Requires: TopZIndex (see below)
3720             */
3721            var scrollTop = $('html,body').scrollTop(); // see comments on http://api.jquery.com/scrollTop/
3722            var exists = h.element_exists('archimedes_mask_el');
3723            if (!h.element_exists('archimedes_mask_el')) {
3724                $('body').append('<div id="archimedes_mask_el" style="display: none;"></div>');
3725            }
3726            $('#archimedes_mask_el')
3727                .fadeTo(1, 0.001)
3728                .css({
3729                    'position': 'absolute',
3730                    'top': '0',
3731                    'left': '0',
3732                    'width': $(window).width() + 'px',
3733                    'height': (scrollTop + $(window).height()) + 'px',
3734                    'background': '#333'
3735                    })
3736                .toTop()
3737                .fadeTo('fast', 0.3)
3738                .click(function() {
3739                    $('body').cancelWin();
3740                });
3741            return this;
3742        },
3743
3744        toTop: function() {
3745            /*
3746             * $.toTop() - bring an object to the top. This assumes the object
3747             * is already absolutely positioned.
3748             *
3749             * Author: Peter Chater
3750             * Date: May 21, 2010
3751             * Requires: TopZIndex (see below)
3752             */
3753            $(this).show().css({'z-index': $.topZIndex() + 1});
3754            return this;
3755        }
3756
3757    });
3758
3759    $.fn.makeAbsolute = function(rebase) {
3760        // Thanks, Rick Strahl!
3761        // http://www.west-wind.com/Weblog/posts/517320.aspx
3762        return this.each(function() {
3763            var el = $(this);
3764            var pos = el.position();
3765            el.css({ position: "absolute",
3766                marginLeft: 0, marginTop: 0,
3767                top: pos.top, left: pos.left });
3768            if (rebase)
3769                el.remove().appendTo("body");
3770        });
3771    };
3772
3773
3774    $(document).ready(function() {
3775        
3776        $('body').addClass('browser-js-enabled');
3777
3778        window.archimedes.archimedes_toolbar.init();
3779        window.archimedes.archimedes_core.init();
3780
3781        $('blockquote').wrapInner('<span></span>');
3782
3783        // make heading images work in ie6
3784        // swap png for jpeg and add a unique class
3785        if ($.browser.msie && $.browser.version < 7.0) {
3786            $('.heading-image').addClass('heading-image-ie6');
3787            $('.heading-image img').attr('src', function() {
3788                var newFilename = $(this).attr('src').replace('.png','.jpg');
3789                $(this).attr('src', newFilename);
3790            });
3791        }
3792
3793        // center all elements with a class of .centered
3794        $('.centered').each(function() {
3795            $(this).centered();
3796        });
3797
3798        // clear after all elements with a class of .clearafter.
3799        // Don't be tempted to remove this - there is a 'clearwithin'
3800        // class which appends a div of class 'clear' *within* the given
3801        // element. This class appends a div of class="clear" *afterwards*
3802        $('.clearafter').after('<div class="clear"></div>');
3803
3804
3805        // Login form
3806        $('#login_form .large_button a', this).click(function() {
3807            $('#login_form').submit();
3808            return false;
3809        });
3810
3811        // Fancybox images
3812        if (h.element_exists('a.fancybox')) {
3813            // first we need to find any captions in div.embedded_img elements
3814            // and move them to the alt attributes...
3815            if (h.element_exists('div.embedded_img')) {
3816                $('div.embedded_img').each(function() {
3817                    var obj = $(this);
3818                    var caption = h.escape_quotes($('div.embedded_img_caption', obj).html());
3819                    var caption2 = h.escape_quotes($('div.embedded_img_caption2', obj).html());
3820                    $('div.embedded_img_caption2', obj).html('');
3821                    $('a.fancybox', obj).attr('alt', caption2 || caption);
3822                    $('div.embedded_img_img img', obj).attr('alt', caption);
3823                });
3824            }
3825            // now prepare fancybox elements
3826            $("a.fancybox").fancybox({
3827                'overlayShow': true,
3828                'overlayOpacity': 0.7,
3829                'overlayColor': '#d9d9d9',
3830                'imageScale': 'true',
3831                'zoomOpacity': 'true'
3832            });
3833        }
3834
3835
3836    });
3837
3838
3839})(jQuery);
3840
3841
3842
3843
3844
3845
3846// FL Win
3847// Must be a better way of doing this...
3848
3849        var fl_win = function(params) {
3850            return fl_popUpWin(params);
3851        }
3852
3853        var fl_popUpWins = [];
3854
3855        function fl_popUpWin(url, name, w, h, scrollbars, resizable, menubar, status) {
3856            if (typeof url == 'object') {
3857                /*
3858                    As of Nov 2007, you may use the shorter alias to this function, 'fl_win',
3859                    and may specify parameters as an object, using only the first argument, e.g.:
3860
3861                        fl_win({
3862                            url: '/my_popup.html'
3863                        });
3864
3865                        fl_win({
3866                            url: '/my_popup.html',
3867                            name: 'PopUp',
3868                            width: 500,
3869                            height: 400,
3870                            scrollbars: true,
3871                            resizable: true,
3872                            menubar: true,
3873                            status: true
3874                        });
3875
3876                    No parameters are required, however 'url' is highly recommended!
3877                    When using the 'params' method, 'fl_win' is an alias for 'fl_popUpWin', e.g.:
3878                    fl_popUpWin({...params...}) will work in the same manner
3879                */
3880                // set up defaults
3881                var params = url;
3882                var url = (params.url) ? params.url : "http://www.artlogic.net/flgui/images/shim.gif";
3883                var name = (params.name) ? params.name : "Window_" + fl_popUpWins.length;
3884                var w = (params.width) ? params.width : 550;
3885                var h = (params.height) ? params.height : 550;
3886                // as of JAN 2009, the height is then adjusted to in accordance with the browser...
3887                h = win_adjust_h(h);
3888                var scrollbars = (params.scrollbars) ? "yes" : "no"
3889                var resizable = (params.resizable) ? "yes" : "no"
3890                var resizable = (params.resizable) ? "yes" : "no"
3891                var menubar = (params.menubar) ? "yes" : "no"
3892                var status = (params.status) ? "yes" : "no"
3893            } else {
3894                var w = (w) ? w : 550;
3895                var h = (h) ? h : 550;
3896                var scrollbars = (scrollbars) ? "yes" : "no";
3897                var resizable = (resizable) ? "yes" : "no";
3898                var menubar = (menubar) ? "yes" : "no";
3899                var status = (status) ? "yes" : "no";
3900            }
3901            var l=(screen.availWidth-w)/2;
3902            var t=((screen.availHeight-h)/3)-40;
3903            if (!fl_popUpWins[name] || fl_popUpWins[name].closed) {
3904                fl_popUpWins[name] = window.open(url,name,'width=' + w + ',height=' + h + ',top=' + t + ',left=' + l + ',scrollbars=' + scrollbars + ',resizable=' + resizable + ',menubar=' + menubar + ',status=' + status);
3905                if (fl_popUpWins[name]) { // the window may have been blocked by a pop-up blocker
3906                    fl_popUpWins[name].focus();
3907                } else {
3908                    alert("Please ensure that you allow pop-up windows for this application as they may contain important messages or functionality. Please update your browser preferences and reload this page.");
3909                }
3910            } else if (fl_popUpWins[name]) { // the window may have been blocked by a pop-up blocker
3911                fl_popUpWins[name].focus();
3912            }
3913        }
3914
3915        var browser = {
3916
3917            init: function() {
3918                this.bname = navigator.appName;
3919                this.ver = parseInt(navigator.appVersion);
3920                this.agt = navigator.userAgent.toLowerCase();
3921                this.mac = (this.agt.indexOf("mac") > -1);
3922                this.win = (this.agt.indexOf("win") > -1);
3923                this.x11 = (this.agt.indexOf("x11") > -1);
3924                this.opera = (this.agt.indexOf("opera") > -1); // must be above IE4 as Opera users can set Opera to 'identify' as IE in preferences,
3925                this.safari = (this.agt.indexOf("safari") > -1);
3926                this.firefox = (this.agt.indexOf("firefox") > -1);
3927                this.camino = (this.agt.indexOf("camino") > -1);
3928                this.ns = this.netscape = (this.bname == "Netscape");
3929                this.ie = (this.bname == "Microsoft Internet Explorer" && !this.opera);
3930                this.iemac = (this.ie && this.mac);
3931                this.get_version(); // must be run before browser-version-specific options, below
3932                this.tweaks();
3933                this.ns3 = (this.ns && this.ver < 4);
3934                this.ns4 = (this.ns && this.ver >= 4 && this.ver < 5);
3935                this.ns6 = (this.ns && this.ver >= 5);
3936                this.ie3 = (this.ie && this.ver < 4);
3937                this.ie4 = (this.ie && this.ver >= 4);
3938                this.ie4win=(this.ie4 && this.win);
3939                this.iewin = (this.ie && this.win);
3940                this.ie4mac=(this.ie4 && this.mac);
3941            },
3942
3943            get_version: function() {
3944                if (this.agt.indexOf('firefox/') > -1) {
3945                    this.ver = parseInt(this.agt.split('firefox/')[1].split(' ')[0]);
3946                } else if (this.safari && this.agt.indexOf('version/') > -1) {
3947                    this.ver = parseInt(this.agt.split('version/')[1].split(' ')[0]);
3948                } else if (this.ie) {
3949                    this.ver = parseInt(this.agt.split(' msie ')[1].split(';')[0]);
3950                }
3951            },
3952
3953            tweaks: function() {
3954                if (this.camino) {
3955                    this.firefox = false;
3956                }
3957            }
3958
3959        };
3960
3961        browser.init();
3962
3963        var win_adjust_h = function(h) {
3964            if (browser.firefox && browser.ver >= 3) {
3965                h += 30;
3966            } else if (browser.firefox && browser.ver >= 2) {
3967                h += 10;
3968            };
3969            return h
3970        };
3971
3972
3973
3974// Email address obfuscator
3975
3976function mangle() {
3977    if (!document.getElementsByTagName && !document.createElement &&
3978        !document.createTextNode) return;
3979    var nodes = document.getElementsByTagName("span");
3980    for(var i=nodes.length-1;i>=0;i--) {
3981        if (nodes[i].className=="fixEmail") {
3982            var at = / at /;
3983            var dot = / dot /g;
3984            var node = document.createElement("a");
3985            var address = nodes[i].firstChild.nodeValue;
3986
3987            address = address.replace(at, "@");
3988            address = address.replace(dot, ".");
3989
3990            node.setAttribute("href", "mailto:"+address);
3991            node.appendChild(document.createTextNode(address));
3992
3993            var prnt = nodes[i].parentNode;
3994            for(var j=0;j<prnt.childNodes.length;j++)
3995                if (prnt.childNodes[j] == nodes[i]) {
3996                    if (!prnt.replaceChild) return;
3997                    prnt.replaceChild(node, prnt.childNodes[j]);
3998                    break;
3999                }
4000        }
4001    }
4002}
4003
4004
4005// Build form elements
4006
4007    // For accessibile forms.
4008
4009    function showForms() {
4010    	
4011        $('.jsSubmit').css('display','block');
4012        $('.nojsSubmit').css('display','none');
4013    }
4014
4015
4016// Stop image flickering in IE
4017
4018    try {
4019        document.execCommand("BackgroundImageCache", false, true);
4020    } catch(err) {}
4021
4022
4023// Run everything
4024
4025    $(document).ready(function() {
4026        showForms();
4027        mangle();
4028    });
4029
4030
4031
4032// Third party plugins required in the above
4033
4034/*
4035    TopZIndex 1.1 (September 23, 2009) plugin for jQuery
4036    http://topzindex.googlecode.com/
4037    Copyright (c) 2009 Todd Northrop
4038    http://www.speednet.biz/
4039    Licensed under GPL 3, see  <http://www.gnu.org/licenses/>
4040*/
4041(function(a){a.topZIndex=function(b){return Math.max(0,Math.max.apply(null,a.map(a(b||"body *:visible"),function(d){return parseInt(a(d).css("z-index"))||null})))};a.fn.topZIndex=function(b){if(this.length===0){return this}b=a.extend({increment:1,selector:"body *:visible"},b);var d=a.topZIndex(b.selector),c=b.increment;return this.each(function(){a(this).css("z-index",d+=c)})}})(jQuery);
4042
4043
4044/* jquery.centerInWindow
4045 * A plug-in to center an element in the window. Based on:
4046 * http://stackoverflow.com/questions/210717/using-jquery-to-center-a-div-on-the-screen
4047 */
4048jQuery.fn.centerInWindow = function (options) {
4049    var options = $.extend({
4050        'close_box': true
4051    }, options || {});
4052    $(this).css("position","absolute");
4053    var t = (($(window).height() - $(this).outerHeight()) / 2) + $(window).scrollTop();
4054    var l = (($(window).width() - $(this).outerWidth()) / 2) + $(window).scrollLeft();
4055    $(this).css("top", t + "px");
4056    $(this).css("left", l + "px");
4057    var el_id = $(this).attr('id');
4058    if (options.close_box) {
4059        var close_el_id = 'center_in_window_closebox';
4060        if (!h.element_exists(close_el_id)) {
4061            $('body').append('<div id="' + close_el_id + '" class="_closebox">&nbsp;</div>')
4062            $('#center_in_window_closebox').css({
4063                'position': 'absolute',
4064                'left': (l + $(this).outerWidth() - 15) + 'px',
4065                'top': (t - 10) + "px",
4066                'width': '30px',
4067                'height': '30px',
4068                'background': 'transparent url(/lib/archimedes/images/close.png) top left no-repeat'
4069            }).click(function() {
4070                $('#center_in_window_closebox').hide();
4071                $('#' + el_id).hide(200);
4072            });
4073            $('.close-btn', '#' + el_id).click(function() {
4074                $('#center_in_window_closebox').hide();
4075                $('#' + el_id).hide(200);
4076            });
4077        }
4078    }
4079    return this;
4080}

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.