PageSourceSearch

https://mileschile.cl/wp-content/plugins/portfolio-elementor/assets/js/custom-portfolio-lightbox.js?ver=3.2.2

js mileschile.cl collected 2026-09-24 19:03:34 UTC 11,553 bytes, 296 lines download raw bytes

1// Function to sanitize captions and prevent XSS
2function sanitizeElptCaption(caption) {
3    if (typeof caption !== 'string') return caption;
4    
5    // Remove any HTML to prevent XSS
6    var tempDiv = jQuery('<div>');
7    tempDiv.text(caption);
8    return tempDiv.html(); // Returns escaped text
9}
10
11// Safe function to decode HTML entities without executing JavaScript
12function safeDecodeHtml(str) {
13    if (typeof str !== 'string') return str;
14    
15    // Check if it contains dangerous patterns before decoding
16    var dangerousPatterns = [
17        /&lt;script/i,
18        /&lt;img[^&]*onerror/i,
19        /&lt;[^&]*on\w+=/i,
20        /javascript:/i,
21        /&lt;iframe/i,
22        /&lt;object/i,
23        /&lt;embed/i
24    ];
25    
26    for (var i = 0; i < dangerousPatterns.length; i++) {
27        if (dangerousPatterns[i].test(str)) {
28            // If it contains dangerous patterns, return only text without decoding
29            return str.replace(/&[#\w]+;/g, '').replace(/[<>]/g, '');
30        }
31    }
32    
33    // If it doesn't contain dangerous patterns, decode safely
34    var txt = document.createElement('textarea');
35    txt.innerHTML = str;
36    var decoded = txt.value;
37    
38    // Remove any remaining HTML tags as an extra security measure
39    decoded = decoded.replace(/<[^>]*>/g, '');
40    
41    return decoded;
42}
43
44// Function to sanitize all images in the DOM
45function sanitizeAllImages() {
46    jQuery('.elpt-portfolio img[title], a.elpt-portfolio-lightbox img[title]').each(function() {
47        var $img = jQuery(this);
48        var originalTitle = $img.attr('title');
49        if (originalTitle) {
50            // Decode HTML entities safely, then sanitize
51            var decodedTitle = safeDecodeHtml(originalTitle);
52            var sanitizedTitle = sanitizeElptCaption(decodedTitle);
53            $img.attr('title', sanitizedTitle);
54        }
55    });
56}
57
58// Execute sanitization immediately
59jQuery(document).ready(function() {
60    sanitizeAllImages();
61});
62
63jQuery(window).on('load', function () {
64    // Sanitize again on load
65    sanitizeAllImages();
66    
67    if (jQuery(".elpt-portfolio-content").length) {
68        
69        // Process video links with data attributes
70        jQuery('a.elpt-portfolio-video-lightbox').each(function() {
71            var videoUrl = jQuery(this).attr('data-video');
72            if (videoUrl) {
73                // Modify link behavior to open video in lightbox
74                jQuery(this).on('click', function(e) {
75                    e.preventDefault();
76                    
77                    // Open lightbox with video
78                    if (videoUrl.indexOf('youtube.com') > -1 || videoUrl.indexOf('youtu.be') > -1) {
79                        // Youtube video
80                        var videoId = getYoutubeId(videoUrl);
81                        if (videoId) {
82                            // Usar o lightbox do SimpleLightbox
83                            var embedUrl = 'https://www.youtube.com/embed/' + videoId + '?autoplay=1&rel=0';
84                            openVideoLightbox(embedUrl);
85                        }
86                    } else if (videoUrl.indexOf('vimeo.com') > -1) {
87                        // Vimeo video
88                        var videoId = getVimeoId(videoUrl);
89                        if (videoId) {
90                            // Usar o lightbox do SimpleLightbox
91                            var embedUrl = 'https://player.vimeo.com/video/' + videoId + '?autoplay=1&title=0&byline=0&portrait=0';
92                            openVideoLightbox(embedUrl);
93                        }
94                    }
95                    
96                    return false;
97                });
98            }
99        });
100        
101        // Prevent Elementor Lightbox from being triggered for our items
102        jQuery('a.elpt-portfolio-lightbox, a.elpt-portfolio-video-lightbox').each(function() {
103            // Remove any class that might trigger Elementor's lightbox
104            jQuery(this).removeClass('elementor-clickable');
105            // Add attribute that prevents Elementor from opening its lightbox
106            jQuery(this).attr('data-elementor-open-lightbox', 'n
106o');
107            
108            // Prevent event propagation to avoid Elementor capturing it (except for videos that already have handlers)
109            if (!jQuery(this).hasClass('elpt-portfolio-video-lightbox') || !jQuery(this).attr('data-video')) {
110                jQuery(this).on('click', function(e) {
111                    e.stopPropagation();
112                });
113            }
114        });
115        
116        // Sanitize again before initializing lightbox
117        sanitizeAllImages();
118        
119        // Initialize lightbox for images with safe configuration
120        var lightboxInstance = jQuery('a.elpt-portfolio-lightbox').simpleLightbox({
121            captions: true,
122            disableScroll: false,
123            rel: true,
124            // Hook to sanitize captions in real time
125            additionalHtml: false
126        });
127        
128                // Override SimpleLightbox library to use text instead of HTML
129        // Intercepts when captions are added
130        var originalAppendTo = jQuery.fn.appendTo;
131        jQuery.fn.appendTo = function(selector) {
132            var result = originalAppendTo.apply(this, arguments);
133            
134            // If it's a caption being added
135            if (this.hasClass && this.hasClass('sl-caption')) {
136                var captionContent = this.html();
137                if (captionContent) {
138                    var decodedContent = safeDecodeHtml(captionContent);
139                    var sanitizedContent = sanitizeElptCaption(decodedContent);
140                    this.text(sanitizedContent); // Use text() instead of html()
141                }
142            }
143            
144            return result;
145        };
146        
147                // Intercept and sanitize captions dynamically
148        // Observe DOM changes to sanitize new captions
149        var observer = new MutationObserver(function(mutations) {
150            mutations.forEach(function(mutation) {
151                if (mutation.addedNodes) {
152                    mutation.addedNodes.forEach(function(node) {
153                        if (node.nodeType === 1) { // Element node
154                            var $node = jQuery(node);
155                            if ($node.hasClass('sl-caption') || $node.find('.sl-caption').length > 0) {
156                                var $caption = $node.hasClass('sl-caption') ? $node : $node.find('.sl-caption');
157                                var captionHtml = $caption.html();
158                                if (captionHtml) {
159                                    var decodedCaption = safeDecodeHtml(captionHtml);
160                                    var sanitizedCaption = sanitizeElptCaption(decodedCaption);
161                                    $caption.text(sanitizedCaption); // Use .text() instead of .html()
162                                }
163                            }
164                        }
165                    });
166                }
167            });
168        });
169        
170        // Observe changes in the body
171        observer.observe(document.body, {
172            childList: true,
173            subtree: true
174        });
175    }
176    
177    // Function to get YouTube video ID from URL
178    function getYoutubeId(url) {
179        var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
180        var match = url.match(regExp);
181        return (match && match[7].length == 11) ? match[7] : false;
182    }
183    
184    // Function to get Vimeo video ID from URL
185    function getVimeoId(url) {
186        var regExp = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/;
187        var match = url.match(regExp);
188        return match ? match[5] : false;
189    }
190    
191    // Function to open lightbox with embedded video
192    function openVideoLightbox(embedUrl) {
193        // Create a dark overlay
194        var overlay = jQuery('<div id="elpt-video-overlay"></div>')
195            .css({
196                'position': 'fixed',
197                'top': 0,
198                'left': 0,
199                'width': '100%',
200                'height': '100%',
201                'background-color': 'rgba(0,0,0,0.9)',
202                'z-index': 9999,
203                'display': 'flex',
204                'justify-content': 'center',
205                'align-items': 'center'
206            })
207            .appendTo('body');
208            
209        // Create video container (responsive size computed in sizeVideoContainer)
210        var videoContainer = jQuery('<div id="elpt-video-container"></div>')
211            .css({
212                'position': 'relative',
213                'box-sizing': 'border-box'
214            })
215            .appendTo(overlay);
216
217        // Create video iframe
218        var iframe = jQuery('<iframe></iframe>')
219            .attr({
220                'src': embedUrl,
221                'frameborder': '0',
222                'allowfullscreen': 'true',
223                'allow': 'autoplay; fullscreen'
224            })
225            .css({
226                'position': 'absolute',
227                'top': 0,
228                'left': 0,
229                'width': '100%',
230                'height': '100%'
231            })
232            .appendTo(videoContainer);
233
234        // Size the container to fit the viewport while keeping a 16:9 ratio.
235        // Constrained by both width (90vw, max 900px) and height (90vh) so it
236        // never overflows on mobile or with vertical videos. Recomputed on resize.
237        function sizeVideoContainer() {
238            var maxWidth = Math.min(window.innerWidth * 0.9, 900);
239            var maxHeight = window.innerHeight * 0.9;
240            var width = maxWidth;
241            var height = width * 9 / 16;
242            if (height > maxHeight) {
243                height = maxHeight;
244                width = height * 16 / 9;
245            }
246            videoContainer.css({
247                'width': Math.round(width) + 'px',
248                'height': Math.round(height) + 'px'
249            });
250        }
251        sizeVideoContainer();
252
253        // Create close button, fixed to the viewport so it is always visible/tappable
254        var closeButton = jQuery('<div id="elpt-video-close">×</div>')
255            .css({
256                'position': 'fixed',
257                'top': '12px',
258                'right': '16px',
259                'width': '44px',
260                'height': '44px',
261                'line-height': '44px',
262                'text-align': 'center',
263                'color': 'white',
264                'font-size': '34px',
265                'cursor': 'pointer',
266                'z-index': 10000
267            })
268            .appendTo(overlay);
269
270        function closeVideoLightbox() {
271            overlay.remove();
272            jQuery(window).off('resize.elptVideo orientationchange.elptVideo');
273            jQuery(document).off('keydown.elptVideo');
274        }
275
276        // Add click event to close lightbox
277        closeButton.on('click', closeVideoLightbox);
278
279        // Close lightbox when clicking outside video
280        overlay.on('click', function(e) {
281            if (e.target === this) {
282                closeVideoLightbox();
283            }
284        });
285
286        // Close lightbox when pressing ESC
287        jQuery(document).on('keydown.elptVideo', function(e) {
288            if (e.keyCode === 27) { // ESC key
289                closeVideoLightbox();
290            }
291        });
292
293        // Recompute size on viewport changes (resize / device rotation)
294        jQuery(window).on('resize.elptVideo orientationchange.elptVideo', sizeVideoContainer);
295    }
296});

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.