").text(caption).html();
}
JS;
}
@@ -699,12 +697,7 @@ function mfbfw_after_load_js( array $s ) {
<<<'JS'
function (instance, current) {
var captionContent = current.opts.caption || '';
- var sanitized = '';
- if (typeof DOMPurify === 'function' && captionContent.length) {
- sanitized = DOMPurify.sanitize(captionContent, {USE_PROFILES: {html: true}});
- } else if (captionContent.length) {
- sanitized = jQuery("
").text(captionContent).html();
- }
+ var sanitized = captionContent.length ? jQuery.fancyboxforwp.sanitize(captionContent) : '';
if (sanitized.length) {
current.$content.append(jQuery('
').html(sanitized));
}
@@ -718,6 +711,12 @@ function (instance, current) {
/**
* The selector that decides which links become lightbox links.
*
+ * Smilies and emoji are the only images an anonymous commenter can place inside a
+ * link, and an avatar's link can be the commenter's own URL, so none of them may
+ * enrol a link. The query group is `?` rather than `*`: `[^/]*` already consumes any
+ * further question marks, and the starred form backtracked exponentially, letting a
+ * single crafted link freeze the browser of everyone who loaded the page.
+ *
* @since 3.4.0
*
* @param array $s Normalized settings.
@@ -731,7 +730,7 @@ function mfbfw_thumbnail_selector_js( array $s ) {
return sprintf(
'jQuery("a:has(img)").not(".nolightbox").not(".envira-gallery-link").not(".ngg-simplelightbox").filter(function () {'
- . ' return /\.(%s)(\?[^/]*)*$/i.test(jQuery(this).attr("href")); })',
+ . ' return jQuery(this).find("img").not(".wp-smiley,.emoji,.avatar").length > 0 && /\.(%s)(\?[^/]*)?$/i.test(jQuery(this).attr("href")); })',
$extensions
);
}
@@ -916,17 +915,19 @@ function (instance) {
}
/**
- * Print inline styles and load FancyBox with the selected settings
+ * The inline script that configures FancyBox and binds it to the links on the page.
+ *
+ * Attached with wp_add_inline_script(), so it always runs straight after the library
+ * wherever that is printed. When it was echoed from wp_footer directly it ran before
+ * the footer scripts, so "load at footer" with jQuery also in the footer left the
+ * lightbox silently dead.
+ *
+ * @since 3.4.3
+ *
+ * @param array $s Normalized settings.
+ * @return string
*/
-function mfbfw_init() {
-
- if ( ! mfbfw_is_enabled() ) {
- return;
- }
-
- global $mfbfw;
-
- $s = mfbfw_get_settings( is_array( $mfbfw ) ? $mfbfw : null );
+function mfbfw_build_inline_js( array $s ) {
$options = mfbfw_build_options( $s );
@@ -948,102 +949,99 @@ function mfbfw_init() {
? "\t\t/* Extra Calls */\n\t\t" . html_entity_decode( $s['extraCallsData'] ) . "\n"
: '';
- // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- CSS and JS are
- // assembled from schema-normalized values above; see mfbfw_build_css()/mfbfw_build_options().
- ?>
-
-
-
-
- $mobile_guard,
+ '__FBFW_IS_SINGULAR__' => is_singular() ? 'true' : 'false',
+ '__FBFW_TITLE_COPY__' => mfbfw_title_copy_js( $s ),
+ '__FBFW_OPTIONS__' => $options['json'],
+ '__FBFW_ASSIGNMENTS__' => $assignments,
+ '__FBFW_ARIA_LABEL__' => wp_json_encode( __( 'Image lightbox', 'fancybox-for-wordpress' ) ),
+ '__FBFW_THUMBNAILS__' => mfbfw_thumbnail_selector_js( $s ),
+ '__FBFW_GALLERY__' => mfbfw_gallery_js( $s ),
+ '__FBFW_EXTRA_CALLS__' => $extra_calls,
+ )
+ );
}
/**
@@ -1053,6 +1051,9 @@ function mfbfw_textdomain() {
// The second parameter has been deprecated since WordPress 2.7 and passing
// anything but false triggers a _doing_it_wrong() notice.
+ // Language packs load on their own, but WordPress only finds the translations bundled
+ // in languages/ (for locales with no pack) through the path this call registers.
+ // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound
load_plugin_textdomain( 'fancybox-for-wordpress', false, dirname( FBFW_PLUGIN_BASE ) . '/languages' );
}
@@ -1136,6 +1137,7 @@ function mfbfw_admin_scripts() {
'fbfwAdmin',
array(
'confirmDefaults' => __( 'Are you sure you want to restore FancyBox for WordPress to default settings?', 'fancybox-for-wordpress' ),
+ 'scriptsReadOnly' => ! mfbfw_can_edit_scripts(),
)
);
@@ -1204,7 +1206,7 @@ function hexTorgba( $hexColor, $opacity ) { // phpcs:ignore WordPress.NamingConv
* Check if WooCommerce Product post
*
*/
-function fancy_check_if_woocommerce() {
+function mfbfw_woocommerce_context() {
if ( ! class_exists( 'WooCommerce' ) ) {
return 'true';
@@ -1221,6 +1223,23 @@ function fancy_check_if_woocommerce() {
return 'true';
}
+/**
+ * Whether the current user may change the settings that print raw JavaScript.
+ *
+ * Those fields run on every page of the site, so writing to them is the same as
+ * publishing unfiltered HTML. `manage_options` alone is not enough: a site
+ * administrator on multisite holds it but must not be able to run script in front
+ * of a network administrator, and DISALLOW_UNFILTERED_HTML has to mean what it says.
+ *
+ * @since 3.4.3
+ *
+ * @return bool
+ */
+function mfbfw_can_edit_scripts() {
+
+ return current_user_can( 'unfiltered_html' );
+}
+
/**
* Sanitize options
*
@@ -1244,8 +1263,15 @@ function mfbfw_sanitize_fancy_options( $value ) {
$stored = is_array( $stored ) ? $stored : array();
$sanitized = array();
+ $can_edit_scripts = mfbfw_can_edit_scripts();
+
foreach ( $schema as $key => $spec ) {
+ if ( 'js' === $spec['type'] && ! $can_edit_scripts ) {
+ $sanitized[ $key ] = array_key_exists( $key, $stored ) ? mfbfw_normalize_value( $stored[ $key ], $spec ) : $spec['default'];
+ continue;
+ }
+
if ( array_key_exists( $key, $value ) ) {
$sanitized[ $key ] = mfbfw_normalize_value( $value[ $key ], $spec );
continue;
@@ -1267,5 +1293,23 @@ function mfbfw_sanitize_fancy_options( $value ) {
$sanitized[ $key ] = ( 'toggle' === $spec['type'] ) ? '' : $spec['default'];
}
+ /*
+ * Switching stored code on is the same decision as writing it, so a user who may
+ * not do one may not do the other. Switching it off stays allowed.
+ */
+ if ( ! $can_edit_scripts ) {
+ $current = mfbfw_get_settings( $stored );
+
+ foreach ( array( 'callbackEnable', 'extraCallsEnable' ) as $switch ) {
+ if ( ! mfbfw_is_on( $switch, $current ) ) {
+ $sanitized[ $switch ] = '';
+ }
+ }
+
+ if ( 'custom' === $sanitized['galleryType'] && 'custom' !== $current['galleryType'] ) {
+ $sanitized['galleryType'] = $current['galleryType'];
+ }
+ }
+
return $sanitized;
}
diff --git a/languages/fancybox-for-wordpress.pot b/languages/fancybox-for-wordpress.pot
index 5944c54..379fe70 100644
--- a/languages/fancybox-for-wordpress.pot
+++ b/languages/fancybox-for-wordpress.pot
@@ -2,14 +2,14 @@
# This file is distributed under the GPLv3 or later.
msgid ""
msgstr ""
-"Project-Id-Version: FancyBox for WordPress 3.4.0\n"
+"Project-Id-Version: FancyBox for WordPress 3.4.3\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/fancybox-for-wordpress/\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"POT-Creation-Date: 2026-08-06T12:01:41+00:00\n"
+"POT-Creation-Date: 2026-11-09T10:48:00+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.12.0\n"
"X-Domain: fancybox-for-wordpress\n"
@@ -53,40 +53,44 @@ msgstr ""
msgid "Seamlessly integrates FancyBox into your blog: Upload, activate, and you're done. Additional configuration optional."
msgstr ""
-#: admin.php:45
+#: admin.php:44
+msgid "The custom JavaScript fields are read-only because your account is not allowed to publish unfiltered HTML."
+msgstr ""
+
+#: admin.php:49
msgid "Appearance"
msgstr ""
-#: admin.php:46
+#: admin.php:50
msgid "Animations"
msgstr ""
-#: admin.php:47
+#: admin.php:51
msgid "Behaviour"
msgstr ""
-#: admin.php:48
+#: admin.php:52
msgid "Galleries"
msgstr ""
-#: admin.php:49
+#: admin.php:53
msgid "Misc."
msgstr ""
-#: admin.php:50
+#: admin.php:54
msgid "Support"
msgstr ""
-#: admin.php:81
+#: admin.php:85
msgid "Save Changes"
msgstr ""
-#: admin.php:90
+#: admin.php:94
msgid "Revert to defaults"
msgstr ""
#. translators: %s: Colorlib link.
-#: admin.php:100
+#: admin.php:104
#, php-format
msgid "Plugin developed and supported by %s"
msgstr ""
@@ -107,23 +111,27 @@ msgstr ""
msgid "Don't show again"
msgstr ""
-#: fancybox.php:994
+#: fancybox.php:1039
+msgid "Image lightbox"
+msgstr ""
+
+#: fancybox.php:1072
msgid "You do not have permission to change these settings."
msgstr ""
-#: fancybox.php:1025
+#: fancybox.php:1103
msgid "Fancybox for WordPress Options"
msgstr ""
-#: fancybox.php:1026
+#: fancybox.php:1104
msgid "Fancybox for WP"
msgstr ""
-#: fancybox.php:1061
+#: fancybox.php:1139
msgid "Are you sure you want to restore FancyBox for WordPress to default settings?"
msgstr ""
-#: fancybox.php:1075
+#: fancybox.php:1154
msgid "Settings"
msgstr ""
diff --git a/lib/admin-head.php b/lib/admin-head.php
index 3d5eccd..3e72eef 100644
--- a/lib/admin-head.php
+++ b/lib/admin-head.php
@@ -21,13 +21,10 @@
* type. This used to call the save-time sanitizer on read, which is now an
* allow-list and would drop anything it did not recognise.
*/
-$settings = mfbfw_get_settings();
-
-// Get Version
-$version = get_option( 'mfbfw_active_version' );
+$mfbfw_settings = mfbfw_get_settings();
// Make selects data
-$transitionTypeArray = array( 'fade', 'zoom', 'zoom-in-out', 'none' );
-$overlayArray = array( 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1 );
-$msArray = array( 0, 25, 50, 75, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1250, 1500, 1750, 2000 );
-$slideEffectArray = array( 'false', 'fade', 'slide', 'circular', 'tube', 'zoom-in-out', 'rotate' );
+$mfbfw_transition_types = array( 'fade', 'zoom', 'zoom-in-out', 'none' );
+$mfbfw_overlay_opacities = array( 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1 );
+$mfbfw_durations = array( 0, 25, 50, 75, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1250, 1500, 1750, 2000 );
+$mfbfw_slide_effects = array( 'false', 'fade', 'slide', 'circular', 'tube', 'zoom-in-out', 'rotate' );
diff --git a/lib/admin-tab-animations.php b/lib/admin-tab-animations.php
index a658c21..6bf311f 100644
--- a/lib/admin-tab-animations.php
+++ b/lib/admin-tab-animations.php
@@ -15,7 +15,7 @@
|