Published : August 5, 2026

CVE-2026-18400: Slider, Gallery, and Carousel by MetaSlider <= 3.111.0 Authenticated (Author+) Stored Cross-Site Scripting via 'delay' Post Meta Setting PoC, Patch Analysis & Rule

Plugin ml-slider
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 3.111.0
Patched Version 3.111.1
Disclosed August 4, 2026

Analysis Overview

“`json
{
“analysis”: “Atomic Edge analysis of CVE-2026-18400: The Slider, Gallery, and Carousel by MetaSlider plugin for WordPress versions up to and including 3.111.0 contains a Stored Cross-Site Scripting (XSS) vulnerability. The flaw resides in the handling of the ‘delay’ Post Meta Setting, which allows authenticated attackers with Author-level access to inject arbitrary web scripts. This vulnerability has a CVSS score of 6.4 and is categorized under CWE-79.nnThe root cause of the vulnerability is insufficient input sanitization and output escaping for the ‘delay’ post meta value. The plugin’s custom ‘ml-slider’ post type is registered without custom capability restrictions, and the ‘ml-slider_settings’ meta key is unprotected. This allows an Author-level user to create a malicious ‘ml-slider’ post and set the ‘delay’ meta value with a crafted payload via XML-RPC’s custom_fields feature. When the slideshow is rendered, the ‘delay’ value is output without proper sanitization, allowing the embedded script to execute in the browser of any user viewing the page.nnExploitation begins by an Author-level user authenticating to the WordPress site via XML-RPC. The attacker crafts a request to the /xmlrpc.php endpoint using the wp.newPost method, creating a new post of type ‘ml-slider’. Within this request, the attacker sets the custom_fields parameter, specifically targeting the ‘ml-slider_settings’ key, to inject a malicious JavaScript payload into the ‘delay’ field. After the post is published, any user, including administrators, who views a page containing the slideshow will trigger the XSS payload. This is a stored attack because the malicious script is persisted in the database and executes on every subsequent page load.nnThe diff provided shows changes to the admin Table.php file, but the patch details are truncated. The core fix likely adds sanitization for the ‘delay’ value when it is retrieved and output, and may also add capability checks to prevent lower-privileged users from creating or modifying ‘ml-slider’ posts with custom fields. The patched code probably escapes the output using functions like esc_attr() or sanitizes the input, which would neutralize the malicious script. The vulnerability is only partially addressed by the admin table changes, indicating the core fix is in the slideshow frontend rendering code.nnSuccessful exploitation allows an unprivileged Author user to execute arbitrary scripts in the context of any logged-in user who views the slideshow. This can lead to session hijacking, where the attacker steals the administrator’s cookies to gain administrative access. It can also be used for the defacement of the site, the injection of malicious content or links for phishing, and the complete compromise of the WordPress installation’s security and integrity.”,
“poc_php”: “n n keyml-slider_settingsn value{“type”:”flex”,”delay”:”>alert(/XSS/)”}n n n n nnXML;nn// Step 2: Send the request using cURLn$ch = curl_init($target_url);ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);ncurl_setopt($ch, CURLOPT_POST, true);ncurl_setopt($ch, CURLOPT_POSTFIELDS, $xml_request);ncurl_setopt($ch, CURLOPT_HTTPHEADER, array(‘Content-Type: text/xml’));nn$response = curl_exec($ch);nif (curl_errno($ch)) {n echo ‘cURL error: ‘ . curl_error($ch) . “\n”;n curl_close($ch);n exit;n}ncurl_close($ch);nn// Step 3: Check if the post was created (look for the post ID in the response)npreg_match(‘/(\d+)/’, $response, $matches);nif (isset($matches[1])) {n echo “[+] Vulnerable, malicous ml-slider post created with ID: ” . $matches[1] . “\n”;n echo “[+] XSS payload will trigger when any user views this slideshow.\n”;n} else {n echo “[-] Failed to create post. Response:\n” . $response . “\n”;n}n?>n”,
“modsecurity_rule”: “# Atomic Edge WAF Rule – CVE-2026-18400n# Block the XML-RPC wp.newPost request that attempts to set a malicious ‘delay’ value inn# ‘ml-slider_settings’ meta for the ‘ml-slider’ post type.nSecRule REQUEST_URI “@streq /xmlrpc.php” \n “id:202619400,phase:1,deny,status:403,chain,msg:’CVE-2026-18400 via XML-RPC custom_fields’,severity:’CRITICAL’,tag:’CVE-2026-18400′”n SecRule REQUEST_BODY “@rx wp.newPost” “chain”n SecRule REQUEST_BODY “@rx post_type.*ml-slider” “chain”n SecRule REQUEST_BODY “@rx ml-slider_settings.*delay” “chain”n SecRule REQUEST_BODY “@rx delay.*|javascript:|on[a-z]+=” “t:lowercase”n”
}

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/ml-slider/admin/Table.php
+++ b/ml-slider/admin/Table.php
@@ -1,626 +1,628 @@
-<?php
-if (!defined('ABSPATH')) {
-    die('No direct access.');
-}
-if (!class_exists('WP_List_table')) {
-    require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php');
-}
-class MetaSlider_Admin_Table extends WP_List_table
-{
-    public function prepare_items()
-    {
-        $this->process_action();
-
-        // Check if we filter by theme
-        $nonce_valid = isset($_REQUEST['search_wpnonce']) && wp_verify_nonce(sanitize_key($_REQUEST['search_wpnonce']), 'metaslider_search_slideshows');
-        if ($nonce_valid && isset($_POST['metaslider_theme'])) {
-            $theme_param = sanitize_text_field($_POST['metaslider_theme']);
-            $current_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
-            if ($theme_param !== '') {
-                $_GET['metaslider_theme'] = $theme_param;
-                $_SERVER['REQUEST_URI'] = add_query_arg('metaslider_theme', $theme_param, $current_uri);
-            } else {
-                unset($_GET['metaslider_theme']);
-                $_SERVER['REQUEST_URI'] = remove_query_arg('metaslider_theme', $current_uri);
-            }
-        }
-        $columns = $this->get_columns();
-        $hidden = array();
-        $sortable = $this->get_sortable_columns();
-        $this->_column_headers = array($columns, $hidden, $sortable);
-
-        if (isset($_REQUEST['s'])) {
-            $table_data = $this->table_data(sanitize_text_field($_REQUEST['s']));
-        } else {
-            $table_data = $this->table_data();
-        }
-
-        //pagination
-        $global_settings = get_option( 'metaslider_global_settings' );
-        $slideshows_per_page = isset($global_settings['dashboardItems']) ? $global_settings['dashboardItems'] : 10;
-        $table_page = $this->get_pagenum();
-        $this->items = array_slice($table_data, (($table_page - 1) * $slideshows_per_page), $slideshows_per_page);
-        $total_slideshows = count($table_data);
-        $this->set_pagination_args(array(
-            'total_items' => $total_slideshows,
-            'per_page'    => $slideshows_per_page,
-            'total_pages' => ceil($total_slideshows/$slideshows_per_page)
-        ));
-    }
-
-    private function table_data($search='', $status='publish')
-    {
-        global $wpdb;
-        $wpdbTable = $wpdb->prefix . 'posts';
-        $columns = ['slides', 'post_title', 'post_date', 'slide_count', 'slideshow_type', 'slideshow_theme'];
-        $global_settings = get_option( 'metaslider_global_settings' );
-
-        $orderBy = $_GET['orderby'] ?? $global_settings['dashboardSort'] ?? 'ID';
-        $orderBy = in_array($orderBy, $columns, true) ? $orderBy : 'ID';
-
-        $order = $_GET['order'] ?? $global_settings['dashboardOrder'] ?? 'asc';
-
-        $status = isset($_GET['post_status']) && 'trash' === $_GET['post_status'] ? 'trash' : 'publish';
-
-        $query_results = $this->get_slideshow_query($status, $search);
-
-        foreach ($query_results as &$each_slide) {
-            $theme = get_post_meta($each_slide['ID'], 'metaslider_slideshow_theme', true);
-
-            if ($theme && isset($theme['title'])) {
-                $each_slide['slideshow_theme'] = $theme['title'];
-                $each_slide['slideshow_theme_folder'] = $theme['folder'] ?? '';
-            } else {
-                $each_slide['slideshow_theme'] = '';
-                $each_slide['slideshow_theme_folder'] = '';
-            }
-
-            $slideshow_slides = $this->get_slides($each_slide['ID'], $status);
-            $each_slide['slideshow_thumb'] = $slideshow_slides;
-            $each_slide['slideshow_type'] = $this->get_slide_types($slideshow_slides);
-            $each_slide['slide_count'] = count($slideshow_slides);
-        }
-
-        $theme_filter = isset($_REQUEST['metaslider_theme']) ? sanitize_text_field($_REQUEST['metaslider_theme']) : '';
-        if ($theme_filter !== '') {
-            $query_results = array_values(array_filter($query_results, function ($slide) use ($theme_filter) {
-                return $theme_filter === '__none__'
-                    ? $slide['slideshow_theme_folder'] === ''
-                    : $slide['slideshow_theme_folder'] === $theme_filter;
-            }));
-        }
-
-        if ($orderBy === 'slide_count') {
-            if ($order === 'asc') {
-                usort($query_results, function($a, $b) {
-                    return $a['slide_count'] - $b['slide_count'];
-                });
-            } else {
-                usort($query_results, function($a, $b) {
-                    return $b['slide_count'] - $a['slide_count'];
-                });
-            }
-        } elseif ($orderBy === 'post_date') {
-            usort($query_results, function($a, $b) use ($orderBy, $order) {
-                $aDate = !empty($a[$orderBy]) ? strtotime($a[$orderBy]) : 0;
-                $bDate = !empty($b[$orderBy]) ? strtotime($b[$orderBy]) : 0;
-                return ($order === 'asc')
-                    ? $aDate - $bDate
-                    : $bDate - $aDate;
-            });
-        } else {
-            usort($query_results, function($a, $b) use ($orderBy, $order) {
-                return ($order === 'asc') ? strcmp($a[$orderBy], $b[$orderBy]) : strcmp($b[$orderBy], $a[$orderBy]);
-            });
-        }
-
-        return $query_results;
-    }
-
-    public function no_items()
-    {
-        if (!empty($_GET['post_status']) && $_GET['post_status'] === 'trash') {
-            esc_html_e(
-                'You don't have any trashed slideshows.',
-                'ml-slider'
-            );
-        } else {
-            printf(
-                esc_html__(
-                    'You don't have any slideshows yet. Click %shere%s to create a new slideshow.',
-                    'ml-slider'
-                ),
-                '<a href="' . esc_url(wp_nonce_url(admin_url("admin-post.php?action=metaslider_create_slider"), "metaslider_create_slider")) . '">','</a>'
-            );
-        }
-
-    }
-
-    protected function get_views() {
-        global $wpdb;
-
-        $views = [];
-        $parameters = ['action', 'slideshows', 'post_status', '_wpnonce', 'paged'];
-        $current = $_REQUEST['post_status'] ?? 'all';
-        $base_url = remove_query_arg($parameters);
-
-        // Helper function to generate view links
-        $generate_view_link = function ($status, $label) use ($base_url, $current) {
-            $count = $this->slideshow_count($status);
-            if ($count == 0 && $status === 'trash') {
-                return null;
-            }
-            $url = ($status === 'all') ? $base_url : add_query_arg('post_status', $status, $base_url);
-            $class = ($current === $status) ? ' class="current"' : '';
-            return "<a href='" . esc_url($url) . "' {$class}>" . esc_html__($label, 'ml-slider') . " ({$count})</a>";
-        };
-
-        $views['all'] = $generate_view_link('all', 'Published');
-
-        if ($trash_link = $generate_view_link('trash', 'Trash')) {
-            $views['trash'] = $trash_link;
-        }
-
-        return $views;
-    }
-
-    private function slideshow_count($status = 'all')
-    {
-        if ($status === 'all') {
-            $status = 'publish';
-        }
-        $results = $this->get_slideshow_query($status);
-        return count($results);
-    }
-
-    public function get_bulk_actions()
-    {
-        if (isset($_REQUEST['post_status']) && $_REQUEST['post_status'] == "trash") {
-            $actions = array(
-                'restore'    => __('Restore', 'ml-slider'),
-                'permanent'  => __('Delete Permanently', 'ml-slider')
-            );
-        } else {
-            $actions = array(
-                'delete'    => __('Trash', 'ml-slider')
-            );
-        }
-
-        return $actions;
-    }
-
-    public function get_columns()
-    {
-        $columns = array(
-            'cb' => '<input type="checkbox" />',
-            'slideshow_thumb' => esc_html__('Preview', 'ml-slider'),
-            'post_title' => esc_html__('Title', 'ml-slider'),
-            'slideshow_type' => esc_html__('Type of Slides', 'ml-slider'),
-            'slide_count' => esc_html__('Number of Slides', 'ml-slider'),
-            'slideshow_theme' => esc_html__('Theme', 'ml-slider'),
-            'post_date' => esc_html__('Created', 'ml-slider'),
-            'ID' => esc_html__('Shortcode', 'ml-slider')
-        );
-
-        $status = $_REQUEST['post_status'] ?? 'publish';
-        if ($status !== 'trash') {
-            $columns['used_on'] = __('Usage', 'ml-slider');
-        }
-
-        return $columns;
-    }
-
-    public function get_sortable_columns()
-    {
-        $sortable_columns = array(
-          'post_title' => array('post_title', false),
-          'post_date' => array('post_date', false),
-          'slideshow_type' => array('slideshow_type',false),
-          'slide_count' => array('slide_count',false),
-          'slideshow_theme' => array('slideshow_theme',false)
-        );
-        return $sortable_columns;
-    }
-
-    public function getslide_thumb($slideId)
-    {
-        $logo = 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(dirname(__FILE__) . '/assets/metaslider.svg'));
-        if (get_post_type($slideId) == 'attachment') {
-            $image = wp_get_attachment_image_src($slideId, 'thumbnail');
-        } else {
-            $image = wp_get_attachment_image_src(get_post_thumbnail_id($slideId), 'thumbnail');
-        }
-
-        if (isset($image[0])) {
-            $slidethumb = "<img src='". esc_url($image[0]) ."'>";
-        } else {
-            $slidethumb = "<img src='". $logo ."' class='thumb-logo'>";
-        }
-        return $slidethumb;
-    }
-
-    private function get_slideshow_query($status = 'publish', $search = '') {
-        global $wpdb;
-        $wpdbTable = $wpdb->prefix . 'posts';
-
-        $query = "SELECT ID, post_title, post_date FROM $wpdbTable WHERE post_type = %s AND post_status = %s";
-        $params = ['ml-slider', $status];
-
-        if (!empty($search)) {
-            $query .= " AND post_title LIKE %s";
-            $params[] = '%' . $wpdb->esc_like($search) . '%';
-        }
-
-        $prepared_query = $wpdb->prepare($query, $params); // WPCS: unprepared SQL OK.
-        return $wpdb->get_results($prepared_query, ARRAY_A); // WPCS: unprepared SQL OK.
-    }
-
-    public function get_slides($slideshowId, $status)
-    {
-        $post_status = $status === 'trash' ? array('trash', 'publish') : array($status);
-        $slides = get_posts(array(
-            'post_type' => array('ml-slide'),
-            'post_status' => $post_status,
-            'orderby' => 'menu_order',
-            'order' => 'ASC',
-            'lang' => '',
-            'suppress_filters' => 1,
-            'posts_per_page' => -1,
-            'tax_query' => array(
-                array(
-                    'taxonomy' => 'ml-slider',
-                    'field' => 'slug',
-                    'terms' => (int) $slideshowId
-                )
-            )
-        ));
-
-        return $slides;
-    }
-
-    public function get_slide_types($slides)
-    {
-        $slide_list = array();
-        foreach ($slides as $slide) {
-            $post_title = explode(' - ', $slide->post_title);
-            $add_space = str_replace('_', ' ', $post_title[1]);
-            if($add_space == 'html overlay') {
-                $slide_list[] = 'Layer';
-            } else {
-                $slide_list[] = ucwords($add_space);
-            }
-        }
-        $final_list = array_values(array_unique($slide_list));
-        $type_list = '';
-        foreach ($final_list as $list) {
-            $type_list .= $list . '<br>';
-        }
-        return $type_list;
-    }
-
-    public function column_cb($item)
-    {
-        return sprintf(
-            '<input type="checkbox" name="slideshows[]" value="%d" />', (int)$item['ID']
-        );
-    }
-
-    public function column_slideshow_thumb($item)
-    {
-        $logo = 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(dirname(__FILE__) . '/assets/metaslider.svg'));
-        $placeholder = "<img src='". $logo ."' class='thumb-logo'>";
-        $numberOfSlides = count($item['slideshow_thumb']);
-
-        $thumbHtml = "<div class='w-16 h-16 bg-gray-light slidethumb'>";
-        if ($numberOfSlides === 0) {
-            $thumbHtml .= $placeholder;
-        } else {
-            if ($numberOfSlides === 1){
-                $thumbHtml .= isset($item['slideshow_thumb']->ID)
-                            ? $this->getslide_thumb($item['slideshow_thumb']->ID) : $placeholder;
-            } else {
-                foreach ($item['slideshow_thumb'] as $thumb) {
-                    $thumbHtml .= isset($thumb->ID) ? $this->getslide_thumb($thumb->ID) : $placeholder;
-                }
-            }
-        }
-        $thumbHtml .= "</div>";
-        return $thumbHtml;
-    }
-
-    public function column_post_title($item)
-    {
-        $page = empty($_REQUEST['page']) ? 'metaslider' : sanitize_key($_REQUEST['page']);
-        if(isset($_GET['post_status']) && $_GET['post_status'] == 'trash') {
-            $restoreUrl = wp_nonce_url('?page=' . $page . '&post_status=trash&action=restore&slideshows=' . absint($item['ID']), 'bulk-' . $this->_args['plural'] );
-            $deleteUrl = wp_nonce_url('?page=' . $page . '&post_status=trash&action=permanent&slideshows=' . absint($item['ID']), 'bulk-' . $this->_args['plural'] );
-
-            $actions = [
-                'restore' => '<a href="' . esc_url($restoreUrl) . '">' . esc_html__('Restore', 'ml-slider') . '</a>',
-                'permanent'  => '<a class="submitdelete" href="' . esc_url($deleteUrl) . '">' . esc_html__('Delete Permanently', 'ml-slider') . '</a>',
-            ];
-
-            return sprintf(
-                '%1$s %2$s',
-                '<a class="row-title">' . esc_html($item['post_title']) . '</a>',
-                $this->row_actions($actions)
-            );
-        } else {
-            $editUrl = '?page=' . $page . '&id=' . absint($item['ID']);
-            $deleteUrl = wp_nonce_url('?page=' . $page . '&action=delete&slideshows=' . absint($item['ID']), 'bulk-' . $this->_args['plural'] );
-
-            $actions = [
-                'edit' => '<a href="' . esc_url($editUrl) . '">' . esc_html__('Edit', 'ml-slider') . '</a>',
-                'trash'  => '<a class="submitdelete" href="' . esc_url($deleteUrl) . '">' . esc_html__('Trash', 'ml-slider') . '</a>',
-            ];
-
-            return sprintf(
-                '%1$s %2$s',
-                '<a class="row-title" href="' . esc_url($editUrl) . '">' . esc_html($item['post_title']) . '</a>',
-                $this->row_actions($actions)
-            );
-        }
-    }
-
-    public function column_slideshow_type($item)
-    {
-        return $item['slideshow_type'];
-    }
-
-    public function column_slide_count($item)
-    {
-        return $item['slide_count'];
-    }
-
-    public function column_slideshow_theme($item)
-    {
-        return $item['slideshow_theme'];
-    }
-
-    public function column_post_date($item)
-    {
-        $date = strtotime($item['post_date']);
-        $dateFormat = get_option('date_format');
-        $timeFormat = get_option( 'time_format' );
-        return ucfirst( wp_date( $dateFormat.' at '.$timeFormat, $date ) );
-    }
-
-    public function column_ID($item)
-    {
-        return ('<pre class="copy-shortcode tipsy-tooltip" original-title="' . __('Click to copy shortcode.', 'ml-slider') . '"><div class="text-orange cursor-pointer whitespace-normal inline">[metaslider id="'. esc_attr($item['ID']) .'"]</div></pre><span class="copy-message" style="display:none;"><div class="dashicons dashicons-yes"></div></span>');
-    }
-
-    public function column_used_on($item)
-    {
-        $slideshow_id = $item['ID'];
-        $pages = $this->get_posts_using_slideshow($slideshow_id);
-
-        if ($pages !== esc_html__('Not found.', 'ml-slider')) {
-            return '<button class="open-modal button" data-id="' . esc_attr($slideshow_id) . '">'
-                . esc_html__('View Usage', 'ml-slider') . '</button>
-                <div class="modal-overlay" id="overlay-' . esc_attr($slideshow_id) . '" style="display: none;"></div>
-                <div class="shortcode-modal bg-white shadow" id="modal-' . esc_attr($slideshow_id) . '" style="display: none;">
-                    <div class="modal-content">
-                        <span class="close-modal" data-id="' . esc_attr($slideshow_id) . '">×</span>
-                        <h3 class="text-lg font-medium m-0 leading-6 text-gray-darkest">'
-                            . esc_html__('Content Using This Slideshow', 'ml-slider') . '</h3>'
-                            . $pages . '
-                    </div>
-                </div>';
-        } else {
-            return esc_html__('Not found.', 'ml-slider');
-        }
-    }
-
-
-    private function get_posts_using_slideshow($slideshow_id)
-    {
-        global $wpdb;
-        $results = [];
-
-        $posts = $wpdb->get_results(
-            $wpdb->prepare(
-                "SELECT ID, post_title, post_type FROM {$wpdb->posts} WHERE post_status = %s AND (post_content LIKE %s OR post_content REGEXP %s)",
-                'publish',
-                '%' . $wpdb->esc_like("[metaslider id="$slideshow_id"") . '%',
-                '\[metaslider[^]]*id=["\']?' . $slideshow_id . '["\']?[^]]*\]'
-            )
-        );
-
-        // Organize by post type
-        $grouped_results = [];
-
-        foreach ($posts as $post) {
-            $post_type_obj = get_post_type_object($post->post_type);
-            $post_type_label = $post_type_obj ? $post_type_obj->labels->singular_name : ucfirst($post->post_type);
-
-            $grouped_results[$post_type_label][] = sprintf(
-                '<li><a href="%s" target="_blank">%s</a></li>',
-                esc_url(get_permalink($post->ID)),
-                esc_html($post->post_title)
-            );
-        }
-
-        $theme_mods = get_option('theme_mods_' . get_option('stylesheet'), []);
-        if (is_array($theme_mods)) {
-            foreach ($theme_mods as $key => $value) {
-                if (is_string($value) && preg_match('/[metaslider[^]]*id=["']?' . $slideshow_id . '["']?[^]]*]/', $value)) {
-                    $grouped_results[esc_html__('Theme Setting', 'ml-slider')][] = "<li>" . esc_html($key) . "</li>";
-                }
-            }
-        }
-
-        $all_options = $wpdb->get_results(
-            $wpdb->prepare(
-                "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_value LIKE %s",
-                '%' . $wpdb->esc_like("[metaslider id="$slideshow_id"") . '%'
-            )
-        );
-
-        foreach ($all_options as $option) {
-            if (is_string($option->option_value) && preg_match('/[metaslider[^]]*id=["']?' . $slideshow_id . '["']?[^]]*]/', $option->option_value)) {
-                $grouped_results[esc_html__('Option', 'ml-slider')][] = "<li>" . esc_html($option->option_name) . "</li>";
-            }
-        }
-
-        $output = '';
-        foreach ($grouped_results as $category => $items) {
-            $output .= "<h5>{$category}:</h5><ul>" . implode('', $items) . "</ul>";
-        }
-
-        return empty($output) ? esc_html__('Not found.', 'ml-slider') : $output;
-    }
-
-    public function extra_tablenav( $which )
-    {
-        if ( $which == "top" ) {
-            echo '<input type="hidden" name="page" value="' . esc_attr($_REQUEST['page'] ?? 'metaslider') . '">';
-            $this->render_filters();
-            if (isset($_REQUEST['post_status']) && $_REQUEST['post_status'] == "trash") {
-                if ( ! empty($this->table_data('', 'trash'))) {
-                    submit_button( __( 'Empty Trash' ), 'apply', 'delete_all', false );
-                }
-            }
-        }
-    }
-
-    /**
-     * Renders table nav filter dropdowns; currently includes theme, extendable for future filters.
-     *
-     * @since 3.110.0
-     *
-     * @return void
-     */
-    private function render_filters()
-    {
-        $status = (isset($_GET['post_status']) && 'trash' === $_GET['post_status']) ? 'trash' : 'publish';
-        $slideshows = $this->get_slideshow_query($status);
-
-        $themes = [];
-        $has_no_theme = false;
-        foreach ($slideshows as $slideshow) {
-            $theme = get_post_meta($slideshow['ID'], 'metaslider_slideshow_theme', true);
-            if ($theme && isset($theme['folder'], $theme['title'])) {
-                $themes[$theme['folder']] = $theme['title'];
-            } else {
-                $has_no_theme = true;
-            }
-        }
-
-        if (empty($themes) && !$has_no_theme) return;
-
-        asort($themes);
-
-        $current = isset($_REQUEST['metaslider_theme']) ? sanitize_text_field($_REQUEST['metaslider_theme']) : '';
-
-        echo '<div class="alignleft actions">';
-        echo '<select name="metaslider_theme" id="metaslider-theme-filter">';
-        echo '<option value="">' . esc_html__('All Themes', 'ml-slider') . '</option>';
-        foreach ($themes as $folder => $title) {
-            printf(
-                '<option value="%s"%s>%s</option>',
-                esc_attr($folder),
-                selected($current, $folder, false),
-                esc_html($title)
-            );
-        }
-        if ($has_no_theme) {
-            printf(
-                '<option value="__none__"%s>%s</option>',
-                selected($current, '__none__', false),
-                esc_html__('(No Theme)', 'ml-slider')
-            );
-        }
-        echo '</select> ';
-        submit_button(__('Filter', 'ml-slider'), 'button', 'filter_action', false);
-        echo '</div>';
-    }
-
-    public function check_num_rows()
-    {
-        $table_data = $this->table_data();
-        return $table_data;
-    }
-
-    protected function process_action()
-    {
-        $action = $this->current_action();
-
-        if (isset($_POST['delete_all'])
-            || ($action && in_array($action, array('delete', 'restore', 'permanent')))
-        ) {
-            // Check nonce
-            if (! isset($_REQUEST['_wpnonce'])
-                || empty($_REQUEST['_wpnonce'])
-                || ! wp_verify_nonce(
-                    sanitize_key($_REQUEST['_wpnonce']),
-                    'bulk-' . $this->_args['plural']
-                )
-            ) {
-                wp_die('Cannot process action', 'ml-slider');
-            }
-
-            if (isset($_POST['delete_all'])) {
-                $slideshows = $this->table_data('', 'trash');
-                foreach($slideshows as $slideshow_id) {
-                    wp_delete_post($slideshow_id['ID'], true);
-                }
-
-                return;
-            }
-
-            if(isset($_REQUEST['slideshows'])) {
-
-                if(is_array($_REQUEST['slideshows'])) {
-                    $slideshows = array_map('intval', $_REQUEST['slideshows']);
-                } else {
-                    $toArray = array($_REQUEST['slideshows']);
-                    $slideshows = array_map('intval', $toArray);
-                }
-            } else {
-                //single slider
-                if(isset($_REQUEST['id'])) {
-                    $toArray = array($_REQUEST['id']);
-                    $slideshows = array_map('intval', $toArray);
-                }
-            }
-
-            switch ( $action ) {
-                case 'delete':
-                    foreach($slideshows as $slideshow_id) {
-                        wp_update_post(array(
-                            'ID' => $slideshow_id,
-                            'post_status' => 'trash'
-                        ));
-                    }
-                    break;
-                case 'permanent':
-                    foreach($slideshows as $slideshow_id) {
-                        wp_delete_post( $slideshow_id, true);
-                    }
-                    break;
-                case 'restore':
-                    foreach($slideshows as $slideshow_id) {
-                        wp_update_post(array(
-                            'ID' => $slideshow_id,
-                            'post_status' => 'publish'
-                        ));
-
-                        $slides = $this->get_slides($slideshow_id, 'trash');
-                        foreach ($slides as $key => $slide) {
-                            wp_update_post(array(
-                                'ID' => $slide->ID,
-                                'post_status' => 'publish'
-                            ));
-                        }
-                    }
-                    break;
-                default:
-                    return;
-                    break;
-            }
-        }
-
-        return;
-    }
-}
+<?php
+if (!defined('ABSPATH')) {
+    die('No direct access.');
+}
+if (!class_exists('WP_List_table')) {
+    require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php');
+}
+class MetaSlider_Admin_Table extends WP_List_table
+{
+    public function prepare_items()
+    {
+        $this->process_action();
+
+        // Check if we filter by theme
+        $nonce_valid = isset($_REQUEST['search_wpnonce']) && wp_verify_nonce(sanitize_key($_REQUEST['search_wpnonce']), 'metaslider_search_slideshows');
+        if ($nonce_valid && isset($_POST['metaslider_theme'])) {
+            $theme_param = sanitize_text_field($_POST['metaslider_theme']);
+            $current_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
+            if ($theme_param !== '') {
+                $_GET['metaslider_theme'] = $theme_param;
+                $_SERVER['REQUEST_URI'] = add_query_arg('metaslider_theme', $theme_param, $current_uri);
+            } else {
+                unset($_GET['metaslider_theme']);
+                $_SERVER['REQUEST_URI'] = remove_query_arg('metaslider_theme', $current_uri);
+            }
+        }
+        $columns = $this->get_columns();
+        $hidden = array();
+        $sortable = $this->get_sortable_columns();
+        $this->_column_headers = array($columns, $hidden, $sortable);
+
+        if (isset($_REQUEST['s'])) {
+            $table_data = $this->table_data(sanitize_text_field($_REQUEST['s']));
+        } else {
+            $table_data = $this->table_data();
+        }
+
+        //pagination
+        $global_settings = get_option( 'metaslider_global_settings' );
+        $slideshows_per_page = isset($global_settings['dashboardItems']) ? $global_settings['dashboardItems'] : 10;
+        $table_page = $this->get_pagenum();
+        $this->items = array_slice($table_data, (($table_page - 1) * $slideshows_per_page), $slideshows_per_page);
+        $total_slideshows = count($table_data);
+        $this->set_pagination_args(array(
+            'total_items' => $total_slideshows,
+            'per_page'    => $slideshows_per_page,
+            'total_pages' => ceil($total_slideshows/$slideshows_per_page)
+        ));
+    }
+
+    private function table_data($search='', $status='publish')
+    {
+        global $wpdb;
+        $wpdbTable = $wpdb->prefix . 'posts';
+        $columns = ['slides', 'post_title', 'post_date', 'slide_count', 'slideshow_type', 'slideshow_theme'];
+        $global_settings = get_option( 'metaslider_global_settings' );
+
+        $orderBy = $_GET['orderby'] ?? $global_settings['dashboardSort'] ?? 'ID';
+        $orderBy = in_array($orderBy, $columns, true) ? $orderBy : 'ID';
+
+        $order = $_GET['order'] ?? $global_settings['dashboardOrder'] ?? 'asc';
+
+        $status = isset($_GET['post_status']) && 'trash' === $_GET['post_status'] ? 'trash' : 'publish';
+
+        $query_results = $this->get_slideshow_query($status, $search);
+
+        foreach ($query_results as &$each_slide) {
+            $theme = get_post_meta($each_slide['ID'], 'metaslider_slideshow_theme', true);
+
+            if ($theme && isset($theme['title'])) {
+                $each_slide['slideshow_theme'] = $theme['title'];
+                $each_slide['slideshow_theme_folder'] = $theme['folder'] ?? '';
+            } else {
+                $each_slide['slideshow_theme'] = '';
+                $each_slide['slideshow_theme_folder'] = '';
+            }
+
+            $slideshow_slides = $this->get_slides($each_slide['ID'], $status);
+            $each_slide['slideshow_thumb'] = $slideshow_slides;
+            $each_slide['slideshow_type'] = $this->get_slide_types($slideshow_slides);
+            $each_slide['slide_count'] = count($slideshow_slides);
+        }
+
+        $theme_filter = isset($_REQUEST['metaslider_theme']) ? sanitize_text_field($_REQUEST['metaslider_theme']) : '';
+        if ($theme_filter !== '') {
+            $query_results = array_values(array_filter($query_results, function ($slide) use ($theme_filter) {
+                return $theme_filter === '__none__'
+                    ? $slide['slideshow_theme_folder'] === ''
+                    : $slide['slideshow_theme_folder'] === $theme_filter;
+            }));
+        }
+
+        if ($orderBy === 'slide_count') {
+            if ($order === 'asc') {
+                usort($query_results, function($a, $b) {
+                    return $a['slide_count'] - $b['slide_count'];
+                });
+            } else {
+                usort($query_results, function($a, $b) {
+                    return $b['slide_count'] - $a['slide_count'];
+                });
+            }
+        } elseif ($orderBy === 'post_date') {
+            usort($query_results, function($a, $b) use ($orderBy, $order) {
+                $aDate = !empty($a[$orderBy]) ? strtotime($a[$orderBy]) : 0;
+                $bDate = !empty($b[$orderBy]) ? strtotime($b[$orderBy]) : 0;
+                return ($order === 'asc')
+                    ? $aDate - $bDate
+                    : $bDate - $aDate;
+            });
+        } else {
+            usort($query_results, function($a, $b) use ($orderBy, $order) {
+                return ($order === 'asc') ? strcmp($a[$orderBy], $b[$orderBy]) : strcmp($b[$orderBy], $a[$orderBy]);
+            });
+        }
+
+        return $query_results;
+    }
+
+    public function no_items()
+    {
+        if (!empty($_GET['post_status']) && $_GET['post_status'] === 'trash') {
+            esc_html_e(
+                'You don't have any trashed slideshows.',
+                'ml-slider'
+            );
+        } else {
+            printf(
+                esc_html__(
+                    'You don't have any slideshows yet. Click %shere%s to create a new slideshow.',
+                    'ml-slider'
+                ),
+                '<a href="' . esc_url(wp_nonce_url(admin_url("admin-post.php?action=metaslider_create_slider"), "metaslider_create_slider")) . '">','</a>'
+            );
+        }
+
+    }
+
+    protected function get_views() {
+        global $wpdb;
+
+        $views = [];
+        $parameters = ['action', 'slideshows', 'post_status', '_wpnonce', 'paged'];
+        $current = $_REQUEST['post_status'] ?? 'all';
+        $base_url = remove_query_arg($parameters);
+
+        // Helper function to generate view links
+        $generate_view_link = function ($status, $label) use ($base_url, $current) {
+            $count = $this->slideshow_count($status);
+            if ($count == 0 && $status === 'trash') {
+                return null;
+            }
+            $url = ($status === 'all') ? $base_url : add_query_arg('post_status', $status, $base_url);
+            $class = ($current === $status) ? ' class="current"' : '';
+            return "<a href='" . esc_url($url) . "' {$class}>" . esc_html__($label, 'ml-slider') . " ({$count})</a>";
+        };
+
+        $views['all'] = $generate_view_link('all', 'Published');
+
+        if ($trash_link = $generate_view_link('trash', 'Trash')) {
+            $views['trash'] = $trash_link;
+        }
+
+        return $views;
+    }
+
+    private function slideshow_count($status = 'all')
+    {
+        if ($status === 'all') {
+            $status = 'publish';
+        }
+        $results = $this->get_slideshow_query($status);
+        return count($results);
+    }
+
+    public function get_bulk_actions()
+    {
+        if (isset($_REQUEST['post_status']) && $_REQUEST['post_status'] == "trash") {
+            $actions = array(
+                'restore'    => __('Restore', 'ml-slider'),
+                'permanent'  => __('Delete Permanently', 'ml-slider')
+            );
+        } else {
+            $actions = array(
+                'delete'    => __('Trash', 'ml-slider')
+            );
+        }
+
+        return $actions;
+    }
+
+    public function get_columns()
+    {
+        $columns = array(
+            'cb' => '<input type="checkbox" />',
+            'slideshow_thumb' => esc_html__('Preview', 'ml-slider'),
+            'post_title' => esc_html__('Title', 'ml-slider'),
+            'slideshow_type' => esc_html__('Type of Slides', 'ml-slider'),
+            'slide_count' => esc_html__('Number of Slides', 'ml-slider'),
+            'slideshow_theme' => esc_html__('Theme', 'ml-slider'),
+            'post_date' => esc_html__('Created', 'ml-slider'),
+            'ID' => esc_html__('Shortcode', 'ml-slider')
+        );
+
+        $status = $_REQUEST['post_status'] ?? 'publish';
+        if ($status !== 'trash') {
+            $columns['used_on'] = __('Usage', 'ml-slider');
+        }
+
+        return $columns;
+    }
+
+    public function get_sortable_columns()
+    {
+        $sortable_columns = array(
+          'post_title' => array('post_title', false),
+          'post_date' => array('post_date', false),
+          'slideshow_type' => array('slideshow_type',false),
+          'slide_count' => array('slide_count',false),
+          'slideshow_theme' => array('slideshow_theme',false)
+        );
+        return $sortable_columns;
+    }
+
+    public function getslide_thumb($slideId)
+    {
+        $logo = 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(dirname(__FILE__) . '/assets/metaslider.svg'));
+        if (get_post_type($slideId) == 'attachment') {
+            $image = wp_get_attachment_image_src($slideId, 'thumbnail');
+        } else {
+            $image = wp_get_attachment_image_src(get_post_thumbnail_id($slideId), 'thumbnail');
+        }
+
+        if (isset($image[0])) {
+            $slidethumb = "<img src='". esc_url($image[0]) ."'>";
+        } else {
+            $slidethumb = "<img src='". $logo ."' class='thumb-logo'>";
+        }
+        return $slidethumb;
+    }
+
+    private function get_slideshow_query($status = 'publish', $search = '') {
+        global $wpdb;
+        $wpdbTable = $wpdb->prefix . 'posts';
+
+        $query = "SELECT ID, post_title, post_date FROM $wpdbTable WHERE post_type = %s AND post_status = %s";
+        $params = ['ml-slider', $status];
+
+        if (!empty($search)) {
+            $query .= " AND post_title LIKE %s";
+            $params[] = '%' . $wpdb->esc_like($search) . '%';
+        }
+
+        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query fragments are fixed SQL above, with all dynamic values passed through $wpdb->prepare().
+        $prepared_query = $wpdb->prepare($query, $params);
+        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $prepared_query is prepared immediately above.
+        return $wpdb->get_results($prepared_query, ARRAY_A);
+    }
+
+    public function get_slides($slideshowId, $status)
+    {
+        $post_status = $status === 'trash' ? array('trash', 'publish') : array($status);
+        $slides = get_posts(array(
+            'post_type' => array('ml-slide'),
+            'post_status' => $post_status,
+            'orderby' => 'menu_order',
+            'order' => 'ASC',
+            'lang' => '',
+            'suppress_filters' => 1,
+            'posts_per_page' => -1,
+            'tax_query' => array(
+                array(
+                    'taxonomy' => 'ml-slider',
+                    'field' => 'slug',
+                    'terms' => (int) $slideshowId
+                )
+            )
+        ));
+
+        return $slides;
+    }
+
+    public function get_slide_types($slides)
+    {
+        $slide_list = array();
+        foreach ($slides as $slide) {
+            $post_title = explode(' - ', $slide->post_title);
+            $add_space = str_replace('_', ' ', $post_title[1]);
+            if($add_space == 'html overlay') {
+                $slide_list[] = 'Layer';
+            } else {
+                $slide_list[] = ucwords($add_space);
+            }
+        }
+        $final_list = array_values(array_unique($slide_list));
+        $type_list = '';
+        foreach ($final_list as $list) {
+            $type_list .= $list . '<br>';
+        }
+        return $type_list;
+    }
+
+    public function column_cb($item)
+    {
+        return sprintf(
+            '<input type="checkbox" name="slideshows[]" value="%d" />', (int)$item['ID']
+        );
+    }
+
+    public function column_slideshow_thumb($item)
+    {
+        $logo = 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(dirname(__FILE__) . '/assets/metaslider.svg'));
+        $placeholder = "<img src='". $logo ."' class='thumb-logo'>";
+        $numberOfSlides = count($item['slideshow_thumb']);
+
+        $thumbHtml = "<div class='w-16 h-16 bg-gray-light slidethumb'>";
+        if ($numberOfSlides === 0) {
+            $thumbHtml .= $placeholder;
+        } else {
+            if ($numberOfSlides === 1){
+                $thumbHtml .= isset($item['slideshow_thumb']->ID)
+                            ? $this->getslide_thumb($item['slideshow_thumb']->ID) : $placeholder;
+            } else {
+                foreach ($item['slideshow_thumb'] as $thumb) {
+                    $thumbHtml .= isset($thumb->ID) ? $this->getslide_thumb($thumb->ID) : $placeholder;
+                }
+            }
+        }
+        $thumbHtml .= "</div>";
+        return $thumbHtml;
+    }
+
+    public function column_post_title($item)
+    {
+        $page = empty($_REQUEST['page']) ? 'metaslider' : sanitize_key($_REQUEST['page']);
+        if(isset($_GET['post_status']) && $_GET['post_status'] == 'trash') {
+            $restoreUrl = wp_nonce_url('?page=' . $page . '&post_status=trash&action=restore&slideshows=' . absint($item['ID']), 'bulk-' . $this->_args['plural'] );
+            $deleteUrl = wp_nonce_url('?page=' . $page . '&post_status=trash&action=permanent&slideshows=' . absint($item['ID']), 'bulk-' . $this->_args['plural'] );
+
+            $actions = [
+                'restore' => '<a href="' . esc_url($restoreUrl) . '">' . esc_html__('Restore', 'ml-slider') . '</a>',
+                'permanent'  => '<a class="submitdelete" href="' . esc_url($deleteUrl) . '">' . esc_html__('Delete Permanently', 'ml-slider') . '</a>',
+            ];
+
+            return sprintf(
+                '%1$s %2$s',
+                '<a class="row-title">' . esc_html($item['post_title']) . '</a>',
+                $this->row_actions($actions)
+            );
+        } else {
+            $editUrl = '?page=' . $page . '&id=' . absint($item['ID']);
+            $deleteUrl = wp_nonce_url('?page=' . $page . '&action=delete&slideshows=' . absint($item['ID']), 'bulk-' . $this->_args['plural'] );
+
+            $actions = [
+                'edit' => '<a href="' . esc_url($editUrl) . '">' . esc_html__('Edit', 'ml-slider') . '</a>',
+                'trash'  => '<a class="submitdelete" href="' . esc_url($deleteUrl) . '">' . esc_html__('Trash', 'ml-slider') . '</a>',
+            ];
+
+            return sprintf(
+                '%1$s %2$s',
+                '<a class="row-title" href="' . esc_url($editUrl) . '">' . esc_html($item['post_title']) . '</a>',
+                $this->row_actions($actions)
+            );
+        }
+    }
+
+    public function column_slideshow_type($item)
+    {
+        return $item['slideshow_type'];
+    }
+
+    public function column_slide_count($item)
+    {
+        return $item['slide_count'];
+    }
+
+    public function column_slideshow_theme($item)
+    {
+        return $item['slideshow_theme'];
+    }
+
+    public function column_post_date($item)
+    {
+        $date = strtotime($item['post_date']);
+        $dateFormat = get_option('date_format');
+        $timeFormat = get_option( 'time_format' );
+        return ucfirst( wp_date( $dateFormat.' at '.$timeFormat, $date ) );
+    }
+
+    public function column_ID($item)
+    {
+        return ('<pre class="copy-shortcode tipsy-tooltip" original-title="' . __('Click to copy shortcode.', 'ml-slider') . '"><div class="text-orange cursor-pointer whitespace-normal inline">[metaslider id="'. esc_attr($item['ID']) .'"]</div></pre><span class="copy-message" style="display:none;"><div class="dashicons dashicons-yes"></div></span>');
+    }
+
+    public function column_used_on($item)
+    {
+        $slideshow_id = $item['ID'];
+        $pages = $this->get_posts_using_slideshow($slideshow_id);
+
+        if ($pages !== esc_html__('Not found.', 'ml-slider')) {
+            return '<button class="open-modal button" data-id="' . esc_attr($slideshow_id) . '">'
+                . esc_html__('View Usage', 'ml-slider') . '</button>
+                <div class="modal-overlay" id="overlay-' . esc_attr($slideshow_id) . '" style="display: none;"></div>
+                <div class="shortcode-modal bg-white shadow" id="modal-' . esc_attr($slideshow_id) . '" style="display: none;">
+                    <div class="modal-content">
+                        <span class="close-modal" data-id="' . esc_attr($slideshow_id) . '">×</span>
+                        <h3 class="text-lg font-medium m-0 leading-6 text-gray-darkest">'
+                            . esc_html__('Content Using This Slideshow', 'ml-slider') . '</h3>'
+                            . $pages . '
+                    </div>
+                </div>';
+        } else {
+            return esc_html__('Not found.', 'ml-slider');
+        }
+    }
+
+
+    private function get_posts_using_slideshow($slideshow_id)
+    {
+        global $wpdb;
+        $results = [];
+
+        $posts = $wpdb->get_results(
+            $wpdb->prepare(
+                "SELECT ID, post_title, post_type FROM {$wpdb->posts} WHERE post_status = %s AND (post_content LIKE %s OR post_content REGEXP %s)",
+                'publish',
+                '%' . $wpdb->esc_like("[metaslider id="$slideshow_id"") . '%',
+                '\[metaslider[^]]*id=["\']?' . $slideshow_id . '["\']?[^]]*\]'
+            )
+        );
+
+        // Organize by post type
+        $grouped_results = [];
+
+        foreach ($posts as $post) {
+            $post_type_obj = get_post_type_object($post->post_type);
+            $post_type_label = $post_type_obj ? $post_type_obj->labels->singular_name : ucfirst($post->post_type);
+
+            $grouped_results[$post_type_label][] = sprintf(
+                '<li><a href="%s" target="_blank">%s</a></li>',
+                esc_url(get_permalink($post->ID)),
+                esc_html($post->post_title)
+            );
+        }
+
+        $theme_mods = get_option('theme_mods_' . get_option('stylesheet'), []);
+        if (is_array($theme_mods)) {
+            foreach ($theme_mods as $key => $value) {
+                if (is_string($value) && preg_match('/[metaslider[^]]*id=["']?' . $slideshow_id . '["']?[^]]*]/', $value)) {
+                    $grouped_results[esc_html__('Theme Setting', 'ml-slider')][] = "<li>" . esc_html($key) . "</li>";
+                }
+            }
+        }
+
+        $all_options = $wpdb->get_results(
+            $wpdb->prepare(
+                "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_value LIKE %s",
+                '%' . $wpdb->esc_like("[metaslider id="$slideshow_id"") . '%'
+            )
+        );
+
+        foreach ($all_options as $option) {
+            if (is_string($option->option_value) && preg_match('/[metaslider[^]]*id=["']?' . $slideshow_id . '["']?[^]]*]/', $option->option_value)) {
+                $grouped_results[esc_html__('Option', 'ml-slider')][] = "<li>" . esc_html($option->option_name) . "</li>";
+            }
+        }
+
+        $output = '';
+        foreach ($grouped_results as $category => $items) {
+            $output .= "<h5>{$category}:</h5><ul>" . implode('', $items) . "</ul>";
+        }
+
+        return empty($output) ? esc_html__('Not found.', 'ml-slider') : $output;
+    }
+
+    public function extra_tablenav( $which )
+    {
+        if ( $which == "top" ) {
+            echo '<input type="hidden" name="page" value="' . esc_attr($_REQUEST['page'] ?? 'metaslider') . '">';
+            $this->render_filters();
+            if (isset($_REQUEST['post_status']) && $_REQUEST['post_status'] == "trash") {
+                if ( ! empty($this->table_data('', 'trash'))) {
+                    submit_button( __( 'Empty Trash' ), 'apply', 'delete_all', false );
+                }
+            }
+        }
+    }
+
+    /**
+     * Renders table nav filter dropdowns; currently includes theme, extendable for future filters.
+     *
+     * @since 3.110.0
+     *
+     * @return void
+     */
+    private function render_filters()
+    {
+        $status = (isset($_GET['post_status']) && 'trash' === $_GET['post_status']) ? 'trash' : 'publish';
+        $slideshows = $this->get_slideshow_query($status);
+
+        $themes = [];
+        $has_no_theme = false;
+        foreach ($slideshows as $slideshow) {
+            $theme = get_post_meta($slideshow['ID'], 'metaslider_slideshow_theme', true);
+            if ($theme && isset($theme['folder'], $theme['title'])) {
+                $themes[$theme['folder']] = $theme['title'];
+            } else {
+                $has_no_theme = true;
+            }
+        }
+
+        if (empty($themes) && !$has_no_theme) return;
+
+        asort($themes);
+
+        $current = isset($_REQUEST['metaslider_theme']) ? sanitize_text_field($_REQUEST['metaslider_theme']) : '';
+
+        echo '<div class="alignleft actions">';
+        echo '<select name="metaslider_theme" id="metaslider-theme-filter">';
+        echo '<option value="">' . esc_html__('All Themes', 'ml-slider') . '</option>';
+        foreach ($themes as $folder => $title) {
+            printf(
+                '<option value="%s"%s>%s</option>',
+                esc_attr($folder),
+                selected($current, $folder, false),
+                esc_html($title)
+            );
+        }
+        if ($has_no_theme) {
+            printf(
+                '<option value="__none__"%s>%s</option>',
+                selected($current, '__none__', false),
+                esc_html__('(No Theme)', 'ml-slider')
+            );
+        }
+        echo '</select> ';
+        submit_button(__('Filter', 'ml-slider'), 'button', 'filter_action', false);
+        echo '</div>';
+    }
+
+    public function check_num_rows()
+    {
+        $table_data = $this->table_data();
+        return $table_data;
+    }
+
+    protected function process_action()
+    {
+        $action = $this->current_action();
+
+        if (isset($_POST['delete_all'])
+            || ($action && in_array($action, array('delete', 'restore', 'permanent')))
+        ) {
+            // Check nonce
+            if (! isset($_REQUEST['_wpnonce'])
+                || empty($_REQUEST['_wpnonce'])
+                || ! wp_verify_nonce(
+                    sanitize_key($_REQUEST['_wpnonce']),
+                    'bulk-' . $this->_args['plural']
+                )
+            ) {
+                wp_die('Cannot process action', 'ml-slider');
+            }
+
+            if (isset($_POST['delete_all'])) {
+                $slideshows = $this->table_data('', 'trash');
+                foreach($slideshows as $slideshow_id) {
+                    wp_delete_post($slideshow_id['ID'], true);
+                }
+
+                return;
+            }
+
+            if(isset($_REQUEST['slideshows'])) {
+
+                if(is_array($_REQUEST['slideshows'])) {
+                    $slideshows = array_map('intval', $_REQUEST['slideshows']);
+                } else {
+                    $toArray = array($_REQUEST['slideshows']);
+                    $slideshows = array_map('intval', $toArray);
+                }
+            } else {
+                //single slider
+                if(isset($_REQUEST['id'])) {
+                    $toArray = array($_REQUEST['id']);
+                    $slideshows = array_map('intval', $toArray);
+                }
+            }
+
+            switch ( $action ) {
+                case 'delete':
+                    foreach($slideshows as $slideshow_id) {
+                        wp_update_post(array(
+                            'ID' => $slideshow_id,
+                            'post_status' => 'trash'
+                        ));
+                    }
+                    break;
+                case 'permanent':
+                    foreach($slideshows as $slideshow_id) {
+                        wp_delete_post( $slideshow_id, true);
+                    }
+                    break;
+                case 'restore':
+                    foreach($slideshows as $slideshow_id) {
+                        wp_update_post(array(
+                            'ID' => $slideshow_id,
+                            'post_status' => 'publish'
+                        ));
+
+                        $slides = $this->get_slides($slideshow_id, 'trash');
+                        foreach ($slides as $key => $slide) {
+                            wp_update_post(array(
+                                'ID' => $slide->ID,
+                                'post_status' => 'publish'
+                            ));
+                        }
+                    }
+                    break;
+                default:
+                    return;
+                    break;
+            }
+        }
+
+        return;
+    }
+}
--- a/ml-slider/admin/lib/helpers.php
+++ b/ml-slider/admin/lib/helpers.php
@@ -509,8 +509,12 @@
 function metaslider_filter_unsafe_html( $content, $slide, $slider_id, $settings )
 {
     try {
-        if ( ! class_exists( 'HTMLPurifier' ) ) {
-            require_once( METASLIDER_PATH . 'lib/htmlpurifier/library/HTMLPurifier.auto.php' );
+        if ( ! class_exists( 'HTMLPurifier_Config' ) || ! class_exists( 'HTMLPurifier' ) ) {
+            $autoloader = METASLIDER_PATH . 'lib/htmlpurifier/library/HTMLPurifier.auto.php';
+            if ( ! file_exists( $autoloader ) ) {
+                throw new RuntimeException( 'HTMLPurifier autoloader not found: ' . $autoloader );
+            }
+            require_once( $autoloader );
         }
         $config = HTMLPurifier_Config::createDefault();
         // How to filter:
@@ -522,8 +526,7 @@
         $config   = apply_filters('metaslider_html_purifier_config', $config, $slide, $slider_id, $settings);
         $purifier = new HTMLPurifier( $config );
         $content  = $purifier->purify( $content );
-    } catch ( Exception $e ) {
-        // If something goes wrong then escape
+    } catch ( Throwable $e ) {
         $content = htmlspecialchars( do_shortcode( $content ), ENT_NOQUOTES, 'UTF-8' );
     }

--- a/ml-slider/admin/routes/api.php
+++ b/ml-slider/admin/routes/api.php
@@ -1168,7 +1168,7 @@

         // If there are files here, then we need to prepare them
         // Dont use get_file_params() as it's WP4.4
-        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- REST route access is checked by can_access(), and uploaded files are validated by process_uploads().
         $images = isset($_FILES['files']) ? $this->process_uploads($_FILES['files'], $data['image_data']) : array();

         // $images should be an array of image data at this point
--- a/ml-slider/inc/slider/metaslider.class.php
+++ b/ml-slider/inc/slider/metaslider.class.php
@@ -56,9 +56,10 @@
         if (
             is_array($settings) &&
             isset($settings['type']) &&
-            in_array($settings['type'], array( 'flex', 'coin', 'nivo', 'responsive' ))
+            in_array($settings['type'], array( 'flex', 'coin', 'nivo', 'responsive' ))
         ) {
-            return $settings;
+            // Only trust keys we actually recognise
+            return array_intersect_key($settings, $this->get_default_parameters());
         } else {
             return $this->get_default_parameters();
         }
@@ -76,6 +77,11 @@
             $defaults = $this->get_default_parameters();

             if (isset($defaults[$name])) {
+                // Make sure to return a string, even if the default is boolean
+                if (gettype($defaults[$name]) == 'boolean') {
+                    return $defaults[$name] ? 'true' : 'false';
+                }
+
                 return $defaults[$name] ? $defaults[$name] : 'false';
             }
         } else {
@@ -644,6 +650,26 @@
     }

     /**
+     * Coerce a setting into a JS-safe integer token, without turning an empty/disabled
+     * setting (e.g. an unset width/height, normalised by get_setting() to the string
+     * 'false') into 0. Anything that's neither numeric nor the 'true'/'false' sentinel
+     * is untrusted and is coerced to 0 rather than ever being output raw.
+     *
+     * @since 3.111.1
+     *
+     * @param mixed $val
+     * @return int|string
+     */
+    protected function to_js_int($val)
+    {
+        if (is_numeric($val)) {
+            return (int) $val;
+        }
+
+        return ($val === 'true' || $val === 'false') ? $val : 0;
+    }
+
+    /**
      * Build the javascript parameter arguments for the slider.
      *
      * @return string parameters
@@ -657,8 +683,10 @@
             if ($param = $this->get_param($name)) {
                 $val = $this->get_setting($name);

-                if (gettype($default) == 'integer' || $val == 'true' || $val == 'false') {
-                    $options[$param] = $val;
+                if (is_int($default)) {
+                    $options[$param] = $this->to_js_int($val);
+                } elseif (is_bool($default) || $val === 'true' || $val === 'false') {
+                    $options[$param] = $val === 'true' ? 'true' : 'false';
                 } else {
                     $options[$param] = '"' . esc_js($val) . '"';
                 }
--- a/ml-slider/inc/slider/metaslider.flex.class.php
+++ b/ml-slider/inc/slider/metaslider.flex.class.php
@@ -80,13 +80,13 @@
     {
         if (isset($options["carouselMode"])) {
             if ($options["carouselMode"] == "true") {
-                $options["itemWidth"] = $this->get_setting('width');
+                $options["itemWidth"] = $this->to_js_int($this->get_setting('width'));
                 $options["animation"] = "'slide'";
                 $options["direction"] = "'horizontal'";
-                $options["minItems"] = $this->get_setting('minItems');
-                $options["maxItems"] = $this->get_setting('maxItems');
+                $options["minItems"] = $this->to_js_int($this->get_setting('minItems'));
+                $options["maxItems"] = $this->to_js_int($this->get_setting('maxItems'));
                 $options["move"] = 1;
-                $options["itemMargin"] = apply_filters('metaslider_carousel_margin', $this->get_setting('carouselMargin'), $slider_id);
+                $options["itemMargin"] = $this->to_js_int(apply_filters('metaslider_carousel_margin', $this->get_setting('carouselMargin'), $slider_id));
                 //activate infinite loop when carousel is set to 'continously' and 'autoplay'
                 if($this->get_setting('infiniteLoop') == 'true'){
                     $options["controlNav"] = "false";
@@ -221,7 +221,7 @@
     public function get_carousel_css($css, $settings, $slider_id)
     {
         if (isset($settings['carouselMode']) && $settings['carouselMode'] == 'true') {
-            $margin = apply_filters('metaslider_carousel_margin', $this->get_setting('carouselMargin'), $slider_id);
+            $margin = (int) apply_filters('metaslider_carousel_margin', $this->get_setting('carouselMargin'), $slider_id);
             $css .= "n        #metaslider_{$slider_id}.flexslider .slides li {margin-right: {$margin}px !important;}";
             if(isset($settings['infiniteLoop']) && $settings['infiniteLoop'] == 'true'){

@@ -240,8 +240,8 @@
                     $slides = count($this->slides);
                 }
                 $double = $slides * 2;
-                $animationtime = ($settings['animationSpeed'] * $slides) + ($settings['delay'] * $slides);
-                $transform_width = $margin + $settings["width"];
+                $animationtime = ((int) $settings['animationSpeed'] * $slides) + ((int) $settings['delay'] * $slides);
+                $transform_width = $margin + (int) $settings["width"];
                 $reverse = ( isset( $settings['reverse'] ) && $settings['reverse'] == 'true' ) ? true : false;
                 $start_position = $reverse ? "calc(var(--ms-slide-width) * -" . $slides . ")" : "0";
                 $end_position = $reverse ? "0" : "calc(var(--ms-slide-width) * -" . $slides . ")";
@@ -856,6 +856,13 @@
      */
     public function manage_pausePlay_button($options, $slider_id, $settings)
     {
+        // Don't show the pause/play button when there's only one slide
+        if (count($this->slides) <= 1) {
+            $options['pausePlay'] = 'false';
+            remove_filter('metaslider_flex_slider_parameters', array($this, 'manage_pausePlay_button'));
+            return $options;
+        }
+
         if (isset($settings['pausePlay']) && $settings['pausePlay'] === 'true') {
             /* @since 3.97 - disable hover on pause when play button is enabled */
             unset($options['pauseOnHover']);
@@ -874,7 +881,7 @@
                 $script = "$('.flex-pauseplay a').removeClass('flex-pause').addClass('flex-play');";

                 if ($showPlayText && !empty($settings['playText'])) {
-                    $script .= "$('.flex-pauseplay a').text('" . addslashes($settings['playText']) . "');";
+                    $script .= "$('.flex-pauseplay a').text('" . esc_js($settings['playText']) . "');";
                 }

                 $options['start'] = array_merge($options['start'], [$script]);
@@ -890,10 +897,10 @@
                 "var ms_pause_play_sync = function() {
                     var pausePlayBtn = $('#metaslider_" . $slider_id . " .flex-pauseplay a');
                     if (pausePlayBtn.hasClass('flex-pause')) {
-                        pausePlayBtn.attr('aria-label', '" . addslashes($pauseText) . "');
+                        pausePlayBtn.attr('aria-label', '" . esc_js($pauseText) . "');
                         {$ariaLiveOff}
                     } else {
-                        pausePlayBtn.attr('aria-label', '" . addslashes($playText) . "');
+                        pausePlayBtn.attr('aria-label', '" . esc_js($playText) . "');
                         {$ariaLiveOn}
                     }
                 };
--- a/ml-slider/ml-slider.php
+++ b/ml-slider/ml-slider.php
@@ -5,7 +5,7 @@
  * Plugin Name: MetaSlider Slideshow
  * Plugin URI:  https://www.metaslider.com
  * Description: MetaSlider gives you the power to create a beautiful slideshow, carousel, or gallery on your WordPre

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.