--- a/responsive-lightbox/includes/class-folders.php
+++ b/responsive-lightbox/includes/class-folders.php
@@ -68,6 +68,8 @@
add_action( 'wp_ajax_rl-folders-add-term', [ $this, 'add_term' ] );
add_action( 'wp_ajax_rl-folders-move-term', [ $this, 'move_term' ] );
add_action( 'wp_ajax_rl-folders-move-attachments', [ $this, 'move_attachments' ] );
+ add_action( 'wp_ajax_rl-folders-get-counters', [ $this, 'get_counters' ] );
+ add_action( 'wp_ajax_rl-folders-select-term', [ $this, 'select_term' ] );
// filters
add_filter( 'admin_body_class', [ $this, 'admin_body_class' ] );
@@ -375,6 +377,9 @@
if ( $pagenow === 'upload.php' ) {
// append class
$classes .= ' rl-folders-upload-' . $this->mode . '-mode';
+
+ // pending state until jsTree hydrates the server-rendered shell
+ $classes .= ' rl-folders-root-pending';
}
return $classes;
@@ -563,7 +568,23 @@
// get active taxonomy
$taxonomy = $this->get_active_taxonomy();
- if ( $pagenow === 'upload.php' && isset( $_GET[$taxonomy] ) ) {
+ if ( $pagenow === 'upload.php' ) {
+ // explicit URL filter has priority; fall back to stored user preference
+ if ( isset( $_GET[$taxonomy] ) ) {
+ $term_id = (int) $_GET[$taxonomy];
+ } else {
+ $stored = get_user_option( 'rl_folders_selected_term' );
+
+ if ( $stored === false || $stored === '' || $stored === 'all' )
+ return $query;
+
+ $term_id = (int) $stored;
+
+ // JS stores root folder as '0'; parse_query uses -1
+ if ( $term_id === 0 )
+ $term_id = -1;
+ }
+
// get tax query
$tax_query = $query->get( 'tax_query' );
@@ -571,7 +592,6 @@
$tax_query = [];
// -1 === root, 0 === all files, >0 === term_id
- $term_id = (int) $_GET[$taxonomy];
if ( $term_id !== 0 && ( $query->is_main_query() || empty( $query->query['rl_folders_root'] ) ) ) {
$tax = [
@@ -611,6 +631,7 @@
// get active taxonomy
$taxonomy = $this->get_active_taxonomy();
+ // explicit filter in POST has priority; fall back to stored user preference
if ( isset( $_POST['query'][$taxonomy] ) ) {
$term_id = sanitize_key( $_POST['query'][$taxonomy] );
@@ -621,22 +642,33 @@
if ( $term_id < 0 )
return $query;
+ } else {
+ // no filter sent -- apply stored user preference for the initial grid query
+ $stored = get_user_option( 'rl_folders_selected_term' );
- if ( empty( $query['tax_query'] ) || ! is_array( $query['tax_query'] ) )
- $query['tax_query'] = [];
+ if ( $stored === false || $stored === '' || $stored === 'all' )
+ return $query;
- $query['tax_query'][] = [
- 'relation' => 'AND',
- [
- 'taxonomy' => $taxonomy,
- 'field' => 'id',
- 'terms' => $term_id,
- 'include_children' => ( ! ( isset( $_POST['query']['include_children'] ) && $_POST['query']['include_children'] === 'false' ) ),
- 'operator' => ( $term_id === 0 ? 'NOT EXISTS' : 'IN' )
- ]
- ];
+ $term_id = (int) $stored;
+
+ if ( $term_id < 0 )
+ return $query;
}
+ if ( empty( $query['tax_query'] ) || ! is_array( $query['tax_query'] ) )
+ $query['tax_query'] = [];
+
+ $query['tax_query'][] = [
+ 'relation' => 'AND',
+ [
+ 'taxonomy' => $taxonomy,
+ 'field' => 'id',
+ 'terms' => $term_id,
+ 'include_children' => ( ! ( isset( $_POST['query']['include_children'] ) && $_POST['query']['include_children'] === 'false' ) ),
+ 'operator' => ( $term_id === 0 ? 'NOT EXISTS' : 'IN' )
+ ]
+ ];
+
return $query;
}
@@ -821,6 +853,11 @@
* @return void
*/
public function delete_term() {
+ // check rate limiting (30 requests per minute for destructive operations)
+ if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_delete_term', 30, 60 ) ) {
+ wp_send_json_error( __( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' ) );
+ }
+
// no data?
if ( ! isset( $_POST['term_id'], $_POST['nonce'], $_POST['children'] ) )
wp_send_json_error();
@@ -1097,6 +1134,143 @@
}
/**
+ * AJAX action to get fresh tree counters.
+ *
+ * @return void
+ */
+ public function get_counters() {
+ global $wpdb;
+
+ // invalid capability?
+ if ( ! current_user_can( 'upload_files' ) )
+ wp_send_json_error();
+
+ // invalid nonce?
+ if ( ! isset( $_POST['nonce'] ) || ! ctype_alnum( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'rl-folders-ajax-library-nonce' ) )
+ wp_send_json_error();
+
+ // get active taxonomy
+ $taxonomy = $this->get_active_taxonomy();
+
+ // taxonomy does not exist?
+ if ( ! taxonomy_exists( $taxonomy ) )
+ wp_send_json_error();
+
+ $counter_statuses = apply_filters( 'rl_folders_counter_post_statuses', [ 'inherit' ], $taxonomy );
+ $counter_statuses = array_values(
+ array_filter(
+ array_map( 'sanitize_key', (array) $counter_statuses ),
+ static function( $status ) {
+ return $status !== '';
+ }
+ )
+ );
+
+ if ( empty( $counter_statuses ) )
+ $counter_statuses = [ 'inherit' ];
+
+ $counters = [
+ 'all' => (int) apply_filters( 'rl_count_attachments', 0 ),
+ '0' => 0
+ ];
+
+ $term_ids = get_terms(
+ [
+ 'taxonomy' => $taxonomy,
+ 'hide_empty' => false,
+ 'fields' => 'ids'
+ ]
+ );
+
+ if ( ! is_wp_error( $term_ids ) && ! empty( $term_ids ) ) {
+ foreach ( $term_ids as $term_id ) {
+ $counters[(string) (int) $term_id] = 0;
+ }
+ }
+
+ $status_placeholders = implode( ', ', array_fill( 0, count( $counter_statuses ), '%s' ) );
+ $query_values = $counter_statuses;
+ $query_values[] = $taxonomy;
+
+ $term_counts = $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT tt.term_id AS term_id, COUNT(DISTINCT p.ID) AS term_count
+ FROM {$wpdb->posts} p
+ INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
+ INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
+ WHERE p.post_type = 'attachment'
+ AND p.post_status IN (" . $status_placeholders . ")
+ AND tt.taxonomy = %s
+ GROUP BY tt.term_id",
+ $query_values
+ ),
+ ARRAY_A
+ );
+
+ if ( ! empty( $term_counts ) ) {
+ foreach ( $term_counts as $term_count ) {
+ $term_id = isset( $term_count['term_id'] ) ? (int) $term_count['term_id'] : 0;
+
+ if ( $term_id > 0 )
+ $counters[(string) $term_id] = isset( $term_count['term_count'] ) ? (int) $term_count['term_count'] : 0;
+ }
+ }
+
+ // root folder query (attachments without any folder term assigned)
+ $root_query = new WP_Query(
+ apply_filters(
+ 'rl_root_folder_query_args',
+ [
+ 'rl_folders_root' => true,
+ 'posts_per_page' => 1,
+ 'post_type' => 'attachment',
+ 'post_status' => $counter_statuses,
+ 'fields' => 'ids',
+ 'no_found_rows' => false,
+ 'tax_query' => [
+ [
+ 'relation' => 'AND',
+ [
+ 'taxonomy' => $taxonomy,
+ 'field' => 'id',
+ 'terms' => 0,
+ 'include_children' => false,
+ 'operator' => 'NOT EXISTS'
+ ]
+ ]
+ ]
+ ]
+ )
+ );
+
+ $counters['0'] = (int) $root_query->found_posts;
+
+ wp_send_json_success(
+ [
+ 'counters' => $counters
+ ]
+ );
+ }
+
+ /**
+ * Persist selected folder term as user option via AJAX.
+ *
+ * @return void
+ */
+ public function select_term() {
+ check_ajax_referer( 'rl-folders-ajax-library-nonce', 'nonce' );
+
+ if ( ! current_user_can( 'upload_files' ) )
+ wp_send_json_error( 'No permission.' );
+
+ $term_id = isset( $_POST['term_id'] ) ? sanitize_key( $_POST['term_id'] ) : 'all';
+
+ update_user_option( get_current_user_id(), 'rl_folders_selected_term', $term_id );
+
+ wp_send_json_success();
+ }
+
+ /**
* Change wp_list_categories HTML link.
*
* @param array $matches Matched elements
@@ -1221,7 +1395,9 @@
// create folder counters
$counters = [];
- if ( $page !== 'media' ) {
+ // Only load full tree/jsTree for upload.php (not media-new.php or media modal)
+ // media-new.php needs only the upload destination dropdown functionality
+ if ( $page === 'upload.php' ) {
// prepare variables
$no_items = '';
$childless = false;
@@ -1287,8 +1463,21 @@
'echo' => false
];
- // get current term id
- $term_id = isset( $_GET[$taxonomy->name] ) ? (int) $_GET[$taxonomy->name] : 0;
+ // get current term id: explicit URL filter > stored user preference > default (all)
+ if ( isset( $_GET[$taxonomy->name] ) ) {
+ $term_id = (int) $_GET[$taxonomy->name];
+ } else {
+ $stored_term = get_user_option( 'rl_folders_selected_term' );
+
+ if ( $stored_term !== false && $stored_term !== '' && $stored_term !== 'all' ) {
+ $term_id = (int) $stored_term;
+
+ // JS stores root folder as '0'; PHP tree uses -1
+ if ( $term_id === 0 )
+ $term_id = -1;
+ } else
+ $term_id = 0;
+ }
// list mode?
if ( $this->mode === 'list' ) {
@@ -1413,6 +1602,13 @@
wp_enqueue_script( 'responsive-lightbox-folders-admin', RESPONSIVE_LIGHTBOX_URL . '/js/admin-folders.js', $dependencies, $rl->defaults['version'], false );
+ $is_upload_screen = ( $page === 'upload.php' );
+ $sidebar_width_default = 272;
+ $sidebar_width_min = 220;
+ $sidebar_width_max = 420;
+ $sidebar_width_collapsed = 0;
+
+ // Minimal script data for media modal
if ( $page === 'media' ) {
// prepare script data
$script_data = [
@@ -1435,6 +1631,29 @@
]
)
];
+ } elseif ( $page === 'media-new.php' ) {
+ // Minimal script data for media-new.php - only needs upload destination sync
+ // No tree/jsTree needed, just page flag for JS to skip initialization
+ $script_data = [
+ 'taxonomy' => $taxonomy->name,
+ 'page' => $page,
+ 'root' => esc_html__( 'Root Folder', 'responsive-lightbox' ),
+ 'terms' => wp_dropdown_categories(
+ [
+ 'orderby' => 'name',
+ 'order' => 'asc',
+ 'show_option_all' => esc_html__( 'All Files', 'responsive-lightbox' ),
+ 'show_count' => false,
+ 'hide_empty' => false,
+ 'hierarchical' => true,
+ 'selected' => 0,
+ 'name' => $taxonomy->name,
+ 'taxonomy' => $taxonomy->name,
+ 'hide_if_empty' => true,
+ 'echo' => false
+ ]
+ )
+ ];
} else {
// prepare script data
$script_data = [
@@ -1445,6 +1664,7 @@
'no_media_items' => $no_items,
'taxonomy' => $taxonomy->name,
'page' => $page,
+ 'mode' => $this->mode,
'root' => esc_html__( 'Root Folder', 'responsive-lightbox' ),
'all_terms' => esc_html__( 'All Files', 'responsive-lightbox' ),
'new_folder' => esc_html__( 'New Folder', 'responsive-lightbox' ),
@@ -1470,7 +1690,7 @@
<div id="rl-folders-tree-container">
<div class="media-toolbar wp-filter">
<div class="view-switch rl-folders-action-links">
- <a href="#" title="' . esc_attr( $taxonomy->labels->add_new_item ) . '" class="dashicons dashicons-plus rl-folders-add-new-folder' . ( $this->mode === 'list' && ( $term_id === -1 || $term_id > 0 ) ? '' : ' disabled-link' ) . '"></a>
+ <a href="#" title="' . esc_attr( $taxonomy->labels->add_new_item ) . '" class="dashicons dashicons-plus rl-folders-add-new-folder"></a>
<a href="#" title="' . esc_attr( sprintf( __( 'Save new %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-yes rl-folders-save-new-folder" style="display: none;"></a>
<a href="#" title="' . esc_attr( sprintf( __( 'Cancel adding new %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-no rl-folders-cancel-new-folder" style="display: none;"></a>
<a href="#" title="' . esc_attr( $taxonomy->labels->edit_item ) . '" class="dashicons dashicons-edit rl-folders-rename-folder' . ( $this->mode === 'list' && $term_id > 0 ? '' : ' disabled-link' ) . '"></a>
@@ -1486,6 +1706,43 @@
];
}
+ if ( $is_upload_screen && isset( $script_data['template'] ) ) {
+ $script_data['template'] = '
+ <div id="rl-folders-tree-root">
+ <div id="rl-folders-tree-shell" class="rl-folders-tree-shell">
+ <div class="rl-folders-tree-header">
+ <span class="rl-folders-tree-title">' . esc_html__( 'Media Folders', 'responsive-lightbox' ) . '</span>
+ <button type="button" class="button button-secondary rl-folders-header-add-new-folder">' . esc_html__( 'New Folder', 'responsive-lightbox' ) . '</button>
+ </div>
+ <div class="rl-folders-tree-viewport">
+ <div class="rl-folders-tree-viewport-inner">' . $script_data['template'] . '</div>
+ <span class="rl-folders-tree-cover" aria-hidden="true"></span>
+ </div>
+ </div>
+ <div class="rl-folders-sidebar-separator" role="separator" aria-label="' . esc_attr__( 'Resize folders sidebar', 'responsive-lightbox' ) . '" aria-orientation="vertical" tabindex="0" aria-valuemin="' . esc_attr( $sidebar_width_min ) . '" aria-valuemax="' . esc_attr( $sidebar_width_max ) . '" aria-valuenow="' . esc_attr( $sidebar_width_default ) . '">
+ <button type="button" class="button-link rl-folders-sidebar-toggle" aria-expanded="true" aria-controls="rl-folders-tree-shell" title="' . esc_attr__( 'Toggle folders sidebar', 'responsive-lightbox' ) . '">
+ <span class="dashicons dashicons-menu-alt2" aria-hidden="true"></span>
+ <span class="screen-reader-text">' . esc_html__( 'Toggle folders sidebar', 'responsive-lightbox' ) . '</span>
+ </button>
+ </div>
+ </div>';
+ }
+
+ $script_data['sidebar_width_default'] = $sidebar_width_default;
+ $script_data['sidebar_width_min'] = $sidebar_width_min;
+ $script_data['sidebar_width_max'] = $sidebar_width_max;
+ $script_data['sidebar_width_collapsed'] = $sidebar_width_collapsed;
+
+ // pass resolved selected term to JS (uses $term_id already resolved from URL > stored > default)
+ if ( $is_upload_screen ) {
+ if ( $term_id === 0 )
+ $script_data['selected_term'] = 'all';
+ elseif ( $term_id === -1 )
+ $script_data['selected_term'] = '0';
+ else
+ $script_data['selected_term'] = (string) $term_id;
+ }
+
wp_add_inline_script( 'responsive-lightbox-folders-admin', 'var rlFoldersArgs = ' . wp_json_encode( $script_data ) . ";n", 'before' );
add_action( 'admin_print_styles', [ $this, 'admin_print_media_styles' ] );
--- a/responsive-lightbox/includes/class-frontend.php
+++ b/responsive-lightbox/includes/class-frontend.php
@@ -279,12 +279,12 @@
if ( preg_match( '/<a[^>]*?bdata-rel=(["'])(.*?)1[^>]*?>/is', $link, $result ) === 1 ) {
// allow to modify link?
if ( $result[2] !== 'norl' ) {
- // gallery?
- if ( $args['settings']['plugin']['images_as_gallery'] || $args['settings']['plugin']['force_custom_gallery'] )
- $link = preg_replace( '/bdata-rel=(["'])(.*?)1/s', 'data-rel="' . esc_attr( $args['selector'] ) . '-gallery-' . esc_attr( base64_encode( $result[2] ) ) . '" data-rl_title="__RL_IMAGE_TITLE__" data-rl_caption="__RL_IMAGE_CAPTION__"' . ( $args['script'] === 'magnific' ? ' data-magnific_type="gallery"' : '' ) . ( $args['script'] === 'imagelightbox' ? ' data-imagelightbox="' . (int) $args['link_number'] . '"' : '' ), $link, 1 );
- // single image
- else
- $link = preg_replace( '/bdata-rel=(["'])(.*?)1/s', 'data-rel="' . esc_attr( $args['selector'] ) . '-image-' . esc_attr( base64_encode( $result[2] ) ) . '"' . ( $args['script'] === 'magnific' ? ' data-magnific_type="image"' : '' ) . ( $args['script'] === 'imagelightbox' ? ' data-imagelightbox="' . (int) $args['link_number'] . '"' : '' ) . ' data-rl_title="__RL_IMAGE_TITLE__" data-rl_caption="__RL_IMAGE_CAPTION__"', $link, 1 );
+ // gallery?
+ if ( $args['settings']['plugin']['images_as_gallery'] || $args['settings']['plugin']['force_custom_gallery'] )
+ $link = preg_replace( '/bdata-rel=(["'])(.*?)1/s', 'data-rel="' . esc_attr( $args['selector'] ) . '-gallery-' . esc_attr( base64_encode( sanitize_text_field( $result[2] ) ) ) . '" data-rl_title="__RL_IMAGE_TITLE__" data-rl_caption="__RL_IMAGE_CAPTION__"' . ( $args['script'] === 'magnific' ? ' data-magnific_type="gallery"' : '' ) . ( $args['script'] === 'imagelightbox' ? ' data-imagelightbox="' . (int) $args['link_number'] . '"' : '' ), $link, 1 );
+ // single image
+ else
+ $link = preg_replace( '/bdata-rel=(["'])(.*?)1/s', 'data-rel="' . esc_attr( $args['selector'] ) . '-image-' . esc_attr( base64_encode( sanitize_text_field( $result[2] ) ) ) . '"' . ( $args['script'] === 'magnific' ? ' data-magnific_type="image"' : '' ) . ( $args['script'] === 'imagelightbox' ? ' data-imagelightbox="' . (int) $args['link_number'] . '"' : '' ) . ' data-rl_title="__RL_IMAGE_TITLE__" data-rl_caption="__RL_IMAGE_CAPTION__"', $link, 1 );
}
// link without data-rel
} else {
@@ -813,9 +813,9 @@
} else {
$ids = [];
- if ( ! empty( $shortcode_atts['include'] ) ) {
- // Normalize input from shortcode strings and widget/programmatic array payloads.
- $include = wp_parse_id_list( $shortcode_atts['include'] );
+ if ( ! empty( $shortcode_atts['include'] ) ) {
+ // Normalize input from shortcode strings and widget/programmatic array payloads.
+ $include = wp_parse_id_list( $shortcode_atts['include'] );
// any attachments?
if ( ! empty( $include ) ) {
@@ -832,9 +832,9 @@
]
);
}
- } elseif ( ! empty( $shortcode_atts['exclude'] ) ) {
- // Normalize input from shortcode strings and widget/programmatic array payloads.
- $exclude = wp_parse_id_list( $shortcode_atts['exclude'] );
+ } elseif ( ! empty( $shortcode_atts['exclude'] ) ) {
+ // Normalize input from shortcode strings and widget/programmatic array payloads.
+ $exclude = wp_parse_id_list( $shortcode_atts['exclude'] );
// any attachments?
if ( ! empty( $exclude ) ) {
@@ -1078,13 +1078,13 @@
if ( $result[2] === 'norl' )
continue;
- $content = str_replace( $link, preg_replace( '/bdata-rel=(["'])(.*?)1/', 'data-rel="' . esc_attr( $rl->options['settings']['selector'] ) . '-gallery-' . esc_attr( base64_encode( $result[2] ) ) . '" data-rl_title="' . esc_attr( $title ) . '" data-rl_caption="' . esc_attr( $caption ) . '"' . ( $script === 'imagelightbox' ? ' data-imagelightbox="' . (int) $link_number . '"' : '' ), $link, 1 ), $content );
- } elseif ( preg_match( '/<a[^>]*?brel=(["'])(.*?)1[^>]*?>/i', $link, $result ) === 1 ) {
- // do not modify this link
- if ( $result[2] === 'norl' )
- continue;
-
- $content = str_replace( $link, preg_replace( '/brel=(["'])(.*?)1/', 'data-rel="' . esc_attr( $rl->options['settings']['selector'] ) . '-gallery-' . esc_attr( base64_encode( $result[2] ) ) . '" data-rl_title="' . esc_attr( $title ) . '" data-rl_caption="' . esc_attr( $caption ) . '"' . ( $script === 'imagelightbox' ? ' data-imagelightbox="' . (int) $link_number . '"' : '' ), $link, 1 ), $content );
+ $content = str_replace( $link, preg_replace( '/bdata-rel=(["'])(.*?)1/', 'data-rel="' . esc_attr( $rl->options['settings']['selector'] ) . '-gallery-' . esc_attr( base64_encode( sanitize_text_field( $result[2] ) ) ) . '" data-rl_title="' . esc_attr( $title ) . '" data-rl_caption="' . esc_attr( $caption ) . '"' . ( $script === 'imagelightbox' ? ' data-imagelightbox="' . (int) $link_number . '"' : '' ), $link, 1 ), $content );
+ } elseif ( preg_match( '/<a[^>]*?brel=(["'])(.*?)1[^>]*?>/i', $link, $result ) === 1 ) {
+ // do not modify this link
+ if ( $result[2] === 'norl' )
+ continue;
+
+ $content = str_replace( $link, preg_replace( '/brel=(["'])(.*?)1/', 'data-rel="' . esc_attr( $rl->options['settings']['selector'] ) . '-gallery-' . esc_attr( base64_encode( sanitize_text_field( $result[2] ) ) ) . '" data-rl_title="' . esc_attr( $title ) . '" data-rl_caption="' . esc_attr( $caption ) . '"' . ( $script === 'imagelightbox' ? ' data-imagelightbox="' . (int) $link_number . '"' : '' ), $link, 1 ), $content );
} else
$content = str_replace( $link, '<a' . $links[1][$link_number] . ' href="' . $links[3][$link_number] . '.' . $links[4][$link_number] . '" data-rel="' . esc_attr( $rl->options['settings']['selector'] ) . '-gallery-' . esc_attr( base64_encode( $this->gallery_no ) ) . '" data-rl_title="' . esc_attr( $title ) . '" data-rl_caption="' . esc_attr( $caption ) . '"' . ( $script === 'imagelightbox' ? ' data-imagelightbox="' . (int) $link_number . '"' : '' ) . $links[5][$link_number] . '>', $content );
}
@@ -1762,9 +1762,9 @@
* @param id $widget_id
* @return string
*/
- public function widget_output( $content, $widget_id_base, $widget_id ) {
- return $this->add_lightbox( $content );
- }
+ public function widget_output( $content, $widget_id_base, $widget_id ) {
+ return wp_kses( $this->add_lightbox( $content ), $this->get_comment_lightbox_allowed_html() );
+ }
/**
* Get allowed HTML tags/attributes for filtered comment content.
@@ -2042,10 +2042,10 @@
<div class="rl-gallery rl-basicgrid-gallery <?php echo esc_attr( $atts['class'] ); ?>" id="rl-gallery-<?php echo (int) $gallery_no; ?>" data-gallery_no="<?php echo (int) $gallery_no; ?>">
- <?php foreach ( $images as $image ) {
- // $image['link'] is already escaped
- echo '<div class="rl-gallery-item">' . $image['link'] . '</div>';
- } ?>
+ <?php foreach ( $images as $image ) {
+ // $image['link'] is already escaped via get_gallery_image_link(), but we apply wp_kses_post() for defense-in-depth
+ echo '<div class="rl-gallery-item">' . wp_kses_post( $image['link'] ) . '</div>';
+ } ?>
</div>
@@ -2257,12 +2257,12 @@
<div class="rl-gallery rl-basicslider-gallery splide__track <?php echo esc_attr( $atts['class'] ); ?>" id="rl-gallery-<?php echo (int) $gallery_no; ?>" data-gallery_no="<?php echo (int) $gallery_no; ?>">
<ul class="splide__list">
- <?php foreach ( $images as $image ) {
- echo '
- <li class="rl-gallery-item splide__slide" ' . implode( ' ', apply_filters( 'rl_gallery_item_extra_args', [], $atts, $image ) ) . ' data-thumb="' . $image['thumbnail_url'] . '">
- ' . $image['link'] . '
- </li>';
- } ?>
+ <?php foreach ( $images as $image ) {
+ echo '
+ <li class="rl-gallery-item splide__slide" ' . implode( ' ', apply_filters( 'rl_gallery_item_extra_args', [], $atts, $image ) ) . ' data-thumb="' . $image['thumbnail_url'] . '">
+ ' . wp_kses_post( $image['link'] ) . '
+ </li>';
+ } ?>
</ul>
</div>
@@ -2486,17 +2486,17 @@
if ( $count === 0 )
echo '<div class="rl-gutter-sizer"></div><div class="rl-grid-sizer"></div>';
- foreach ( $images as $image ) {
- // $image['link'] is already escaped
- echo '
- <div class="rl-gallery-item' . ( $count === 0 ? ' rl-gallery-item-width-4' : '' ) . '" ' . implode( ' ', apply_filters( 'rl_gallery_item_extra_args', [], $atts, $image ) ) . '>
- <div class="rl-gallery-item-content">
- ' . $image['link'] . '
- </div>
- </div>';
-
- $count++;
- } ?>
+ foreach ( $images as $image ) {
+ // $image['link'] is already escaped via get_gallery_image_link(), but we apply wp_kses_post() for defense-in-depth
+ echo '
+ <div class="rl-gallery-item' . ( $count === 0 ? ' rl-gallery-item-width-4' : '' ) . '" ' . implode( ' ', apply_filters( 'rl_gallery_item_extra_args', [], $atts, $image ) ) . '>
+ <div class="rl-gallery-item-content">
+ ' . wp_kses_post( $image['link'] ) . '
+ </div>
+ </div>';
+
+ $count++;
+ } ?>
</div>
--- a/responsive-lightbox/includes/class-remote-library.php
+++ b/responsive-lightbox/includes/class-remote-library.php
@@ -293,16 +293,21 @@
*
* @return void
*/
- public function ajax_query_media() {
- $data = stripslashes_deep( $_POST );
-
- // check user capabilities
- if ( ! current_user_can( 'upload_files' ) )
- wp_send_json_error( esc_html__( 'Insufficient permissions.', 'responsive-lightbox' ) );
-
- // verify nonce
- if ( ! isset( $data['nonce'] ) || ! wp_verify_nonce( $data['nonce'], 'rl-remote-library-query' ) )
- wp_send_json_error( esc_html__( 'Invalid nonce.', 'responsive-lightbox' ) );
+ public function ajax_query_media() {
+ $data = stripslashes_deep( $_POST );
+
+ // check rate limiting (30 queries per minute to prevent API abuse)
+ if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_ajax_query_media', 30, 60 ) ) {
+ wp_send_json_error( esc_html__( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' ) );
+ }
+
+ // check user capabilities
+ if ( ! current_user_can( 'upload_files' ) )
+ wp_send_json_error( esc_html__( 'Insufficient permissions.', 'responsive-lightbox' ) );
+
+ // verify nonce
+ if ( ! isset( $data['nonce'] ) || ! wp_verify_nonce( $data['nonce'], 'rl-remote-library-query' ) )
+ wp_send_json_error( esc_html__( 'Invalid nonce.', 'responsive-lightbox' ) );
$results = [
'last' => false,
@@ -391,17 +396,24 @@
'message' => ''
];
- // verified upload?
- if ( current_user_can( 'upload_files' ) && isset( $data['rlnonce'], $data['image'], $data['post_id'] ) && wp_verify_nonce( $data['rlnonce'], 'rl-remote-library-upload-image' ) ) {
- // include required files if needed
- if ( ! function_exists( 'media_handle_upload' ) )
- require_once( path_join( ABSPATH, 'wp-admin/includes/media.php' ) );
-
- if ( ! function_exists( 'wp_handle_upload' ) )
- require_once( path_join( ABSPATH, 'wp-admin/includes/file.php' ) );
-
- // get media provider
- $media_provider = ! empty( $data['image']['media_provider'] ) ? sanitize_key( $data['image']['media_provider'] ) : '';
+ // verified upload?
+ if ( current_user_can( 'upload_files' ) && isset( $data['rlnonce'], $data['image'], $data['post_id'] ) && wp_verify_nonce( $data['rlnonce'], 'rl-remote-library-upload-image' ) ) {
+ // check rate limiting (10 uploads per minute)
+ if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_upload_image', 10, 60 ) ) {
+ $result['error'] = true;
+ $result['message'] = __( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' );
+ wp_send_json( $result );
+ }
+
+ // include required files if needed
+ if ( ! function_exists( 'media_handle_upload' ) )
+ require_once( path_join( ABSPATH, 'wp-admin/includes/media.php' ) );
+
+ if ( ! function_exists( 'wp_handle_upload' ) )
+ require_once( path_join( ABSPATH, 'wp-admin/includes/file.php' ) );
+
+ // get media provider
+ $media_provider = ! empty( $data['image']['media_provider'] ) ? sanitize_key( $data['image']['media_provider'] ) : '';
// get active providers
$providers = $this->get_active_providers();
@@ -417,31 +429,44 @@
// get allowed hosts
$hosts = $this->get_allowed_hosts( $media_provider );
- if ( ! empty( $hosts ) ) {
- $valid_host = false;
-
- // get image host
- $image_host = parse_url( $image_url, PHP_URL_HOST );
-
- // check allowed hosts
- foreach ( $hosts as $host ) {
- // invalid host?
- if ( strpos( $image_host, $host ) !== false ) {
- $valid_host = true;
-
- // no need to check rest of the hosts
- break;
- }
- }
- } else
- $valid_host = true;
-
- if ( $valid_host ) {
+ $valid_host = false;
+
+ if ( ! empty( $hosts ) ) {
+ // get image host
+ $image_host = parse_url( $image_url, PHP_URL_HOST );
+
+ // validate that we got a valid hostname
+ if ( ! is_string( $image_host ) || $image_host === '' ) {
+ $result['error'] = true;
+ $result['message'] = __( 'Invalid image URL.', 'responsive-lightbox' );
+ } else {
+ // normalize hostname to lowercase for case-insensitive comparison
+ $image_host = strtolower( $image_host );
+
+ // check allowed hosts - strict validation to prevent SSRF bypasses
+ foreach ( $hosts as $host ) {
+ $host = strtolower( $host );
+
+ // Validate exact match or valid subdomain (e.g., upload.wikimedia.org matches wikimedia.org)
+ // Prevent substring bypass: evil-wikimedia.org or upload.wikimedia.org.evil.com must NOT match
+ if ( $image_host === $host || substr( $image_host, -( strlen( $host ) + 1 ) ) === '.' . $host ) {
+ $valid_host = true;
+
+ // no need to check rest of the hosts
+ break;
+ }
+ }
+ }
+ } else {
+ $valid_host = true;
+ }
+
+ if ( $valid_host && empty( $result['error'] ) ) {
// get max image size (ensure at least 1MB)
$max_size = max( 1, absint( Responsive_Lightbox()->options['remote_library']['max_image_size'] ) ) * 1024 * 1024;
- // check image size via HEAD request
- $head_response = wp_remote_head( $image_url );
+ // check image size via HEAD request - use wp_safe_remote_head for SSRF protection
+ $head_response = wp_safe_remote_head( $image_url );
$skip_size_check = false;
if ( is_wp_error( $head_response ) ) {
@@ -455,41 +480,44 @@
}
}
- if ( empty( $result['error'] ) ) {
- // get image as binary data with timeout
- $response = wp_safe_remote_get( $image_url, [ 'timeout' => 30 ] );
-
- // no errors?
- if ( ! is_wp_error( $response ) ) {
- // get image binary data
- $image_bits = wp_remote_retrieve_body( $response );
-
- // check body size if HEAD was skipped or as fallback
- $body_size = strlen( $image_bits );
- if ( $skip_size_check || $body_size > $max_size ) {
- if ( $body_size > $max_size ) {
- $result['error'] = true;
- $result['message'] = __( 'Image size exceeds maximum allowed size.', 'responsive-lightbox' );
- }
- }
-
- if ( empty( $result['error'] ) ) {
- // get sanitized file name
- $file_name = sanitize_file_name( pathinfo( $data['image']['name'], PATHINFO_BASENAME ) );
-
- // get file extension
- $file_ext = pathinfo( $file_name, PATHINFO_EXTENSION );
-
- // no extension?
- if ( $file_ext === '' || ! array_key_exists( $file_ext, $image_formats ) ) {
- $file_name .= '.jpg';
- $file_ext = 'jpg';
- }
-
- // simple mime checking
- $check = wp_check_filetype( $file_name );
-
- if ( $check['type'] === $data['image']['mime'] && $check['ext'] !== false && array_key_exists( $file_ext, $image_formats ) ) {
+ if ( empty( $result['error'] ) ) {
+ // get image as binary data with timeout and size limit to prevent memory exhaustion
+ $response = wp_safe_remote_get( $image_url, [
+ 'timeout' => 30,
+ 'limit_response_size' => $max_size + 1024, // Add 1KB buffer for headers
+ ] );
+
+ // no errors?
+ if ( ! is_wp_error( $response ) ) {
+ // get image binary data
+ $image_bits = wp_remote_retrieve_body( $response );
+
+ // check body size as validation
+ // Note: limit_response_size prevents memory exhaustion, but we still validate the actual size
+ $body_size = strlen( $image_bits );
+ if ( $body_size > $max_size ) {
+ $result['error'] = true;
+ $result['message'] = __( 'Image size exceeds maximum allowed size.', 'responsive-lightbox' );
+ }
+
+ if ( empty( $result['error'] ) ) {
+ // get sanitized file name
+ $file_name = sanitize_file_name( pathinfo( $data['image']['name'], PATHINFO_BASENAME ) );
+
+ // get file extension
+ $file_ext = pathinfo( $file_name, PATHINFO_EXTENSION );
+
+ // no extension?
+ if ( $file_ext === '' || ! array_key_exists( $file_ext, $image_formats ) ) {
+ $file_name .= '.jpg';
+ $file_ext = 'jpg';
+ }
+
+ // strict file validation using wp_check_filetype with allowed formats whitelist
+ $check = wp_check_filetype( $file_name, $image_formats );
+
+ // validate extension is allowed and mime type matches
+ if ( $check['ext'] && $check['type'] && array_key_exists( $check['ext'], $image_formats ) && $check['type'] === $data['image']['mime'] ) {
// upload image
$uploaded_image = wp_upload_bits( $file_name, null, $image_bits, current_time( 'Y/m' ) );
@@ -560,10 +588,11 @@
}
}
}
- } else {
- $result['error'] = true;
- $result['message'] = __( 'Invalid host', 'responsive-lightbox' );
- }
+ } elseif ( empty( $result['error'] ) ) {
+ // Only set "Invalid host" if no previous error was set (e.g., "Invalid image URL")
+ $result['error'] = true;
+ $result['message'] = __( 'Invalid host', 'responsive-lightbox' );
+ }
} else {
$result['error'] = true;
$result['message'] = __( 'Missing or invalid image data', 'responsive-lightbox' );
--- a/responsive-lightbox/includes/class-settings.php
+++ b/responsive-lightbox/includes/class-settings.php
@@ -391,9 +391,9 @@
// Category options now populated by Settings API (class-settings-builder.php)
// Legacy code removed - no longer writing to $this->settings
- // flush rewrite rules if needed
- if ( current_user_can( apply_filters( 'rl_lightbox_settings_capability', $rl->options['capabilities']['active'] ? 'edit_lightbox_settings' : 'manage_options' ) ) && isset( $_POST['flush_rules'] ) && isset( $_POST['option_page'], $_POST['action'], $_POST['responsive_lightbox_builder'], $_POST['_wpnonce'] ) && $_POST['option_page'] === 'responsive_lightbox_builder' && $_POST['action'] === 'update' && ( isset( $_POST['save_rl_builder'] ) || isset( $_POST['reset_rl_builder'] ) || isset( $_POST['save_responsive_lightbox_builder'] ) || isset( $_POST['reset_responsive_lightbox_builder'] ) ) && check_admin_referer( 'responsive_lightbox_builder-options', '_wpnonce' ) !== false )
- flush_rewrite_rules();
+ // flush rewrite rules if needed
+ if ( current_user_can( apply_filters( 'rl_lightbox_settings_capability', $rl->options['capabilities']['active'] ? 'edit_lightbox_settings' : 'manage_options' ) ) && isset( $_POST['flush_rules'] ) && isset( $_POST['option_page'], $_POST['action'], $_POST['responsive_lightbox_builder'], $_POST['_wpnonce'] ) && $_POST['option_page'] === 'responsive_lightbox_builder' && $_POST['action'] === 'update' && ( isset( $_POST['save_rl_builder'] ) || isset( $_POST['reset_rl_builder'] ) || isset( $_POST['save_responsive_lightbox_builder'] ) || isset( $_POST['reset_responsive_lightbox_builder'] ) ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'responsive_lightbox_builder-options' ) )
+ flush_rewrite_rules();
}
/**
--- a/responsive-lightbox/includes/galleries/trait-gallery-ajax.php
+++ b/responsive-lightbox/includes/galleries/trait-gallery-ajax.php
@@ -40,11 +40,16 @@
* @param array $args
* @return void
*/
- public function get_gallery_page( $args ) {
- // check whether is it valid gallery ajax request
- if ( $this->gallery_ajax_verified() ) {
- // cast page number
- $_GET['rl_page'] = (int) $_POST['page'];
+ public function get_gallery_page( $args ) {
+ // check whether is it valid gallery ajax request
+ if ( $this->gallery_ajax_verified() ) {
+ // check rate limiting (60 requests per minute)
+ if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_get_gallery_page', 60, 60 ) ) {
+ wp_send_json_error( __( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' ) );
+ }
+
+ // cast page number
+ $_GET['rl_page'] = (int) $_POST['page'];
// check preview
$preview = ( $_POST['preview'] === 'true' );
@@ -138,10 +143,15 @@
*
* @return void
*/
- public function post_get_galleries() {
- // check data
- if ( ! isset( $_POST['post_id'], $_POST['search'], $_POST['nonce'], $_POST['page'] ) || ! check_ajax_referer( 'rl-gallery-post', 'nonce', false ) )
- wp_send_json_error();
+ public function post_get_galleries() {
+ // check rate limiting (60 requests per minute)
+ if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_post_get_galleries', 60, 60 ) ) {
+ wp_send_json_error( __( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' ) );
+ }
+
+ // check data
+ if ( ! isset( $_POST['post_id'], $_POST['search'], $_POST['nonce'], $_POST['page'] ) || ! check_ajax_referer( 'rl-gallery-post', 'nonce', false ) )
+ wp_send_json_error();
// check page
$page = preg_replace( '/[^a-z-.]/i', '', $_POST['page'] );
@@ -276,10 +286,15 @@
*
* @return void
*/
- public function get_gallery_preview_content() {
- // initial checks
- if ( ! isset( $_POST['post_id'], $_POST['menu_item'], $_POST['nonce'], $_POST['preview_type'] ) || ! check_ajax_referer( 'rl-gallery', 'nonce', false ) )
- wp_send_json_error();
+ public function get_gallery_preview_content() {
+ // check rate limiting (60 requests per minute)
+ if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_get_gallery_preview_content', 60, 60 ) ) {
+ wp_send_json_error( __( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' ) );
+ }
+
+ // initial checks
+ if ( ! isset( $_POST['post_id'], $_POST['menu_item'], $_POST['nonce'], $_POST['preview_type'] ) || ! check_ajax_referer( 'rl-gallery', 'nonce', false ) )
+ wp_send_json_error();
// cast gallery ID
$post_id = (int) $_POST['post_id'];
@@ -504,19 +519,19 @@
$disable_first = $disable_last = $disable_prev = $disable_next = false;
$current_url = 'preview_page';
- if ( $current == 1 ) {
- $disable_first = true;
- $disable_prev = true;
- } elseif ( $current == 2 )
- $disable_first = true;
-
- if ( $current == $total_pages ) {
- $disable_last = true;
- $disable_next = true;
- }
+ if ( $current === 1 ) {
+ $disable_first = true;
+ $disable_prev = true;
+ } elseif ( $current === 2 )
+ $disable_first = true;
+
+ if ( $current === $total_pages ) {
+ $disable_last = true;
+ $disable_next = true;
+ }
- if ( $current == $total_pages - 1 )
- $disable_last = true;
+ if ( $current === $total_pages - 1 )
+ $disable_last = true;
if ( $disable_first )
$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">«</span>';
--- a/responsive-lightbox/includes/settings/class-settings-addons.php
+++ b/responsive-lightbox/includes/settings/class-settings-addons.php
@@ -133,11 +133,11 @@
*
* @return string
*/
- private static function fetch_addons_feed() {
- $feed = wp_remote_get( 'http://www.dfactory.co/?feed=addons&product=responsive-lightbox', [ 'sslverify' => false ] );
-
- if ( is_wp_error( $feed ) )
- return '';
+ private static function fetch_addons_feed() {
+ $feed = wp_remote_get( 'https://www.dfactory.co/?feed=addons&product=responsive-lightbox', [ 'sslverify' => true ] );
+
+ if ( is_wp_error( $feed ) )
+ return '';
$body = wp_remote_retrieve_body( $feed );
--- a/responsive-lightbox/responsive-lightbox.php
+++ b/responsive-lightbox/responsive-lightbox.php
@@ -2,7 +2,7 @@
/*
Plugin Name: Responsive Lightbox & Gallery
Description: Responsive Lightbox & Gallery allows users to create galleries and view larger versions of images, galleries and videos in a lightbox (overlay) effect optimized for mobile devices.
-Version: 2.7.1
+Version: 2.7.2
Author: dFactory
Author URI: http://www.dfactory.co/
Plugin URI: http://www.dfactory.co/products/responsive-lightbox/
@@ -45,7 +45,7 @@
* Responsive Lightbox class.
*
* @class Responsive_Lightbox
- * @version 2.7.1
+ * @version 2.7.2
*/
class Responsive_Lightbox {
@@ -284,7 +284,7 @@
'origin_left' => true,
'origin_top' => true
],
- 'version' => '2.7.1',
+ 'version' => '2.7.2',
'activation_date' => ''
];
public $options = [];
@@ -380,9 +380,9 @@
}
// add default galleries options
- $this->options['basicgrid_gallery'] = array_merge( $this->defaults['basicgrid_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicgrid_gallery', $this->defaults['basicgrid_gallery'] ) ) == false ? [] : $array ) );
- $this->options['basicslider_gallery'] = array_merge( $this->defaults['basicslider_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicslider_gallery', $this->defaults['basicslider_gallery'] ) ) == false ? [] : $array ) );
- $this->options['basicmasonry_gallery'] = array_merge( $this->defaults['basicmasonry_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicmasonry_gallery', $this->defaults['basicmasonry_gallery'] ) ) == false ? [] : $array ) );
+ $this->options['basicgrid_gallery'] = array_merge( $this->defaults['basicgrid_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicgrid_gallery', $this->defaults['basicgrid_gallery'] ) ) === false ? [] : $array ) );
+ $this->options['basicslider_gallery'] = array_merge( $this->defaults['basicslider_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicslider_gallery', $this->defaults['basicslider_gallery'] ) ) === false ? [] : $array ) );
+ $this->options['basicmasonry_gallery'] = array_merge( $this->defaults['basicmasonry_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicmasonry_gallery', $this->defaults['basicmasonry_gallery'] ) ) === false ? [] : $array ) );
// set current lightbox script
$this->current_script = $this->options['settings']['script'];
@@ -1067,7 +1067,7 @@
$other = sanitize_text_field( $_POST['other'] );
// avoid fake submissions
- if ( $option_id == 6 && $other == '' )
+ if ( $option_id === 6 && $other === '' )
wp_send_json_success();
wp_remote_post(
@@ -1321,7 +1321,7 @@
$breadcrumbs = [];
// get page
- $page_raw = isset( $_GET['page'] ) ? wp_unslash( $_GET['page'] ) : '';
+ $page_raw = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
$page_parts = $page_raw !== '' ? explode( '&', $page_raw, 2 ) : [ '' ];
$page = $page_parts[0] !== '' ? sanitize_key( $page_parts[0] ) : '';
$page_args = [];
@@ -1329,6 +1329,9 @@
if ( ! empty( $page_parts[1] ) )
parse_str( $page_parts[1], $page_args );
+ // sanitize all page_args to prevent XSS
+ $page_args = array_map( 'sanitize_text_field', $page_args );
+
// get tabs from Settings API
$api_pages = $this->settings_api->get_pages();
$tabs = [];
@@ -2165,6 +2168,55 @@
return [ 'r' => hexdec( $r ), 'g' => hexdec( $g ), 'b' => hexdec( $b ) ];
}
+
+ /**
+ * Check rate limit for AJAX actions to prevent abuse.
+ *
+ * @param string $action The action name to rate limit.
+ * @param int $max_requests Maximum number of requests allowed in time window.
+ * @param int $time_window Time window in seconds.
+ * @return bool True if request is allowed, false if rate limit exceeded.
+ */
+ public function check_rate_limit( $action, $max_requests = 30, $time_window = 60 ) {
+ $current_user_id = get_current_user_id();
+
+ // skip rate limiting for administrators
+ if ( $current_user_id && current_user_can( 'manage_options' ) ) {
+ return true;
+ }
+
+ // Get client IP with fallbacks for various server configurations
+ $client_ip = '127.0.0.1';
+ if ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
+ $client_ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
+ } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
+ // Handle proxied requests - extract first IP if multiple are present
+ $forwarded_ips = explode( ',', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) );
+ $client_ip = trim( $forwarded_ips[0] );
+ } elseif ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
+ $client_ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_CLIENT_IP'] ) );
+ }
+
+ // Validate IP address format
+ if ( ! filter_var( $client_ip, FILTER_VALIDATE_IP ) ) {
+ $client_ip = '127.0.0.1';
+ }
+
+ $transient_key = 'rl_rate_limit_' . $action . '_' . ( $current_user_id ? $current_user_id : md5( $client_ip ) );
+ $requests = get_transient( $transient_key );
+
+ if ( false === $requests ) {
+ set_transient( $transient_key, 1, $time_window );
+ return true;
+ }
+
+ if ( $requests >= $max_requests ) {
+ return false;
+ }
+
+ set_transient( $transient_key, $requests + 1, $time_window );
+ return true;
+ }
}
/**